为自定义类型创建自定义模型绑定器

时间:2017-02-19 14:09:33

标签: asp.net-core

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

select      id
           ,concat(',',concat_ws(',',A),',')    as left_side_of_regexp
           ,concat(',(',concat_ws('|',B),'),')  as right_side_of_regexp

           ,concat(',',concat_ws(',',A),',') regexp 
                concat(',(',concat_ws('|',B),'),')  as are_common_elements

from        mytable
;

OrderExpression是一个具有以下方法的类:

+----+---------------------+----------------------+---------------------+
| id | left_side_of_regexp | right_side_of_regexp | are_common_elements |
+----+---------------------+----------------------+---------------------+
|  1 | ,P908,S57,A65,      | ,(P908|S57),         | true                |
|  2 | ,P908,S57,A65,      | ,(P9|S5777),         | false               |
+----+---------------------+----------------------+---------------------+    

该方法从// // Some globals to store what the user selects. // VAR _COUNTY, _STATE, _CITY; // // This will append scripts to the body so that you dont need to load all the // scripts in one go, this is the trick. // function addScript( src,callback) { var s = document.createElement( 'script' ); s.setAttribute( 'src', src ); s.onload=callback; document.body.appendChild( s ); } // // Get all the states based on country // add the correct file to the dom // ie: country/Canada.js // these files would be better scoped geodata.country geodata.state. // // IMPORTANT CHANGE YOUR HTML SELECT HANDLER TO FIRE HERE. // <select name="country" id="country" onchange="getStates();"> // function getStates(){ _COUNRTY = document.getElementById('country'), file = ["country/",_COUNTRY,".js"].join; addScript(file, setStates); }; // // // Called when the script loads // function setStates() { // // Here is where we would do // geodata.states for now I'm using your declaration of state. // var stateList = states; changeSelect('state', stateList, stateList); // Not sure why you are doing this they need to select a state first? setCities(); } // // Called when they select a state, // I would replicate the state code and load all the cities per state. // I would disable the city drop down until the states are loaded. // function setCities() { _STATE = document.getElementById('state'); cityList = cities[_COUNTRY][_STATE]; changeSelect('city', cityList, cityList); } function changeSelect(fieldID, newOptions, newValues) { selectField = document.getElementById(fieldID); selectField.options.length = 0; for (i=0; i<newOptions.length; i++) { selectField.options[selectField.length]=newOption(newOptions[i],newValues[i]); } } function addLoadEvent(func) { var oldonload = window.onload; if (typeof window.onload != 'function') { window.onload = func; } else { window.onload = function() { if (oldonload) { oldonload(); } func(); } } } addLoadEvent(function() { setStates(); }); 创建public class GetProductsModel { public OrderExpression OrderBy { get; set; } } 实例,可以使用:

Boolean TryParse(String value, out OrderExpression expression)

如何为OrderExpression类型的属性创建自定义模型绑定器?

1 个答案:

答案 0 :(得分:6)

我假设您的请求数据中有一个属性orderBy,您想要使用OrderExpression绑定到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;
    }
}

您还需要一个新的模型绑定程序提供程序,它只返回OrderExpression类型的新绑定程序:

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());
});

有了这个,您将能够绑定控制器操作的OrderExpression个参数。类似于以下示例中的内容:

[HttpPost]
public IActionResult Products([FromBody]OrderExpression orderBy) 
{
    return Ok();
}

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

但是,还需要做一些事情才能发送json并将其绑定到内部包含GetProductsModel的{​​{1}}这样的复杂模型。我在谈论这样的场景:

OrderExpression

在这种情况下,ASP.Net Core将使用Newtonsoft.Json作为InputFormatter,并将收到的json转换为[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'}) }); 模型的实例,而不是尝试将新的GetProductsModel用于内部属性。

幸运的是,您还可以通过创建JsonConverter告诉Newtonsoft.Json如何格式化OrderExpressionBinderProvider类型的属性:

OrderExpression

哪些应该在您的Startup类中注册:

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();
    }
}

现在你终于可以处理这两种情况:)