简体   繁体   English

将路由查询参数绑定到默认属性而不在 URL 中指定它

[英]Bind route query parameter to a default property without specifying it in the URL

I'm creating a .NET Core 3 WebApi and i'm having some 'troubles' with modelbinding from the query parameters.我正在创建一个 .NET Core 3 WebApi,但我在查询参数的模型绑定方面遇到了一些“麻烦”。 I have a Range class with Min , Max end Value properties.我有一个具有MinMax end Value属性的Range类。 The usage of this class is to filter with a range or a constant value.这个类的用途是用一个范围或一个常数值进行过滤。

    public class Range
    {
        public int? Min { get; set; }
        public int? Max { get; set; }
        public int? Value { get; set; }
        public static implicit operator Range(int value) => new Range {Value = value};
    }

When i don't define a sub property i want it to bind to the Value property, like api/contacts?age=40 .当我没有定义子属性时,我希望它绑定到Value属性,例如api/contacts?age=40
The Min and Max works as planned with the default binding way like api/contacts?age.min=18&age.max=30 . MinMax按计划使用默认绑定方式工作,如api/contacts?age.min=18&age.max=30 I thought adding an implicit operator would work but it doesn't.我认为添加一个隐式运算符会起作用,但它不会。

Is there a way (like an attribute) to make Value as default property?有没有办法(如属性)将Value作为默认属性?

Implicit operator does an implicit conversion when you assign an 'int' value to a 'Range' object.当您将“int”值分配给“Range”对象时,隐式运算符会进行隐式转换。 However, it could not be applied in model binding scenario of ASP.NET Core.但是,它不能应用于 ASP.NET Core 的模型绑定场景。 The model binding only checks the data from request and call the model binder to bind data for different types of classes.模型绑定只检查请求中的数据,并调用模型绑定器为不同类型的类绑定数据。

The asp.net core framework also provide 'TypeConverter' to determine whether it should convert a string to an object. asp.net 核心框架还提供了“TypeConverter”来确定是否应该将字符串转换为对象。 However, it could only deal with one query string and disturb a common model binding process.但是,它只能处理一个查询字符串并干扰常见的模型绑定过程。 (eg age.max and age.min). (例如age.max 和age.min)。

For your problem, the only way I suggest is to construct a custom model binder so that it will bind data following the custom rules: (Make your case as an example)对于您的问题,我建议的唯一方法是构建一个自定义模型绑定器,以便它按照自定义规则绑定数据:(以您的案例为例)

The steps of the custom model binding for complex model as below:复杂模型的自定义模型绑定步骤如下:

  1. Create a custom binder, for example, RangeEntityBinder which should extend IModelBinder创建一个自定义绑定器,例如,应该扩展IModelBinder RangeEntityBinder

  2. Create a custom binder provider, for example, RangeEntityBinderProvider which should extend IModelBinderProvider创建一个自定义的绑定器提供程序,例如RangeEntityBinderProvider ,它应该扩展IModelBinderProvider

  3. Register the binder provider into ModelBinderProviders in ConfigureServices of Startup file在 Startup 文件的 ConfigureServices 中将 binder provider 注册到ModelBinderProviders

According to your description, I construct a demo which you could refer to.根据你的描述,我搭建了一个demo,你可以参考。

Controller:控制器:

public IActionResult RangePage(Range age) 
        {
            if(age == null)
            {
                age = new Range();
            }
            return View(age);
        }

RangePage.cshtml: RangePage.cshtml:

<a asp-controller="Home" asp-action="RangePage" asp-route-age.min="18" asp-route-age.max="30">age.min=18&age.max=30</a>
<br />
<a asp-controller="Home" asp-action="RangePage" asp-route-age="40">age=40</a>
<div class="container">
    <label asp-for="Max">Max: </label>@Model.Max
    <br />
    <label asp-for="Min">Min: </label>@Model.Min
    <br />
    <label asp-for="Value">Value: </label>@Model.Value
</div>

Range.cs Range.cs

public class Range
{
    public int? Min { get; set; }
    public int? Max { get; set; }

    public int? Value { get; set; }

}

RangeEntityBinder.cs RangeEntityBinder.cs

public class RangeEntityBinder : IModelBinder
    {
        private readonly ComplexTypeModelBinder worker;


        public RangeEntityBinder(Dictionary<ModelMetadata, IModelBinder> propertyBinders, ILoggerFactory loggerFactory)
        {
            worker = new ComplexTypeModelBinder(propertyBinders, loggerFactory);
        }

        public async Task BindModelAsync(ModelBindingContext bindingContext)
        {
            if (bindingContext == null)
            {
                throw new ArgumentNullException(nameof(bindingContext));
            }

            // Try get the "age" to populate the model
            var modelName = bindingContext.ModelName;

            // Try to fetch the value of the argument by name
            var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);

            // If find 'age', return Range object with Value="age"
            // If not, use ComplexTypeModelBinder do the common binding
            if (valueProviderResult == ValueProviderResult.None)
            {
                await this.worker.BindModelAsync(bindingContext);
                if (!bindingContext.Result.IsModelSet)
                {
                    return;
                }

                var model= bindingContext.Result.Model as Range;
                if (model== null)
                {
                    throw new InvalidOperationException($"Expected {bindingContext.ModelName} to have been bound by ComplexTypeModelBinder");
                }
            }
            else
            {
                var value = valueProviderResult.FirstValue;

                // Check if the argument value is null or empty
                if (string.IsNullOrEmpty(value))
                {
                    await Task.CompletedTask;
                }

                if (!int.TryParse(value, out var ageValue))
                {
                    // Non-integer arguments result in model state errors
                    bindingContext.ModelState.TryAddModelError(
                        modelName, "Age value must be an integer.");

                    await Task.CompletedTask;
                    return;
                }

                var model = new Range()
                {
                    Value = ageValue
                };

                bindingContext.Result = ModelBindingResult.Success(model);
                await Task.CompletedTask;
            }


        }
    }

RangeEntityBinderProvider.cs RangeEntityBinderProvider.cs

public class RangeEntityBinderProvider: IModelBinderProvider
    {

        public IModelBinder GetBinder(ModelBinderProviderContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            // If type is Range, use RangeEntityBinder to bind model
            if (context.Metadata.ModelType == typeof(Range))
            {
                var propertyBinders = new Dictionary<ModelMetadata, IModelBinder>();
                for (var i = 0; i < context.Metadata.Properties.Count; i++)
                {
                    var property = context.Metadata.Properties[i];
                    propertyBinders.Add(property, context.CreateBinder(property));
                }

                var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>();

                return new RangeEntityBinder(propertyBinders, loggerFactory);
            }

            return null;
        }
    }

ConfigureServices method配置服务方法

public void ConfigureServices(IServiceCollection services)
        {
            /* Other services*/

            services.AddMvc(options =>
            {
                options.ModelBinderProviders.Insert(0, new RangeEntityBinderProvider());
            });

            /* Other services*/

        }

Demo:演示:

在此处输入图片说明

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM