1

In this class library (targeting .NET Standard 2.0 and .NET Framework 4.6.1), I am doing data validation at the data context level, as these classes may be used by non-web applications, so I'm following the example from https://github.com/dotnet/efcore/issues/9662 in a static helper class:

internal static ICollection<ValidationResult> ValidateChanges(DbContext context)
{
    var modifiedEntries = context.ChangeTracker.Entries()
                                 .Where(x => x.State == EntityState.Added || 
                                             x.State == EntityState.Modified);
    List<ValidationResult> validationResults = new List<ValidationResult>();

    foreach (var entry in modifiedEntries)
    {
        var validationContext = new ValidationContext(entry);
        Validator.TryValidateObject(entry, validationContext, validationResults, true);
    }

    return validationResults;
}

My POCO (simplified):

public class Person
{
    [Required]
    public int Id { get; set; }
    [Required]
    public string FirstName { get; set; }
    [Required]
    public string LastName { get; set; }
    // other properties
}

My unit test (simplified):

[Fact]
public void TestDataValidation()
{
    IPersonService svc = _container.Resolve<IPersonService>();
    Person p = svc.Get(1);
    string origValue = p.FirstName;
    p.FirstName = string.Empty;

    try
    {
        Assert.Throws<ValidationException>(() => svc.Update(p, p.Id));
    }
    catch (Xunit.Sdk.XunitException) // ValidationException not thrown
    {
        // restore original value

        throw;
    }
}

The service class method:

    public virtual TEntity Update(TEntity entity, TKey key)
    {
        if (entity == null)
            throw new ArgumentNullException(nameof(entity));

        ((IDatabaseContext)Context).BeginTransaction();
        TEntity dbEntity = Get(key);
        Context.Entry(dbEntity).CurrentValues.SetValues(entity);
        ((IDatabaseContext)Context).Commit();
        return entity;
    }

The Commit() method:

public abstract class DatabaseContext<TContext> : DbContext, IDatabaseContext
    where TContext : DbContext
{
    public void Commit()
    {
        ICollection<ValidationResult> validationResults = DbContextHelper.ValidateChanges(this);
        if (validationResults.Count > 0)
        {
            foreach (ValidationResult validationResult in validationResults)
            {
                string message = string.Format("{0}: {1}", string.Join(", ", validationResult.MemberNames.ToArray()), validationResult.ErrorMessage);
                throw new ValidationException(message);
            }
        }
        base.Commit();
    }
}

When I execute the test, the validation complete fails. When I put a break point in the middle of the foreach loop of the validation method, this is what I see:

  • modifiedEntries has one item (as expected)
  • entry.CurrentValues["FirstName"] is an empty string (as expected)
  • When you view it in the debugger watch list, entry.CurrentValues.Properties[5] shows {Property: Person.FirstName (string) Required} (seems right)
  • validationContext.Items shows Count = 0 (wait, what? shouldn't this be 1?)

Then when I reach the end of the validation method, validationResults is empty, even though there definitely should be something in there.

According to Data annotations attributes not working in asp net core you need AddDataAnnotations() on the ServiceCollection, but that's in ASP.NET MVC Core. Is there something similar I needed to do for my unit test application? Am I missing anything else?

Thanks in advance

1 Answer 1

0

You can try something like that

//Assert
foreach (var item in ValidateModel(itemToCreate))
{
    if(!string.IsNullOrEmpty(item.ErrorMessage)) throw new ValidationException(item.ErrorMessage); 
}

/////

private static IList<ValidationResult> ValidateModel(object model)
{
    var validationResults = new List<ValidationResult>();
    var ctx = new ValidationContext(model, null, null);
    Validator.TryValidateObject(model, ctx, validationResults, true);
    return validationResults;
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.