天天看點

編寫自定義的模型綁定器

接手了一個重構舊項目的任務,後端是PHP的,前端用的幾個零散的東西。重構的原則是 前端基本不動,後端改造成 dotnetcore 。

過程基本順利,遇到的一個問題的解決方式覺得值得說一下。問題是這樣:一個頁面的某一個接收參數,有從A頁面來的,也有B頁面來的,但是A和B頁面送出過來的格式是不一樣的,A頁面是正常的字元,B頁面過來的是Unicode 編碼,%u5ba2%u6237 的這種。

接收的地方大概是這樣:

public IActionResult Search(string cust_id, string project_id)
{}
           

也就是這裡的 cust_id 和 project_id 既可能是正常字元,也可能是 Unicode 編碼。

編碼的問題很好解決,在網上找了一段正則替換的:

public static string Unicode2String(this string source)
{
    return new Regex(@"%u([0-9A-F]{4})", RegexOptions.IgnoreCase | RegexOptions.Compiled).Replace(
                         source, x => string.Empty + Convert.ToChar(Convert.ToUInt16(x.Result("$1"), 16)));
}
           

用的時候調一下就可以了。

public IActionResult Search(string cust_id, string project_id)
{
    cust_id = cust_id.Unicode2String();
    project_id = project_id.Unicode2String();
    //....
}
           

但是後來發現,這樣的地方很多,如果每個Action 都加這個東西,有點 Low 。自然就想到了用模型綁定器解決這個問題,就像

我們總用的 FromBody FromQuery 等等。記得以前用過 ModelBinder ,翻了翻以前的代碼,寫了一個類:

public class UnicodeModelBinder : IModelBinder
    {
        public Task BindModelAsync(ModelBindingContext bindingContext)
        {
            if (bindingContext.ModelType != typeof(string))
                return Task.CompletedTask;
            var mn = bindingContext.FieldName;
            string result;
            if (bindingContext.ActionContext.HttpContext.Request.Query.ContainsKey(mn))
                result = bindingContext.ActionContext.HttpContext.Request.Query[mn];
            else
                result = null;
            if(result != null)
            {
                result = result.Unicode2String();
                bindingContext.Result = ModelBindingResult.Success(result);
            }
            return Task.CompletedTask;
        }
    }
           

功能很簡單,就是判斷是不是 string ,如果是 判斷 Request.Query 是有沒有,如果有,就用上面的方法轉一下。

然後在 Action 上這樣改:

public IActionResult Search([ModelBinder(BinderType = typeof(UnicodeModelBinder))]string cust_id,[ModelBinder(BinderType = typeof(UnicodeModelBinder))] string project_id)
{
    //删掉這句 cust_id = cust_id.Unicode2String();
    //删掉這句 project_id = project_id.Unicode2String();
    //....
}
           

問題解決了,但是這樣有點醜,加的這個東西太長了。然後就想能不能直接定義一個 ModelBinder 這樣的東西。

先來看看 ModelBinder 怎麼寫的。

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
    public class ModelBinderAttribute : Attribute, IBinderTypeProviderMetadata, IBindingSourceMetadata, IModelNameProvider
    {
        public ModelBinderAttribute();
        public ModelBinderAttribute(Type binderType);
        public Type BinderType { get; set; }
        public virtual BindingSource BindingSource { get; protected set; }
        public string Name { get; set; }
    }
           

那麼寫一個子類行不行?試試吧。

寫了一個類 UnicodeAttribute 繼承 ModelBinder,然後指定 BinderType = typeof(UnicodeModelBinder)。

結果發現不行,因為 BinderType 不是 virtual 的,沒法 override 。

既然繼承不行,那試試代理模式吧。寫個代理模式的類,把 ModelBinder 代理一下,代碼如下:

public class UnicodeAttribute : Attribute, IBinderTypeProviderMetadata, IBindingSourceMetadata, IModelNameProvider
    {
        private readonly ModelBinderAttribute modelBinderAttribute = new ModelBinderAttribute() { BinderType = typeof(UnicodeModelBinder) };

        public BindingSource BindingSource => modelBinderAttribute.BindingSource;

        public string Name => modelBinderAttribute.Name;

        public Type BinderType => modelBinderAttribute.BinderType;
    }
           

然後Action 改成這樣:

public IActionResult Search([Unicode]string cust_id,[Unicode] string project_id)
{
    //....
}
           

跑起來看一下,完美解決。

繼續閱讀