在ASP.NET CORE 1.1项目中,我具有以下模型:

public class GetProductsModel {
  public OrderExpression OrderBy { get; set; }
}

OrderExpression是具有以下方法的类:
Boolean TryParse(String value, out OrderExpression expression)

该方法从OrderExpression创建一个String实例,可以使用:
OrderExpression expression;

Boolean parsed = OrderExpression.TryParse(value, out expression);

如何为OrderExpression类型的属性创建自定义Model Binder?

最佳答案

我假设您的请求数据中有一个属性orderBy,您想使用OrderExpression绑定(bind)到OrderExpression.TryParse中。

假设您的OrderExpression类如下所示,其中我提供了TryParse方法的非常简单的实现:

public class OrderExpression
{
    public string RawValue { get; set; }
    public static bool TryParse(string value, out OrderExpression expr)
    {
        expr = new OrderExpression { RawValue = value };
        return true;
    }
}

然后,您可以创建一个模型装订器,该装订器基本上获取原始字符串值并调用OrderExpression.TryParse:
public class OrderExpressionBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        var values = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
        if (values.Length == 0) return Task.CompletedTask;

        // Attempt to parse
        var stringValue = values.FirstValue;
        OrderExpression expression;
        if (OrderExpression.TryParse(stringValue, out expression))
        {
            bindingContext.ModelState.SetModelValue(bindingContext.ModelName, expression, stringValue);
            bindingContext.Result = ModelBindingResult.Success(expression);
        }

        return Task.CompletedTask;
    }
}

您还将需要一个新的模型绑定(bind)程序提供程序,该提供程序仅针对OrderExpression类型返回新的绑定(bind)程序:
public class OrderExpressionBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        return context.Metadata.ModelType == typeof(OrderExpression) ? new OrderExpressionBinder() : null;
    }
}

// It should be registered in your Startup class, adding it to the ModelBinderProviders collection:
services.AddMvc(opts => {
    opts.ModelBinderProviders.Insert(0, new OrderExpressionBinderProvider());
});

有了这个,您将能够绑定(bind) Controller Action 的OrderExpression参数。以下示例中的内容:
[HttpPost]
public IActionResult Products([FromBody]OrderExpression orderBy)
{
    return Ok();
}

$.ajax({
    method: 'POST',
    dataType: 'json',
    url: '/home/products',
    data: {orderby: 'my orderby expression'}
});

但是,您还需要做其他事情,才能发送json并将其绑定(bind)到内部包含GetProductsModel的复杂模型(例如OrderExpression)。我说的是这样的情况:
[HttpPost]
public IActionResult Products([FromBody]GetProductsModel model)
{
    return Ok();
}

public class GetProductsModel
{
    public OrderExpression OrderBy { get; set; }
}

$.ajax({
    method: 'POST',
    dataType: 'json',
    contentType: 'application/json; charset=utf-8',
    url: '/home/products',
    data: JSON.stringify({orderby: 'my orderby expression'})
});

在这种情况下,ASP.Net Core将仅使用Newtonsoft.Json作为InputFormatter,并将接收到的json转换为GetProductsModel模型的实例,而无需尝试将新的OrderExpressionBinderProvider用于内部属性。

幸运的是,您还可以通过创建JsonConverter来告诉Newtonsoft.Json如何格式化OrderExpression类型的属性:
public class OrderExpressionJsonConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(OrderExpression);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var stringValue = reader.Value?.ToString();
        OrderExpression expression;
        if (OrderExpression.TryParse(stringValue, out expression))
        {
            return expression;
        }
        return null;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

应该在您的Startup类中注册哪个:
services.AddMvc(opts => {
    opts.ModelBinderProviders.Insert(0, new OrderExpressionBinderProvider());

}).AddJsonOptions(opts => {
    opts.SerializerSettings.Converters.Add(new OrderExpressionJsonConverter());
});

现在,您终于可以处理这两种情况了:)

10-01 07:34