ASP.NET Web API編程——模型驗證與綁定

 1.模型驗證git

使用特性約束模型屬性web

可使用System.ComponentModel.DataAnnotations提供的特性來限制模型。json

例如,Required特性表示字段值不能爲空,Range特性限制數值類型的範圍。api

對實體類使用特性後,可使用ModelState.IsValid來判斷驗證是否經過。cookie

例:app

實體:框架

public class DataModel
{
        public int Id { get; set; }

        public string Field1Name {get;set;}
        [Required]
        public string Field2Name { get; set; }

}

 

控制器操做:ide

        [HttpPost]
        public IHttpActionResult ModelValid(DataModel model)
        {
            if (!ModelState.IsValid)
            {
                throw new HttpResponseException(HttpStatusCode.BadRequest);
            }
            return Ok(model);
        }

 

客戶端調用:網站

            HttpClient client = new HttpClient();
            string url = "http://localhost/WebApi_Test/api/account/modelvalid";
            using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url))
            {
                var cont = new { Id = 1, Field1Name = "1name" };
                HttpContent content = new StringContent(JsonConvert.SerializeObject(cont));
                content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
                request.Content = content;
                HttpResponseMessage response = client.SendAsync(request).Result;
                Console.WriteLine("狀態碼:{0}",(int)response.StatusCode);
                var task = response.Content.ReadAsStringAsync();
                task.Wait();
                Console.WriteLine("結果:{0}", task.Result);
            }

 

輸出結果:ui

 

服務端運行截圖:

 

若客戶端傳值爲:var cont = new { Id = 1, Field1Name = "1name", Field2Name="2name" };

 

默認賦值

Web API會對客戶端未指定的模型屬性賦初值。對於int,double等數值類型默認的初值爲0,對於字符串或引用類型默認的初值是null。若是未對屬性使用特性加以約束,那麼ModelState.IsValid的值就是true,若對這樣的屬性應用Required特性,那麼當客戶端爲對其賦初值時,驗證將沒法經過,即ModelState.IsValid的值爲false。

例:

上例中不對Id屬性賦值,運行客戶端結果爲:

可見框架自動爲int型的Id賦初值0

 

過載

此外當客戶端所用實體屬性於服務端時,服務端會忽略多出來的屬性,但建議控制器操做(Action)所用參數列表的參數或類屬性與客戶端所傳參數徹底匹配。

例:

若使用上述客戶端,但傳值爲

var cont = new { Field1Name = "1name", Field2Name = "2name",FieldOverLoad ="overload"};

其中DataModel不包含FieldOverLoad 字段。

運行結果以下:

 

過濾驗證結果

能夠自定義操做過濾器來統一處理模型驗證失敗的情形。自定義操做過濾器派生自ActionFilterAttribute,咱們須要重寫OnActionExecuting方法,以便在操做(Action)調用以前處理。

例:

using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using System.Web.Http.ModelBinding;

namespace MyApi.Filters
{
    public class ValidateModelAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            if (actionContext.ModelState.IsValid == false)
            {
                actionContext.Response = actionContext.Request.CreateErrorResponse(
                    HttpStatusCode.BadRequest, actionContext.ModelState);
            }
        }
    }
}

 

WebApiConfig的Register方法中將上述自定義過濾器添加進來,這樣過濾器對每個操做(Action)都起做用,若是不想使其對每個操做都起做用,而是想應用於個別操做(Action),能夠將此特性應用到目標操做(Action)

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Filters.Add(new ValidateModelAttribute());

        // ...
    }
}

public class ProductsController : ApiController
{
    [ValidateModel]
    public HttpResponseMessage Post(Product product)
    {
        // ...
    }
}

 

2模型綁定

默認的綁定規則

1)若是操做(Action)參數是簡單類型,Web API框架會從URI中獲取值。簡單類型是指:.NET 框架定義的原始類型(int, bool, double等)、TimeSpan、DateTime、Guid、decimal、string;另外還有包含類型轉換器的類型,改轉換器可將字符串轉換爲此類型。這裏從URI獲取值具體指:從路由詞典中獲取值或者從URI的查詢字符串中獲取值。具體過程見介紹路由那篇博文。

2)對於複雜類型,Web API會使用多媒體格式化器從消息體中得到值。

 

類型轉換

默認的模型綁定規則中說起了包含類型轉換器的類型也是簡單類型。類型轉換器可使類也被看作簡單類型。這樣按照默認的規則就能夠從URI中獲取值來構建參數列表了。

例:使用TypeConverter特性指明所使用的類型轉換器。

[TypeConverter(typeof(GeoPointConverter))]
public class GeoPoint
{
    public double Latitude { get; set; } 
    public double Longitude { get; set; }

    public static bool TryParse(string s, out GeoPoint result)
    {
        result = null;

        var parts = s.Split(',');
        if (parts.Length != 2)
        {
            return false;
        }

        double latitude, longitude;
        if (double.TryParse(parts[0], out latitude) &&
            double.TryParse(parts[1], out longitude))
        {
            result = new GeoPoint() { Longitude = longitude, Latitude = latitude };
            return true;
        }
        return false;
    }
}

class GeoPointConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        if (sourceType == typeof(string))
        {
            return true;
        }
        return base.CanConvertFrom(context, sourceType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, 
        CultureInfo culture, object value)
    {
        if (value is string)
        {
            GeoPoint point;
            if (GeoPoint.TryParse((string)value, out point))
            {
                return point;
            }
        }
        return base.ConvertFrom(context, culture, value);
    }
}

 

使用[FromUri]

爲了強制Web API從URI中取值,可使用FromUri特性。這樣即便操做(Action)參數是複雜類型,框架也會中URI中取值來爲參數賦值。

 

使用[FromBody]

爲了強制Web API從消息體中取值,可使用FromBody特性。這樣即便操做(Action)參數是簡單類型,框架也會從消息體中取值來爲參數賦值。當使用FromBody特性時,Web API使用請求的Content-Type標頭來選擇格式化器。

注意:對多個參數使用FromBody不起做用。

例:

服務端操做爲:

        [HttpPost]
        public IHttpActionResult ModelValid([FromBody]DataModel model)
        {
            if (!ModelState.IsValid)
            {
                throw new HttpResponseException(HttpStatusCode.BadRequest);
            }
            return Ok(model);
        }

 

客戶端調用爲:

            HttpClient client = new HttpClient();
            string url = "http://localhost/WebApi_Test/api/account/modelvalid";
            using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url))
            {
                //var cont = new { Id = 1, Field1Name = "111" };
                var cont = new { Field1Name = "1name", Field2Name = "2name"};
                HttpContent content = new StringContent(JsonConvert.SerializeObject(cont));
                content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
                request.Content = content;
                HttpResponseMessage response = client.SendAsync(request).Result;
                Console.WriteLine("狀態碼:{0}",(int)response.StatusCode);
                var task = response.Content.ReadAsStringAsync();
                task.Wait();
                Console.WriteLine("結果:{0}", task.Result);
            }

運行客戶端能夠正常得到結果,若使用FromUri,沒法經過模型綁定驗證,也沒法得到結果。

 

改變客戶端傳值的方式:

            HttpClient client = new HttpClient();
            string url = "http://localhost/WebApi_Test/api/account/modelvalid?Field1Name=1name&Field2Name=2name";
            using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url))
            {
                HttpResponseMessage response = client.SendAsync(request).Result;
                Console.WriteLine("狀態碼:{0}",(int)response.StatusCode);
                var task = response.Content.ReadAsStringAsync();
                task.Wait();
                Console.WriteLine("結果:{0}", task.Result);
            }

 

運行結果爲:

 

自定義模型綁定器

模型綁定器從值提供器(value provider)中得到原始輸入,這種設計拆分出兩個不一樣的功能:

1)值提供器使用HTTP請求而且填充一個詞典。

2)模型綁定器使用這個詞典填充模型。

默認的值提供器從請求URI的查詢字符串和路由詞典中獲取值。要綁定的參數的名稱保存在ModelBindingContext.ModelName屬性中,模型綁定器在詞典中找相應的鍵值對。若是鍵值對存在,而且可以轉換爲待處理模型,模型綁定器分配綁定值給ModelBindingContext.Model屬性。模型綁定器不會限制簡單類型的轉換。自定義模型綁定器須要實現IModelBinder接口。

例:

public class GeoPointModelBinder : IModelBinder
{
    // List of known locations.
    private static ConcurrentDictionary<string, GeoPoint> _locations
        = new ConcurrentDictionary<string, GeoPoint>(StringComparer.OrdinalIgnoreCase);

    static GeoPointModelBinder()
    {
        _locations["redmond"] = new GeoPoint() { Latitude = 47.67856, Longitude = -122.131 };
        _locations["paris"] = new GeoPoint() { Latitude = 48.856930, Longitude = 2.3412 };
        _locations["tokyo"] = new GeoPoint() { Latitude = 35.683208, Longitude = 139.80894 };
    }

    public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
    {
        if (bindingContext.ModelType != typeof(GeoPoint))
        {
            return false;
        }

        ValueProviderResult val = bindingContext.ValueProvider.GetValue(
            bindingContext.ModelName);
        if (val == null)
        {
            return false;
        }

        string key = val.RawValue as string;
        if (key == null)
        {
            bindingContext.ModelState.AddModelError(
                bindingContext.ModelName, "Wrong value type");
            return false;
        }

        GeoPoint result;
        if (_locations.TryGetValue(key, out result) || GeoPoint.TryParse(key, out result))
        {
            bindingContext.Model = result;
            return true;
        }

        bindingContext.ModelState.AddModelError(
            bindingContext.ModelName, "Cannot convert value to Location");
        return false;
    }
}

 

使用上述自定義的模型綁定器的方式有多種。

方式1、對於一個操做(Action)。

例:

public HttpResponseMessage Get([ModelBinder(typeof(GeoPointModelBinder))] GeoPoint location)

 

方式2、對於一個控制器。

例:

[ModelBinder(typeof(GeoPointModelBinder))]
public class GeoPoint
{
    // ....
}

 

方式3、註冊模型綁定器後,依然要使用在操做上使用特性,不過不用指定類型

例:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        var provider = new SimpleModelBinderProvider(
            typeof(GeoPoint), new GeoPointModelBinder());
        config.Services.Insert(typeof(ModelBinderProvider), 0, provider);

        // ...
    }
}
public HttpResponseMessage Get([ModelBinder] GeoPoint location) { ... }

 

自定義值提供器

模型綁定器從值提供器中獲取值,自定義值提供器須要實現IValueProvider接口。

例:

public class CookieValueProvider : IValueProvider
{
    private Dictionary<string, string> _values;

    public CookieValueProvider(HttpActionContext actionContext)
    {
        if (actionContext == null)
        {
            throw new ArgumentNullException("actionContext");
        }

        _values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        foreach (var cookie in actionContext.Request.Headers.GetCookies())
        {
            foreach (CookieState state in cookie.Cookies)
            {
                _values[state.Name] = state.Value;
            }
        }
    }

    public bool ContainsPrefix(string prefix)
    {
        return _values.Keys.Contains(prefix);
    }

    public ValueProviderResult GetValue(string key)
    {
        string value;
        if (_values.TryGetValue(key, out value))
        {
            return new ValueProviderResult(value, value, CultureInfo.InvariantCulture);
        }
        return null;
    }
}

 

建立值提供器工廠,其派生自ValueProviderFactory

public class CookieValueProviderFactory : ValueProviderFactory
{
    public override IValueProvider GetValueProvider(HttpActionContext actionContext)
    {
        return new CookieValueProvider(actionContext);
    }
}

 

註冊值提供器工廠。

public static void Register(HttpConfiguration config)
{
    config.Services.Add(typeof(ValueProviderFactory), new CookieValueProviderFactory());

    // ...
}

 

使用值提供器工廠,指定使用CookieValueProvider。

public HttpResponseMessage Get(
[ValueProvider(typeof(CookieValueProviderFactory))] GeoPoint location)

 

自定義HttpParameterBinding

ModelBinderAttribute繼承自ParameterBindingAttributeParameterBindingAttribute繼承自AttributeParameterBindingAttribute只有一個方法GetBinding,改=該方法返回HttpParameterBindingHttpParameterBinding表明了參數與值之間的綁定關係。

