4

I have a Validation Framework set up to automatically validate parameters of Web Api action methods (including OData action methods). However, this does not work for PATCH requests that store the changed properties in a Delta<Entity> type.

I've done some digging around and, as you can see in the ASP.NET source code of Delta, it has the NonValidatingParameterBinding attribute, which means that Delta's are not subjected to validation.

Now an obvious solution would be to apply the delta and then perform manual validation of the resulting entity.

But are there other solutions that do not require applying the patch? Ideally it should happen automatically before the action method is called...

Thanks.

2 Answers 2

4

It was an explicit design decision to not validate Delta<Entity>. Reason being, a Delta<Entity> is only a partial entity and by definition could be invalid. As you have guessed, the proper approach is to validate the entity after the Delta<TEntity> is applied on top of it.

Sample code,

public void Patch(Delta<Customer> delta)
{
    Customer c = new Customer();
    delta.Patch(c);

    Validate(c, typeof(Customer));
}

private void Validate(object model, Type type)
{
    var validator = Configuration.Services.GetBodyModelValidator();
    var metadataProvider = Configuration.Services.GetModelMetadataProvider();

    HttpActionContext actionContext = new HttpActionContext(ControllerContext, Request.GetActionDescriptor());

    if (!validator.Validate(model, type, metadataProvider, actionContext, String.Empty))
    {
        throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState));
    }
}

Complete sample here.

3
  • Well that is an interesting design decision, considering that there are validators for single properties. I have created a filter that validates Delta's one changed property at a time and it works fine... what's so bad about it? Validation logic here: pastebin.com/npSMxg9y ; Filter attribute here: pastebin.com/ARnstAJD Commented Sep 11, 2013 at 7:08
  • Your FilterAttribute relies on the assumption that the framework runs validation on Delta<T>. That assumption is incorrect. Delta<T> explicitly opts out of validation. So, there wouldn't be any validation errors in ModelState and ModelState.IsValid would be true even if the model is invalid. Commented Sep 17, 2013 at 20:43
  • That's why the attribute calls searchForAndValidateDelta, which looks for a Delta parameter passed to the action and validates each changed property value separately. And this is working superbly, by the way :) My question is if there are any ill side-effects I should be aware of. Commented Sep 18, 2013 at 13:44
3

We too arrived at this problem. However, we are using a custom version of Delta<T> (heavily based on OData).

The core issue (apart from [NonValidatingParameterBinding]) is that the entity is hidden (behind delta.GetEntity()) and hence will not be validated. Also we only want to validate changed properties.

We solved this by adding the IValidateObject interface to our custom Delta class, making the signature as follows:

[NonValidatingParameterBinding]
public class Delta<TEntityType> : DynamicObject, IDelta, IValidatableObject
    where TEntityType : class

This can probably also be done in a class inheriting from OData's Delta<T>, but that is not something we have tried.

Implementation of the interface looks like this:

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        var entity = this.GetEntity();

        var innerValidationContext = new ValidationContext(entity);

        List<ValidationResult> validationResults = new List<ValidationResult>();

        foreach (var propertyName in this.GetChangedPropertyNames())
        {
            innerValidationContext.MemberName = propertyName;
            Validator.TryValidateProperty(EntityType.GetProperty(propertyName).GetValue(entity), innerValidationContext, validationResults);
        }

        return validationResults;
    }

Hope this helps! /Victor

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.