6

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?

2
  • Possible duplicate of SO answer. Commented Dec 5, 2016 at 7:49
  • @MihailStancescu I saw this question. It works fine with DataAnnotations. but if I use FluentValidation or similar, It won't work. So seems something is missing here.
    – SajithK
    Commented Dec 5, 2016 at 8:24

2 Answers 2

3

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.

2
  • @Sajith I have tried the same, but the validation not triggered for the sub entity used in the model. Any option to invoke validations for the entities used in the main model Commented Mar 27, 2018 at 12:01
  • @MagendranV i'm quite curious if you've managed to find a solution since I've hit the same dead end. The DataAnnotations validation works brilliantly for the 1st level properties, but anything any deeper and it doesn't work
    – Andrei U
    Commented Apr 30, 2018 at 15:50
0

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>();
    }
}
4
  • Web API does not have DefaultModelBinder. DefaultModelBinder comes with MVC.
    – SajithK
    Commented Dec 5, 2016 at 8:21
  • @sajith, when you develop Web API in a MVC .NET application, DefaultModelBinder will come :)
    – Bob Dust
    Commented Dec 5, 2016 at 8:27
  • Yah I know that. But the thing is that you can not use your PaginationModelBinder : DefaultModelBinder in a controller extended ApiController.
    – SajithK
    Commented Dec 5, 2016 at 8:36
  • Have you tried your code? I tried this earlier. it gives the following error, Could not create a 'IModelBinder' from 'PaginationModelBinder'. Please ensure it derives from 'IModelBinder' and has a public parameterless constructor. I think the reason is that the model binder must be derived from System.Web.Http.ModelBinding.IModelBinder. But DefaultModelBinder is in System.Web.Mvc namespace.
    – SajithK
    Commented Dec 5, 2016 at 9:01

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.