SajithK
SajithK

Reputation: 1032

Web API Validation not trigger with custom model binder

I'm building Web Service with Web API 5. I'm implementing custom model binder by extending IModelBinder interface to map complex type as a parameter to action. The binding part is working fine. But Model validation does not occur. ModelState.IsValid is always true.

public class PagingParamsVM
{
        [Range(1, Int32.MaxValue, ErrorMessage = "Page must be at least 1")]
        public int? Page { get; set; }

        [Range(1, Int32.MaxValue, ErrorMessage = "Page size must be at least 1")]
        public int? PageSize { get; set; }
}

public class PaginationModelBinder : IModelBinder
{
        public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
        {
              var model = (PagingParamsVM)bindingContext.Model ?? new PagingParamsVM();
              //model population logic
              .....

              bindingContext.Model = model;
              return true;
        }
}

public IEnumerable<NewsItemVM> Get([ModelBinder(typeof(PaginationModelBinder))]PagingParamsVM pegination)
{
            //Validate(pegination); //if I call this explicitly ModelState.IsValid is set correctly.
            var valid = ModelState.IsValid; //this is always true
}

public class ModelStateValidationActionFilter : ActionFilterAttribute
{
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            var valid = actionContext.ModelState.IsValid //this is always true.
        }
}

If I call Validate() explicitly or use [FromUri] attribute, ModelState.IsValid is set correctly.

public IEnumerable<NewsItemVM> Get([FromUri]PagingParamsVM pegination)
{
            var valid = ModelState.IsValid;
}

Should I implement validation part inside model binder. If so how should I implement?

Upvotes: 6

Views: 2020

Answers (2)

SajithK
SajithK

Reputation: 1032

I found an answer. The default validation process can be invoked in custom model binder as follows,

public abstract class PaginationModelBinder : IModelBinder
{
        public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
        {
              var model = (PagingParamsVM)bindingContext.Model ?? new PagingParamsVM();
              //model population logic
              .....

              bindingContext.Model = model;

              //following lines invoke default validation on model
              bindingContext.ValidationNode.ValidateAllProperties = true;
              bindingContext.ValidationNode.Validate(actionContext);

              return true;
        }
}

Thank you guys for your support.

Upvotes: 3

Bob Dust
Bob Dust

Reputation: 2460

DefaultModelBinder.CreateModel should help you keep model state:

public class PaginationModelBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
        if(modelType == typeof(PagingParamsVM))
        {
            var page = default(int?);
            var model = bindingContext.Model;
            var valueProvider = bindingContext.ValueProvider;
            var pageValue = valueProvider.GetValue("Page");
            var tmp = default(int);
            if(pageValue != null && int.TryParse(pageValue.AttemptedValue, out tmp))
            {
                page = tmp;
            }

            var pageSize = default(int?);
            var sizeValue = valueProvider.GetValue("PageSize");
            if(sizeValue != null && int.TryParse(sizeValue.AttemptedValue, out tmp))
            {
                pageSize = tmp;
            }
            return new PagingParamsVM { Page = page, PageSize = pageSize };
        }
        return base.CreateModel(controllerContext, bindingContext, modelType);
    }
}

A web api controller that uses the binder can be:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;

public class NewsItemController : ApiController
{
    public IEnumerable<NewsItemVM> Get([ModelBinder(typeof(PaginationModelBinder))]PagingParamsVM pegination)
    {
        //Validate(pegination); //if I call this explicitly ModelState.IsValid is set correctly.
        var valid = ModelState.IsValid; //this is always true
        return Enumerable.Empty<NewsItemVM>();
    }
}

Upvotes: 0

Related Questions