public class ModelBinderAttribute : ParameterBindingAttribute
{......}
public abstract class ParameterBindingAttribute : Attribute
{
        protected ParameterBindingAttribute();

        // 得到參數綁定
        // parameter:參數描述
        public abstract HttpParameterBinding GetBinding(HttpParameterDescriptor parameter);
}

 

例:利用請求頭中的if-matchif-none-match得到ETags

public class ETag
{
    public string Tag { get; set; }
}
public enum ETagMatch
{
    IfMatch,
    IfNoneMatch
}
public class ETagParameterBinding : HttpParameterBinding
{
    ETagMatch _match;

    public ETagParameterBinding(HttpParameterDescriptor parameter, ETagMatch match) 
        : base(parameter)
    {
        _match = match;
    }

    public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, 
        HttpActionContext actionContext, CancellationToken cancellationToken)
    {
        EntityTagHeaderValue etagHeader = null;
        switch (_match)
        {
            case ETagMatch.IfNoneMatch:
                etagHeader = actionContext.Request.Headers.IfNoneMatch.FirstOrDefault();
                break;

            case ETagMatch.IfMatch:
                etagHeader = actionContext.Request.Headers.IfMatch.FirstOrDefault();
                break;
        }

        ETag etag = null;
        if (etagHeader != null)
        {
            etag = new ETag { Tag = etagHeader.Tag };
        }
        actionContext.ActionArguments[Descriptor.ParameterName] = etag;

        var tsc = new TaskCompletionSource<object>();
        tsc.SetResult(null);
        return tsc.Task;
    }
}

 

爲使用自定義的HttpParameterBinding,定義一個派生自ParameterBindingAttribute的類。

public abstract class ETagMatchAttribute : ParameterBindingAttribute
{
    private ETagMatch _match;

    public ETagMatchAttribute(ETagMatch match)
    {
        _match = match;
    }

    public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter)
    {
        if (parameter.ParameterType == typeof(ETag))
        {
            return new ETagParameterBinding(parameter, _match);
        }
        return parameter.BindAsError("Wrong parameter type");
    }
}

public class IfMatchAttribute : ETagMatchAttribute
{
    public IfMatchAttribute()
        : base(ETagMatch.IfMatch)
    {
    }
}

public class IfNoneMatchAttribute : ETagMatchAttribute
{
    public IfNoneMatchAttribute()
        : base(ETagMatch.IfNoneMatch)
    {
    }
}

 

在控制器操做(Action)中使用它。

public HttpResponseMessage Get([IfNoneMatch] ETag etag) { ... }

 

另一種使用自定義的HttpParameterBinding的方式是利用HttpConfiguration.ParameterBindingRules這個屬性。

例:

config.ParameterBindingRules.Add(p =>
{
    if (p.ParameterType == typeof(ETag) && 
        p.ActionDescriptor.SupportedHttpMethods.Contains(HttpMethod.Get))
    {
        return new ETagParameterBinding(p, ETagMatch.IfNoneMatch);
    }
    else
    {
        return null;
    }
});

 

可插拔服務IActionValueBinder

整個模型綁定過程是由IActionValueBinder服務控制器的。其默認實現完成如下工做:

1)在參數中查找ParameterBindingAttribute,包括[FromBody], [FromUri], and [ModelBinder], 或者自定義特性。

2)若是步奏1)中沒有找到,那麼在HttpConfiguration.ParameterBindingRules中尋找一個返回值爲HttpParameterBinding的方法。

3)若是沒有找到就使用默認規則。

若是操做(Action)參數是簡單類型,Web API框架會從URI中獲取值。簡單類型是指:.NET 框架定義的原始類型(int, bool, double等)、TimeSpan、DateTime、Guid、decimal、string;另外還有包含類型轉換器的類型,改轉換器可將字符串轉換爲此類型。這裏從URI獲取值具體指:從路由詞典中獲取值或者從URI的查詢字符串中獲取值。具體過程見介紹路由那篇博文。對於複雜類型,Web API會使用多媒體格式化器從消息體中得到值。

 

參考:

https://docs.microsoft.com/en-us/aspnet/web-api/

部分示例自於該網站

 

轉載與引用請註明出處。

時間倉促,水平有限,若有不當之處,歡迎指正。
相關文章
相關標籤/搜索