Пользовательская модель Binder в Asp.Net Core для подклассов

У меня есть сценарий, где у меня есть определенный базовый класс, мы будем называть его "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;
    }
}

а затем у меня есть другие классы, которые будут наследоваться от "PagingCriteriaBase", например

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)
    {
    }
}

Теперь я хотел бы создать связыватель модели, который будет использоваться с методами веб-API, и связыватель модели будет использоваться со всеми подклассами "PagingCriteriaBase", цель связующего устройства состоит в том, чтобы установить некоторые свойства в соответствии с данными, поступающими из запросов AJAX, я попытался сделать следующее:

  1. Я создал класс, который реализует "IModelBinder" следующим образом:

    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;
    }
    }
    
  2. Я создал ModelBinderProvider следующим образом:

    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;
    }
    }
    
  3. Я зарегистрировал модель связующего с помощью:

    services.AddMvc(op => op.ModelBinderProviders.Insert(0, new PagingEntityBinderProvider())) ;
    
  4. В моем методе Web API я сделал следующее:

     public IActionResult GetAll([ModelBinder(typeof(PagingModelBinder))]UserCriteria crit)
    {
     //Code goes here
    }
    

Когда я использовал связыватель модели, как описано выше, я обнаружил, что как только код достигает методов Web API, ничего из значений в классе не изменяется, например, свойство PageNo остается 1, поэтому мне нужно иметь связыватель модели устанавливает все связанные свойства для объекта подкласса независимо от типа самого класса, и, в конце концов, как только код достигнет метода Web API, в модели все свойства будут установлены правильно, не могли бы вы указать, что мне делать? нужно изменить в моем коде, чтобы справиться с этим?

Обратите внимание, что я использую Asp.Net Core 2.0

1 ответ

Решение

Я полагаю, это потому, что вы не установили какое-либо свойство модели, а только создали его экземпляр.

Я думаю, что мы можем пройти через все свойства подкласса с отражением и установить значение на основе значения состояния модели (при условии, что имя свойства совпадает с ключом состояния модели)

    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;
        ...
    }

И измените свой 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;
    }
}

И удалите атрибут ModelBinder из метода веб-API

public IActionResult GetAll(UserCriteria crit)
{
    //Code goes here
}
Другие вопросы по тегам