Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Revalidate Model When Using WebAPI (TryValidateModel equivalent)

Using vanilla MVC I can revalidate my model with TryValidateModel. The TryValidateModel method doesn't seem to be applicable to WebAPI. How can I revalidate my model when using WebAPI?

like image 280
Mark Avatar asked Oct 16 '12 01:10

Mark


3 Answers

I know it has been a while since this has been asked, but the problem is still valid. Thus i thought i should share my solution to this problem. I decided to implement the TryValidateModel(object model) myself, based on the implementation in the System.Web.Mvc.Controller.cs

The problem is that the mvc's TryValidateModel internally used their own HttpContext and ModelState. If you go and compaire the two, they are very similar....

The be able to use our own HttpContext there exists a HttpContextWrapper that can be used for that.
And Since we have to clear our model state, it doesn't really matter that we use a different type of ModelState , as long as we get the desired result, thus i create a new ModelState object from the correct type...
I did add the error to the ModelState of the controller and not to the model state to the newly created ModelState , This seems to work just fine for me :)
Here is my code, that i just added to the controller...
do not forget to import the library...

using System.Web.ModelBinding;

    protected internal bool TryValidateModel(object model)
    {
        return TryValidateModel(model, null /* prefix */);
    }

    protected internal bool TryValidateModel(object model, string prefix)
    {
        if (model == null)
        {
            throw new ArgumentNullException("model");
        }

        ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType());
        var t = new ModelBindingExecutionContext(new HttpContextWrapper(HttpContext.Current), new System.Web.ModelBinding.ModelStateDictionary());

        foreach (ModelValidationResult validationResult in ModelValidator.GetModelValidator(metadata, t).Validate(null))
        {
            ModelState.AddModelError(validationResult.MemberName, validationResult.Message);
        }

        return ModelState.IsValid;
    }
like image 89
rik.vanmechelen Avatar answered Dec 08 '22 00:12

rik.vanmechelen


I don't know when was it added but now there is Validate method on api controller.

ApiController.Validate Method (TEntity) https://msdn.microsoft.com/en-us/library/dn573258%28v=vs.118%29.aspx

like image 45
hex Avatar answered Dec 07 '22 22:12

hex


Based from rik-vanmechelen original answer, here is my version that relies on the services container exposed by Web API.

    /// <summary>
    /// Tries to validate the model.
    /// </summary>
    /// <param name="model">The model.</param>
    /// <returns>Whether the model is valid or not.</returns>
    protected internal bool TryValidateModel(object model)
    {
        if (model == null)
        {
            throw new ArgumentNullException("model");
        }

        var metadataProvider = Configuration.Services.GetService<System.Web.Http.Metadata.ModelMetadataProvider>();
        var validatorProviders = Configuration.Services.GetServices<System.Web.Http.Validation.ModelValidatorProvider>();
        var metadata = metadataProvider.GetMetadataForType(() => model, model.GetType());

        ModelState.Clear();
        var modelValidators = metadata.GetValidators(validatorProviders);
        foreach (var validationResult in modelValidators.SelectMany(v => v.Validate(metadata, null)))
        {
            ModelState.AddModelError(validationResult.MemberName, validationResult.Message);
        }

        return ModelState.IsValid;
    }

This uses the following simple extension methods to access the services :

  /// <summary>
  /// Services container extension methods.
  /// </summary>
  public static class ServicesContainerExtensions
  {
    /// <summary>
    /// Gets the service.
    /// </summary>
    /// <typeparam name="TService">The type of the service.</typeparam>
    /// <param name="services">The services.</param>
    /// <returns>The service.</returns>
    /// <exception cref="System.ArgumentNullException">services</exception>
    public static TService GetService<TService>(this ServicesContainer services)
    {
        if (services == null)
        {
            throw new ArgumentNullException("services");
        }
        return (TService)((object)services.GetService(typeof(TService)));
    }

    /// <summary>
    /// Gets the services.
    /// </summary>
    /// <typeparam name="TService">The type of the service.</typeparam>
    /// <param name="services">The services.</param>
    /// <returns>The services.</returns>
    /// <exception cref="System.ArgumentNullException">services</exception>
    public static IEnumerable<TService> GetServices<TService>(this ServicesContainer services)
    {
        if (services == null)
        {
            throw new ArgumentNullException("services");
        }
        return services.GetServices(typeof(TService)).Cast<TService>();
    }
}

The advantage of using this method is that it reuses the MetadataProvider and ValidatorProvider(s) you have configured for your Web API application while the previous answer is retrieving the one configured in ASP.NET MVC. ASP.NET MVC and WebAPI run through different pipelines.

like image 35
Onlyann Avatar answered Dec 07 '22 22:12

Onlyann