Reputation: 87
I have a scenario where I have a certain base class we will call it "PagingCriteriaBase"
public class PagingCriteriaBase : CriteriaBase
{
public Int32 CountOfItemsPerPage { get; set; }
public SortOrder SortingOrder { get; set; }
public String SortBy { get; set; }
public Int32 PageNo { get; set; }
public PagingCriteriaBase(Int32 pageNo,Int32 countOfItemsPerPage, SortOrder sortingOrder, String sortBy,Int32 draw)
{
this.PageNo = pageNo>0?pageNo:1;
this.CountOfItemsPerPage = countOfItemsPerPage>0?countOfItemsPerPage:10;
this.SortBy = sortBy;
this.SortingOrder = sortingOrder;
this.Draw = draw;
}
}
and then I have other classes that will inherit from "PagingCriteriaBase", for example
public class UserCriteria:PagingCriteriaBase
{
public String Email { get; set; }
public String DisplayName { get; set; }
public UserCriteria():base(1,0,SortOrder.Asc,"",1)
{
}
public UserCriteria(Int32 pageNo,Int32 countOfItemsPerPage, SortOrder sortingOrder, String sortBy, Int32 draw)
:base(pageNo, countOfItemsPerPage,sortingOrder,sortBy,draw)
{
}
}
Now what I would like to do is that I wanted to create a Model Binder that will be used with Web API methods, and the model binder will be used with all of the subclasses of "PagingCriteriaBase", the purpose of this model binder is to set some properties according to data coming from ajax requests, I tried to do the following:
I created a class that implements "IModelBinder" as follows:
public class PagingModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (!bindingContext.ModelType.IsSubclassOf(typeof(PagingCriteriaBase)))
{
return Task.FromResult(false);
}
String startModelName = "start";
String lengthModelName = "length";
var startResult = bindingContext.ValueProvider.GetValue(startModelName);
var lengthResult = bindingContext.ValueProvider.GetValue(lengthModelName);
Int32 start, length;
if (!Int32.TryParse(startResult.FirstValue, out start))
{
start = 0;
}
if (!Int32.TryParse(lengthResult.FirstValue, out length))
{
length = SystemProp.PAGE_SIZE;
}
else
{
length = 20;
}
var model = Activator.CreateInstance(bindingContext.ModelType);
Int32 pageNo = (int)Math.Ceiling((decimal)start / length);
bindingContext.ModelState.SetModelValue("PageNo", new ValueProviderResult(pageNo.ToString()));
bindingContext.ModelState.SetModelValue("CountOfItemsPerPage", new ValueProviderResult(length.ToString()));
bindingContext.Model = model;
var mProv = (IModelMetadataProvider)bindingContext.HttpContext.RequestServices.GetService(typeof(IModelMetadataProvider));
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
I created a ModelBinderProvider as follows:
public class PagingEntityBinderProvider:IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.ModelType == typeof(PagingCriteriaBase))
{
return new BinderTypeModelBinder(typeof(PagingModelBinder));
}
return null;
}
}
I registered the model binder using:
services.AddMvc(op => op.ModelBinderProviders.Insert(0, new PagingEntityBinderProvider())) ;
In my Web API method I did the following:
public IActionResult GetAll([ModelBinder(typeof(PagingModelBinder))]UserCriteria crit)
{
//Code goes here
}
When I used the model binder as above I found out that once the code reaches the Web API Methods, nothing from the values in the class is changed, for example "PageNo" property stays 1, So what I need to do is to have the model binder set all the related property for the subclass object regardless of the type of the class itself and in the end once the code reaches the Web API method, the model will have all properties set correctly, can you please point me to what do I need to change in my code to handle this?
Please note that I am using Asp.Net Core 2.0
Upvotes: 1
Views: 2416
Reputation: 690
I guess that's because you haven't set any property of the model, only instantiated it.
I think we can go trough all the properties of the subclass with reflection and set the value based on the model state value (assuming that the property name is the same with the model state key)
public Task BindModelAsync(ModelBindingContext bindingContext)
{
...
bindingContext.ModelState.SetModelValue("PageNo", new ValueProviderResult(pageNo.ToString()));
bindingContext.ModelState.SetModelValue("CountOfItemsPerPage", new ValueProviderResult(length.ToString()));
ModelStateEntry v;
foreach (PropertyInfo pi in bindingContext.ModelType.GetProperties())
{
if (bindingContext.ModelState.TryGetValue(pi.Name, out v))
{
try
{
pi.SetValue(model, v.RawValue);
}
catch
{
}
}
}
bindingContext.Model = model;
...
}
And change your PagingEntityBinderProvider
public class PagingEntityBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (typeof(PagingCriteriaBase).IsAssignableFrom(context.Metadata.ModelType))
{
return new BinderTypeModelBinder(typeof(PagingModelBinder));
}
return null;
}
}
And remove the ModelBinder Attribute from the Web API Method
public IActionResult GetAll(UserCriteria crit)
{
//Code goes here
}
Upvotes: 1