Asp.Net Web API 2第十六课——Parameter Binding in ASP.NET Web API(参数绑定)

简介: 导航 阅读本文之前,您也可以到Asp.Net Web API 2 系列导航进行查看 http://www.cnblogs.com/aehyok/p/3446289.html。 本文主要来讲解以下内容:   〇、前言   Ⅰ、Using[FromUri]   Ⅱ、Using[FromBody...

导航

阅读本文之前,您也可以到Asp.Net Web API 2 系列导航进行查看 http://www.cnblogs.com/aehyok/p/3446289.html

本文主要来讲解以下内容:

  〇、前言

  Ⅰ、Using[FromUri]

  Ⅱ、Using[FromBody]

  Ⅲ、Type Converters

  Ⅳ、Model Binders

  Ⅴ、Value Providers

  Ⅵ、HttpParameterBinding

  Ⅶ、IActionValueBinder

前言

阅读本文之前,您也可以到Asp.Net Web API 2 系列导航进行查看 http://www.cnblogs.com/aehyok/p/3446289.html

当Web API在一个控制器中调用一个方法的时候,它必须为参数设定值,这个过程就叫做绑定。这篇文章描述Web API如何绑定参数,以及如何自定义绑定过程。

  默认情况,Web API使用如下规则来绑定参数:

  1、如果参数一个"简单"类型,那么Web API试图从URI中获取值。简单的类型包含.NET的基元类型(int,bool,double等等)加上TimeSpanDateTimeGuiddecimal, and string,再加上任何的能从字符串进行转换的类型。

  2、对于复杂类型,Web API试图用媒体格式化器http://www.cnblogs.com/aehyok/p/3460164.html从消息体中来读取值。

例如,这是一个典型的Web API控制器方法:

HttpResponseMessage Put(int id, Product item) { ... }

这个“id”参数是一个“简单”类型,因此Web API试图从请求的URI中获取参数值,这个“item”参数是一个复杂类型,因此Web API试图使用一个媒体格式化器从请求消息体中来读取参数值。

为了从URI中获取值,Web API会查看路由数据和URI查询字符串。这个路由数据被填充是在路由系统解析URI并匹配它到路由的时候。对于路由的更多信息: http://www.cnblogs.com/aehyok/p/3444710.html

在这篇文章剩余的部分我将来展示如何自定义模型绑定的过程。对于复杂类型,要尽可能的使用媒体格式化器来处理。HTTP的一个主要原则就是资源被发送在消息体中,使用内容协商http://www.cnblogs.com/aehyok/p/3481265.html来指定资源的展现。媒体格式化器被设计就是为了这个目的。

Using [FromUri]

 为了更好的让Web API从URI中读取复杂类型,添加【FormUri】属性到参数上。下面的例子定义了一个GeoPoint 的类型,紧接着一个控制器方法从URI中获得这个GetPoint参数。

public class GeoPoint
{
    public double Latitude { get; set; } 
    public double Longitude { get; set; }
}

public ValuesController : ApiController
{
    public HttpResponseMessage Get([FromUri] GeoPoint location) { ... }
}

这个客户端可以把Latitude和Longitude的值放进查询字符串中。Web API将用这两个参数来构造一个GeoPoint参数。例如:

http://localhost/api/values/?Latitude=47.678558&Longitude=-122.130989

Using [FromBody]

 为了更好的让Web API 从消息体中读取一个简单类型。添加【FromBody】属性到参数上:

public HttpResponseMessage Post([FromBody] string name) { ... }

在这个例子中,Web API将使用媒体格式化器来读取消息体中的name值。这是一个客户端请求的例子:

POST http://localhost:5076/api/values HTTP/1.1
User-Agent: Fiddler
Host: localhost:5076
Content-Type: application/json
Content-Length: 7

"Alice"

当一个参数拥有【FromBody】属性的时候,Web API使用Content-Type header去选择一个格式化器。在这个例子中Content-Type是“application/json”,这个请求体是一个原始的Json字符串(而不是Json对象)。

至多一个参数被允许从消息体中读取值。因此如下这段将不会起作用:

public HttpResponseMessage Post([FromBody] int id, [FromBody] string name) { ... }

对于这个规则的原因就是这个请求体被存储在只能被读取一次的非缓冲流中。

Type Converters

 你也可以让Web API对待一个class像一个简单的类型,通过创建一个TypeConverter 并提供一个字符串的转换。

接下来的代码展示了用一个GeoPoint类来表示一个地理位置。添加一个 TypeConverter来把字符串转换为GeoPoint实例。这个GeoPoint类用了一个TypeConverter属性来修饰,并且指定了这个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;
        }
    }
    public 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);
        }
    }

现在Web API可以把GeoPoint看做是一个简单类型。意味着它将可以从URI中绑定GeoPoint参数。在参数上你不需要添加【FromUri】属性。

客户端可以调用这个方法,例如如下的URI:

http://localhost/api/values/?location=47.678558,-122.130989

Model Binders

 比一个type converter更灵活的选项是创建一个自定义的模型绑定。有了模型绑定,你可以使用像HTTP请求,Action描述,以及路由数据中的原始值。

为了创建一个Model Binder,你需要实现IModelBinder 接口,这个接口中定义了一个方法,BindModel:

bool BindModel(ModelBindingExecutionContext modelBindingExecutionContext, ModelBindingContext bindingContext);

接下来为GeoPoint对象来创建一个Model Binder。

    public class GeoPointModelBinder:IModelBinder
    {
        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(ModelBindingExecutionContext modelBindingExecutionContext, 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;
        }
    }

一个model binder从一个value provider中获得原始的录入值。这个设计分为两个独立的方法:

1、这个value provider接收到一个HTTP请求,并且填充一个键值对的字典。

2、然后model binder使用键值对的字典来填充model。

Web API中默认的value provider从路由数据和查询字符串中获取值。例如,这样一个URI:

http://localhost/api/values/1?location=48,-122

value provider将会创建如下的键值对:

id = "1"
location = "48,122"

我们假设使用的是默认的路由模版。

被绑定的参数的名称被存储在ModelBindingContext.ModelName这个属性上。model binder在字典中寻找一个键的值。如果这个值存在,并且也能被转换成GeoPoint,这个model binder将分配这个值到ModelBindingContext.Model属性。

注意:Model Binder不会限制一个简单类型的转换,这个model binder首先会在已知位置的列表中寻找,如果查找失败,将会使用 type converter。

Setting the Model Binder

这里有几种方法去设置Model Binder.首先你可以添加一个【Model Binder】属性到参数上。

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

你也能添加【Model Binder】属性到这个参数类型上。Web API将指定这个model binder到这个类型的所有参数上。

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

最后,你能添加一个model-binder的提供者到HttpConfiguration。一个model-binder的提供者就是一个简单的工厂类,它可以创建一个model binder。你能创建一个provider通过派生自 ModelBinderProvider类。无论怎样,如果你的model binder处理单个类型,它是比较容易的通过使用已经创建的SimpleModelBinderProvider

 

接下来的代码展示如何启用他们:

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

        // ...
    }
}

有了一个model-binding provider,你仍然需要添加一个[ModelBinder] 属性到参数上,它的目的就是告知Web API应该是用model binder,而不是使用媒体格式化器。但是现在你不需要在属性上指定这个model binder的类型。

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

Value Providers

 我前面提到过一个model binder是从value provider中获取值。写一个自定义的value provider,实现这个IValueProvider 接口。这个例子是从请求的cookie中获取值。

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

你也需要创建一个value provider 工厂通过继承自ValueProviderFactory 。

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

添加value provider 工厂到HttpConfiguration ,代码如下:

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

    // ...
}

 Web API组合了所有的value provider,因此当一个model binder调用ValueProvider.GetValue,这个model binder接收从第一个value provider能提供它的值。

或者,通过使用ValueProvider属性你也能在参数级别上设置value provider 工厂,代码如下:

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

这将告诉Web API模型绑定使用指定的value  provider 工厂,不要用任何另外的被注册的value  provider。

HttpParameterBinding

 模型绑定是一个更加普遍机制的特性实例。如果你看到这个 [ModelBinder] 属性,你将明白它是派生自ParameterBindingAttribute 抽象类。这个类定义了一个单独的方法,并返回一个HttpParameterBinding 对象:

public abstract class ParameterBindingAttribute : Attribute
{
    public abstract HttpParameterBinding GetBinding(HttpParameterDescriptor parameter);
}

HttpParameterBinding 对象负责绑定一个值到一个参数。在[ModelBinder]修饰的情况下,这个属性返回一个HttpParameterBinding 的实现,它使用了一个IModelBinder 去展现真实的binding。你也可以实现自己的HttpParameterBinding

 例如,假定你想从请求的if-match 和 if-none-match 的header中获取ETags。开始我们将定义一个class来代替ETags 。

public class ETag
{
    public string Tag { get; set; }
}

我们也来定义一个枚举指明是否从if-match 和 if-none-match 的header中获得了ETag 。

public enum ETagMatch
{
    IfMatch,
    IfNoneMatch
}

这里是HttpParameterBinding ,获取该 ETag 从所需的标头并将其绑定到类型的参数的 ETag:

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

ExecuteBindingAsync 方法来处理绑定。在此方法中,添加参数值到ActionArgument 字典中并在HttpActionContext中。

如果你的ExecuteBindingAsync 方法读取请求消息体。重写这个WillReadBody 属性去返回true。这个消息体可能是只能读一次的未缓冲的流。因此Web API施行了一个规则至多有一个绑定可以读取消息体。

应用一个自定义的HttpParameterBinding,你能定义一个派生自ParameterBindingAttribute 的属性,对于ETagParameterBinding,我们将定义两个属性:一个是对于if-match  Header的,一个是对于if-none-match Header。都派生自一个抽象的基类。

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)
    {
    }
}

这是一个控制器方法 使用了[IfNoneMatch] 属性。

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

除了ParameterBindingAttribute 之外,对于添加一个自定义的HttpParameterBinding 有另外一个挂钩。在HttpConfiguration 对象上,ParameterBindingRules 是一个匿名方法类型(HttpParameterDescriptor -> HttpParameterBinding)的集合。例如,你可以添加一个规则:在Get请求方法中任何ETag 参数使用ETagParameterBinding with if-none-match。

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

这个方法对于绑定不适用的参数应该返回null。

IActionValueBinder

 整个参数绑定的过程被一个可插拔的服务控制,IActionValueBinderIActionValueBinder 的默认实现将执行以下操作:

  1、在参数上查看ParameterBindingAttribute ,这包括 [FromBody][FromUri], 和[ModelBinder], 或者是自定义的属性。

  2、否则,查看一个函数的HttpConfiguration.ParameterBindingRules ,它返回一个非null的HttpParameterBinding

  3、否则,使用我之前描述的默认规则。

    ①、如果参数类型是一个“简单”的,或者拥有一个type converter,将会从URI进行绑定。它等价于在参数上添加[FromUri]属性。

    ②、否则,试图从消息体中读取参数,这等价于在参数上添加[FromBody]属性。

 如果你需要,你可以用一个自定义的实现来替代整个IActionValueBinder 。

总结

本文主要来讲解参数绑定,但是通过上面也可以看出涉及到的知识点还是蛮多的,但是都是很实用的,例子也比较清晰。但是还是需要在项目中进行应用,才能更好的学习和掌握参数绑定的环节。

本文的参考链接为http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api 

目录
相关文章
|
19天前
|
开发框架 前端开发 JavaScript
ASP.NET Web Pages - 教程
ASP.NET Web Pages 是一种用于创建动态网页的开发模式,采用HTML、CSS、JavaScript 和服务器脚本。本教程聚焦于Web Pages,介绍如何使用Razor语法结合服务器端代码与前端技术,以及利用WebMatrix工具进行开发。适合初学者入门ASP.NET。
|
2月前
|
XML JSON API
ServiceStack:不仅仅是一个高性能Web API和微服务框架,更是一站式解决方案——深入解析其多协议支持及简便开发流程,带您体验前所未有的.NET开发效率革命
【10月更文挑战第9天】ServiceStack 是一个高性能的 Web API 和微服务框架,支持 JSON、XML、CSV 等多种数据格式。它简化了 .NET 应用的开发流程,提供了直观的 RESTful 服务构建方式。ServiceStack 支持高并发请求和复杂业务逻辑,安装简单,通过 NuGet 包管理器即可快速集成。示例代码展示了如何创建一个返回当前日期的简单服务,包括定义请求和响应 DTO、实现服务逻辑、配置路由和宿主。ServiceStack 还支持 WebSocket、SignalR 等实时通信协议,具备自动验证、自动过滤器等丰富功能,适合快速搭建高性能、可扩展的服务端应用。
159 3
|
19天前
|
开发框架 .NET PHP
ASP.NET Web Pages - 添加 Razor 代码
ASP.NET Web Pages 使用 Razor 标记添加服务器端代码,支持 C# 和 Visual Basic。Razor 语法简洁易学,类似于 ASP 和 PHP。例如,在网页中加入 `@DateTime.Now` 可以实时显示当前时间。
|
3月前
|
开发框架 监控 前端开发
在 ASP.NET Core Web API 中使用操作筛选器统一处理通用操作
【9月更文挑战第27天】操作筛选器是ASP.NET Core MVC和Web API中的一种过滤器,可在操作方法执行前后运行代码,适用于日志记录、性能监控和验证等场景。通过实现`IActionFilter`接口的`OnActionExecuting`和`OnActionExecuted`方法,可以统一处理日志、验证及异常。创建并注册自定义筛选器类,能提升代码的可维护性和复用性。
|
1月前
|
设计模式 前端开发 数据库
Python Web开发:Django框架下的全栈开发实战
【10月更文挑战第27天】本文介绍了Django框架在Python Web开发中的应用,涵盖了Django与Flask等框架的比较、项目结构、模型、视图、模板和URL配置等内容,并展示了实际代码示例,帮助读者快速掌握Django全栈开发的核心技术。
171 45
|
16天前
|
前端开发 安全 JavaScript
2025年,Web3开发学习路线全指南
本文提供了一条针对Dapp应用开发的学习路线,涵盖了Web3领域的重要技术栈,如区块链基础、以太坊技术、Solidity编程、智能合约开发及安全、web3.js和ethers.js库的使用、Truffle框架等。文章首先分析了国内区块链企业的技术需求,随后详细介绍了每个技术点的学习资源和方法,旨在帮助初学者系统地掌握Dapp开发所需的知识和技能。
2025年,Web3开发学习路线全指南
|
22天前
|
存储 前端开发 JavaScript
如何在项目中高效地进行 Web 组件化开发
高效地进行 Web 组件化开发需要从多个方面入手,通过明确目标、合理规划、规范开发、加强测试等一系列措施,实现组件的高效管理和利用,从而提高项目的整体开发效率和质量,为用户提供更好的体验。
27 7
|
27天前
|
开发框架 搜索推荐 数据可视化
Django框架适合开发哪种类型的Web应用程序?
Django 框架凭借其强大的功能、稳定性和可扩展性,几乎可以适应各种类型的 Web 应用程序开发需求。无论是简单的网站还是复杂的企业级系统,Django 都能提供可靠的支持,帮助开发者快速构建高质量的应用。同时,其活跃的社区和丰富的资源也为开发者在项目实施过程中提供了有力的保障。
|
26天前
|
开发框架 JavaScript 前端开发
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势。通过明确的类型定义,TypeScript 能够在编码阶段发现潜在错误,提高代码质量;支持组件的清晰定义与复用,增强代码的可维护性;与 React、Vue 等框架结合,提供更佳的开发体验;适用于大型项目,优化代码结构和性能。随着 Web 技术的发展,TypeScript 的应用前景广阔,将继续引领 Web 开发的新趋势。
35 2
|
1月前
|
前端开发 API 开发者
Python Web开发者必看!AJAX、Fetch API实战技巧,让前后端交互如丝般顺滑!
在Web开发中,前后端的高效交互是提升用户体验的关键。本文通过一个基于Flask框架的博客系统实战案例,详细介绍了如何使用AJAX和Fetch API实现不刷新页面查看评论的功能。从后端路由设置到前端请求处理,全面展示了这两种技术的应用技巧,帮助Python Web开发者提升项目质量和开发效率。
52 1
下一篇
DataWorks