绑定和匹配的概念和定义一样吗一样吗

966,690 四月 独立访问用户
语言 & 开发
架构 & 设计
文化 & 方法
您目前处于:
使用绑定实现灵活通信
使用绑定实现灵活通信
日. 估计阅读时间:
道AI风控、Serverless架构、EB级存储引擎,尽在!
相关厂商内容
相关赞助商
GMTC北京-10日,北京&国际会议中心,
在这篇文章中,我们将了解如何在服务以及引用上使用绑定;此外,如果没有配置绑定话,意味着什么;最后,我们还要去看看SCA的域,去了解如何在SCA域内和域外运用绑定。
为服务和引用配置绑定
你可以在服务以及引用上配置绑定。为服务设定一个绑定意味着人们可以通过由该绑定所指定的交互协议来访问这个服务。而服务的实现不需要做任何特别的改变来促成这个目标,你要做的仅仅是为服务增加一条绑定配置。
例如,如果要让Bookings服务以Web服务的方式暴露出来,那么组件的定义看起来就应该是这样的:
&component name=&TripBooking&&
&implementation.java class=&com.tuscanyscatours.TripBooking& /&
&service name=&Bookings&&
&binding.ws uri=&:8085/Bookings& /&
&/service&
&/component&
我们通过增加&binding.ws&元素告诉SCA运行时,Bookings服务要以Web服务的形式暴露出来,使用SOAP/HTTP的 方式,并且以uri属性中指定的值作为服务的端点(endpoint)。这就是要创建Web服务我们要做的全部事情,无需学习JAX-WS或者在 Bookings服务的实现上做文章。
可以为一个服务配置多个绑定。如果既要通过JMS访问又要通过Web服务方式来访问Bookings服务,则只需要增加另一条绑定。请看下面的例子:
&component name=&TripBooking&&
&implementation.java class=&com.tuscanyscatours.TripBooking& /&
&service name=&Bookings&&
&binding.ws uri=&:8085/Bookings& /&
&binding.jms /&
&/service&
&/component&
这个定义让Bookings服务同时以JMS的方式暴露出来,并且使用了缺省配置。同样,我们不需要去学习JMS的API或修改服务的实现代码。
绑定还将SCA和Tuscany与更广阔的外部世界联系起来!Bookings服务是以SCA实现,运行在Tuscany中的。由于使用绑定对这个服务进行了配置,它可以被以非SCA方式实现的客户端或运行在Tuscany中的客户端调用。对于Bookings的Web服务调用,客户端可以是任何语言编写的,运行在任何遵循WS-I的Web服务运行时上的程序;同样,对于JMS方式的调用,客户端可以直接使用任何JMS提供者所提供的JMS API,只要该JMS提供者与Tuscany中配置的JMS绑定对应的JMS提供者兼容即可。
现在我们已经看到如何通过在SCA的服务上配置绑定让服务以标准的交互协议向外提供服务。同样,绑定也可以用于SCA引用上,让其通过标准的交互协议去调 用外部服务。这种情况下角色正好倒置:客户端由SCA实现,而服务端使用标准的交互协议。例如,服务提供者可能是一个支持SOAP/HTTP的Web服务 端点,也有可能是一个以RMI-IIOP方式交互的EJB(session bean)。与Web服务交互时,SCA引用使用&binding.ws&绑定;而调用EJB时,它使 用&binding.ejb&绑定。
图1显示了如何在组件的服务以及引用上配置绑定。
图1:Bookings服务配置了Web服务和JMS的绑定,&cars&引用配置了Web服务绑定,&flights&引用配置的是EJB绑定。
列表1中的示例代码显示的是图1中描绘的TripBooking组件对应的组件定义文件。
列表1 为组件的服务和引用配置绑定和连线
&composite xmlns=&http://www.osoa.org/xmlns/sca/1.0&
targetNamespace=&/&
name=&bookings&&
&component name=&TripBooking&&
&implementation.java class=&com.tuscanyscatours.TripBooking& /&
&service name=&Bookings&&
&binding.ws uri=&:8085/Bookings& /&
&binding.jms /&
&/service&
&reference name=&cars&&
&binding.ws uri=&:8081/Cars& /&
&/reference&
&reference name=&flights&&
&binding.ejb uri=&corbaname:rir:#flight/FlightProviderHome& /&
&/reference&
&reference name=&hotels& target=&HotelProvider& /&
&/component&
&/composite&
你可能已经发现列表1中有两个引用(cars和flights)配置了绑定而没有target属性,这是因为该绑定元素已经提供了引用的目标端点信息,同时还指定了所使用的交互协议。
至此,我们已经了解如何在服务和引用上使用绑定。在下一节中,我们要看看如果在服务和引用的配置中不指定绑定将会发生什么?
在列表1中我们为&hotels&引用没有设置绑定,这意味着它有一个隐含的.sca绑定(经常被称为缺省绑定)。缺省绑定用于连接SCA服务和SCA引用,把交互技术的选择工作交给部署服务和引用的SCA运行时,而其他的绑定(如WS绑定和JMS绑定)则要选择具体的交互协议或API,从而让SCA服务或引用可以与非SCA的程序进行交互。正因为如此,非缺省的绑定通常被称为可互操作的绑定。
Tuscany使用基于SOAP/HTTP的Web服务实现缺省绑定的远程调用;其他的SCA运行时可能使用不同的标准协议,如RMI/IIOP,它们也 可以使用某种私有协议。将来,Tuscany的缺省绑定的通讯协议也可以从Web服务转向其他的协议,因此,Tuscany的应用程序不应该想当然地假设 使用缺省绑定就意味着使用Web服务。应用程序若要使用Web服务在组件之间交互的话,为保险起见,还是应该要指定使 用&binding.ws&绑定。
缺省绑定只能用于连接位于同一个SCA域中的服务和引用,而当连接跨越域边界时,应该要为其指定某个可互操作的绑定。域在SCA中是一个重要的概念,我们将在第四章详细介绍域的概念,不过在下一个节中我们先简单介绍一下什么是域以及它与绑定和连线的关系。
域,绑定和连线(wire)
SCA域是一个SCA组件的部署和管理边界,比如,一个域可能是单个应用服务器或一个服务器集群,也可以是一组服务器或一组集群。而一个完整的域通常只运行某一家提供商的SCA实现。
任何SCA组件都是SCA域的一部分;同一域中的服务和引用可以通过连线进行连接;对于同一域中的服务和引用之间的连接,可以使用缺省绑定,因为SCA保证了缺省绑定的实现在域中的一致性。相反,对于域内到域外的交互,不能使用连线进行连接,而应该使用可互操作的绑定。
在第2章我们已经介绍了TuscanySCATours公司,下面我们将通过该公司的一个场景来描述域的使用。现在,这个公司已经壮大,并且设立了一个独 立的部门提供专门的hotel booking(酒店预订)服务。这个服务不仅要服务于TuscanySCATours的trip booking(行程预订)服务,还要直接服务于只需预订酒店的客户。公司的两个部门使用不同的域管理他们所提供的服务:TuscanySCATours 域提供原先的trip booking(行程预订)服务,而TuscanySCAHotels域提供新的hotel booking(酒店预订)服务,如图二所示:
图2中有两个SCA域及多个组件,展示了组件是如何连接的,连线(实线)用于连接同一域中的组件,而 绑定(带箭头的虚线)用于连接跨域的组件。
图2中用实线表示连接组件的SCA连线,而用带箭头的虚线表示用可互操作的绑定配置的连接。我们先来看看TuscanySCATours域中 TripBooking组件的3个引用。首先,flights(航班)引用连到一个实现flights预定的Web 服务,因为这个服务不是SCA服务,所以flights引用通过可互操作的绑定&binding.ws&和flights预订服务的URI进 行配置。
其次,&hotels&引用指向了TuscanySCAHotels域中的由HotelProvider这个组件所提供的Hotels服务。由于连线不能 跨越域的界限,所以这样的服务和引用都要使用&binding.ws&和服务端点URI进行配置。最后,&cars&引用连到了 TuscanySCATours域中的由CarProvider组件提供的Cars服务,因为这对引用和服务位于同一个域中,所以我们可以使用连线以及缺 省绑定连接它们。&Cars&服务没有对外暴露,也不需要和外部的非SCA程序交互,所以没有必要为其配置可互操作绑定。
接下来,看看TuscanySCAHotels域中由HotelProvider组件周围的连线。除了来自TripBooking组件的&hotels& 引用连到它之外,还有来自HotelBooker(非SCA的酒店预订客户端程序)和TuscanySCAHotels域内的HotelOffers组件 的&hotels&引用。由于&Hotels&服务配置了客户操作的绑定类型&&&binding.ws&,所以HotelBooker客户 端软件可以使用任何Web服务程序调用&Hotels&服务。HotelOffers和HotelProvider之间的连接使用SCA连线的原因是这些 组件处于同一域中。这个连线既可以使用缺省绑定也可以使用该服务提供的可互操作binding.ws绑定,在本例中,我们使用binding.ws,目的 是为了说明同一域中的连线也并不一定要使用缺省绑定。
了解图2中所描述的场景在组件配置文件中是如何配置是很有必要的。列表2给出了位于TuscanySCATours域中的组件的定义。
列表2 TuscanySCATours域中组件的组件定义文件
&composite xmlns=&http://www.osoa.org/xmlns/sca/1.0&
targetNamespace=&/&
name=&toursdomain&&
&component name=&TripBooking&&
&implementation.java class=&com.tuscanyscatours.TripBooking& /&
&reference name=&flights&&
&binding.ws uri=&:8084/Flights& /&
&/reference&
&reference name=&hotels&&
&binding.ws uri=&:8083/Hotels& /&
&/reference&
&reference name=&cars& target=&CarProvider/Cars& /&
&/component&
&component name=&CarProvider&&
&implementation.java class=&com.tuscanyscatours.CarProvider& /&
&/component&
&/composite&
列表3给出了TuscanySCAHotels域中组件的组件定义文件:
列表3 TuscanySCAHotels域的组件定义文件
&composite xmlns=&http://www.osoa.org/xmlns/sca/1.0&
targetNamespace=&/&
name=&hotelsdomain&&
&component name=&HotelProvider&&
&implementation.java
class=&com.tuscanyscahotels.Hotels& /&
&service name=&Hotels&&
&binding.ws uri=&:8083/Hotels& /&
&/service&
&/component&
&component name=&HotelOffers&&
&implementation.java class=&com.tuscanyscahotels.HotelOffers& /&
&reference name=&hotels&
target=&HotelProvider/Hotels& &
&binding.ws/& 1
&/reference&
&/component&
&/composite&
1 此处的引用使用binding.ws进行配置
为HotelOffers的&hotels&引用指定引用绑定是有必要的,如果没有,它就可能被设定成默认的binding.sca绑定,而这样会导致错误,因为它(&hotels&引用)的目标服务的配置中没有提供binding.sca方式的绑定。
图2中的例子展示了引用以及服务的大多数连接方式。不过,对于同一个域中的SCA服务和SCA引用,除了使用SCA连线之外,也可以使用一对匹配的绑定进行连接。很多Tuscany的例子就是这么做的,这样很容易展示如何为客户端和服务端配置匹配的绑定。
我们已经描述了SCA服务和引用连接的若干种选择,这里简要做一个总结。在同一个域中连接引用和服务,你可以:
使用带缺省绑定的SCA连线
使用SCA连线,并为其配置一对匹配的可互操作的绑定
为引用和服务配置匹配的绑定
连接不在同一域中的SCA引用和服务,你可以:
为引用和服务配置匹配的可互操作的绑定。
将引用或服务连接到非SCA程序,你可以:
为应用或服务配置一种可互操作绑定
可互操作绑定使SCA程序能够与非SCA程序交互,也支持不同提供商的SCA程序跨域的交互。而缺省绑定的好处是在互操作不是必须的情况下,提供商可以提供更优质的交互能力。
若了解源代码,示例章节,作者的论坛以及其他资源,请访问。
这篇文章节选自,它介绍了如何使用绑定进行组件服务连接的配置
若从购买这本书,InfoQ的读者可以获得25%的折扣,请使用这个折扣码:&infoq25&
查看英文原文:。
感谢对本文的审校。
给InfoQ中文站投稿或者参与内容翻译工作,请邮件至。也欢迎大家加入到中与我们的编辑和其他读者朋友交流。
Author Contacted
告诉我们您的想法
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
赞助商链接
InfoQ每周精要
订阅InfoQ每周精要,加入拥有25万多名资深开发者的庞大技术社区。
架构 & 设计
文化 & 方法
<及所有内容,版权所有 &#169;
C4Media Inc.
服务器由 提供, 我们最信赖的ISP伙伴。
北京创新网媒广告有限公司
京ICP备号-7
注意:如果要修改您的邮箱,我们将会发送确认邮件到您原来的邮箱。
使用现有的公司名称
修改公司名称为:
公司性质:
使用现有的公司性质
修改公司性质为:
使用现有的公司规模
修改公司规模为:
使用现在的国家
使用现在的省份
Subscribe to our newsletter?
Subscribe to our industry email notices?
我们发现您在使用ad blocker。
我们理解您使用ad blocker的初衷,但为了保证InfoQ能够继续以免费方式为您服务,我们需要您的支持。InfoQ绝不会在未经您许可的情况下将您的数据提供给第三方。我们仅将其用于向读者发送相关广告内容。请您将InfoQ添加至白名单,感谢您的理解与支持。   项目名称:MvcModels
   模板:Basic
&&&&&&&& 下面是项目的基础文件及其内容:
&&&&&&&& 模型类:Person.cs
using System.Collections.G
using System.L
using System.W
namespace MvcModels.Models
public partial class Person
public int PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
public Address HomeAddress { get; set; }
public bool IsApproved { get; set; }
public Role Role { get; set; }
public class Address
public string Line1 { get; set; }
public string Line2 { get; set; }
public string City { get; set; }
public string PostalCode { get; set; }
public string Country { get; set; }
public enum Role
控制器:HomeController.cs
using MvcModels.M
using System.Collections.G
using System.L
using System.W
using System.Web.M
namespace MvcModels.Controllers
public class HomeController : Controller
private Person[] personData =
new Person{PersonId = 1, FirstName = "Adam",LastName = "Freeman",Role = Role.Admin},
new Person{PersonId = 2 ,FirstName = "Steven",LastName = "Sanderson",Role = Role.Admin},
new Person{PersonId = 3 ,FirstName = "Jacqui",LastName = "Griffyth",Role = Role.User},
new Person{PersonId = 4 ,FirstName = "John",LastName = "Smith",Role = Role.User},
new Person{PersonId = 5 ,FirstName = "Anne",LastName = "Jones",Role = Role.Guest}
public ActionResult Index(int id)
Person dataItem = personData.Where(p =& p.PersonId == id).First();
return View(dataItem);
&&&&&&&& 视图:Index.cshtml(强类型)
@model MvcModels.Models.Person
ViewBag.Title = "Index";
&h2&Person&/h2&
&div&&label&ID:&/label&@Html.DisplayFor(m =& m.PersonId)&/div&
&div&&label&First Name:&/label&@Html.DisplayFor(m =& m.FirstName)&/div&
&div&&label&Last Name:&/label&@Html.DisplayFor(m =& m.LastName)&/div&
&div&&label&Role:&/label&@Html.DisplayFor(m =& m.Role)&/div&
&&&&&&&& CSS样式:Site.css
display: inline-
width: 100
font-weight:
form label {
input.text-box {
button[type=submit] {
margin-top: 5
form div {
理解模型绑定
&&&&&&&& 模型绑定在HTTP请求和C#(指的是MVC中的动作方法)直接起到了桥梁的作用。大多数MVC项目都在某种程度上依赖模型绑定。
&&&&&&&& 看看刚才创建的项目是否能够正常启动,并查看模型绑定是否正常工作:
& & & & & & & & & & & &
&&&&&&&& 从上图看我们的模型绑定是没有问题的,这个页面对应的URL是Home/Index/1,它包含了查看Person对象的PersonId属性值,如:Home/Index/1。
&&&&&&&& MVC自动地对该URL进行了解析,并在调用Home控制器中的Index方法时获取1作为其参数,因此使得程序能够按照预期运行。这种将URL片段转换成int型方法参数的过程是模型绑定的一个例子。
&&&&&&&& 当程序接收到请求并由路由引擎处理时,模型绑定过程便开始了。该示例中未对默认路由进行调整,也就不再这里展示了。
动作调用器会使用该路由信息推断出,为该请求进行服务所需要的是Index动作方法。但此时还不能调用,直到获取该方法的参数所需的有用的值。
默认的动作调用器,ControllerActionInvoker,要依靠模型绑定器来生成调用动作所需要的数据对象。模型绑定器由IModeBinder接口所定义:
namespace System.Web.Mvc
public interface IModelBinder
object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext);
&&&&&&&& 在项目中,可以有多个模型绑定器,每一个绑定器可以绑定一个或多个模型类型。在动作调用器需要调用一个动作方法时,它会检查该方法所定义的参数,并查找各个参数类型所依赖的模型绑定器。
&&&&&&&& 就这个示例而言,动作调用器会检查Index方法,并发现它具有一个int型的参数。然后便会查找负责int值绑定的绑定器,并调用它的BindModel方法。
&&&&&&&& 模型绑定器负责提供能用于调用Index方法的int值,这通常意味着要对请求数据(如表单数据或查询字符串值)的某些元素进行转换,但是MVC框架对如何获取这些数据并无任何限制。
模型绑定的过程:
检测目标对象(要创建的对象&&这种对象通常是动作方法的参数)的名称和类型;
通过对象名称查找数据源(请求),并找到可用数据(通常是字符串);
根据对象类型将找到的数据值转换成目标类型;
通过对象名称、对象类型和这种经过处理的数据来构造目标对象;
将构造好的对象送给动作调用器,并由动作调用器将对象注入到目标动作方法中去。
使用默认的模型绑定器
&&&&&&&& 当动作调用器找不到绑定某个类型的自定义绑定器时,将会使用默认的模型绑定器(DefaultModelBinder)。默认情况下,这个模型绑定器会按顺序依次搜索四个位置,具体如下表所示:
Request.Form
由用户在HTML的form元素中提供的值
RouteData.Values
用应用程序路由获得的值
Request.QueryString
包含在请求URL中的查询字符串部分的数据
Request.Files
请求中上传的文件
&&&&&&&& 就以上面的例子,DefaultModelBinder会这样搜索id参数的值:
Request.Form["id"];
RouteData.Values["id"];
Request.QueryString["id"];
Request.Files["id"];
   在上述的搜索过程中,直到找到适用的值,搜索才会停止。对于上述示例在搜索表单数据时将会失败,但会找到具有正确名称的路由变量(之所以能从路由中找到与参数对应的值,是因为动作方法的参数名称与路由变量名相对应,如果将动作方法参数改为类似personId这样的其他值,将不会得到匹配的数据值,最终请求将会失败。所以,在依靠默认模型绑定器的情况下,重要的是保证动作方法参数与寻找的数据属性相匹配)。也就是说在找到值后,将不会继续搜索查询字符串和上传文件的名称。
绑定简单类型
&&&&&&&& 在处理简单类型时,DefaultModelBinder会尝试使用ponentModel.TypeDescriptor类,将已经从请求数据获得的字符串值转换成参数类型。如无法成功转换,那么DefaultModelBinder便不能绑定该模型。
&&&&&&&& 如下面启动程序后,导航到/Home/Index/apple地址,将会看到绑定失败的问题:
&&&&&&&& 可以为模型绑定器做一点简化处理,即使其能够接受一个可空的(nullable)类型,这为绑定器提供一个可选方案。这样,可以让模型绑定器在调用动作时,可以选择将动作方法参数设置为null。具体调整以及效果如下:
public ActionResult Index(int? id)
Person dataItem = personData.Where(p =& p.PersonId == id).First();
return View(dataItem);
   从上图可看出,问题已经发生变化了,模型绑定器可以使用null值作为Index方法的id参数值了,只是动作方法中的代码未对null值进行处理。
   下面是通过使用为参数设置默认值的方式解决了这一问题:
public ActionResult Index(int id = 1)
Person dataItem = personData.Where(p =& p.PersonId == id).First();
return View(dataItem);
&&&&&&&& 结果如下:
&&&&&&&& 到这里,我们已经做了初步的问题处理,但是,还没有对各种情况进行检查,如路由中给出-1或500等这样在代码中不存在的id值,即超出了代码处理的范围的值,这是依然会存在一些问题。所以,在实际项目中要多注意动作方法可能接受到的参数值的范围并进行相应的测试,以保证程序的健康运行。
文化敏感解析
DefaultModelBinder类对来自不同地域的请求数据,会采用相应的设置来执行类型转换。从URL获得的值(路由及查询字符串数据)会采用非文化敏感解析进行转换。但是,从表单数据获取的值,则会考虑文化因素进行转换。
(注:这里的文化指的是计算机上设置不同区域或地域,如中国、美国、德国等。不同文化的差异主要表现为日期和货币的表示方式不同。)
这种情况引起的最普遍的问题与DateTime值有关。通常希望非文化敏感日期采取通用格式yyyy-mm-dd,而表单的日期值是服务器设定的格式。即,如果服务器的文化设置为UK(英国),则希望日期的格式是dd-mm-yyyy;而服务器如果设置为US(美国),则希望其格式为yyyy-mm-dd,尽管这两种情况下都可以接受yyyy-mm-dd格式。
如果日期值不是正确格式,则不会被转换。即,必须确保URL中的所有日期都被表示成通用格式。必须小心处理用户提供的日期值&&默认绑定器假定,用户将用服务器的文化设置格式来表示日期,这是具有国际用户的MVC应用程序不愿意遇到的情况。
绑定复合类型
&&&&&&&& 复合类型:不能使用TypeConverter类进行转换的类型,反之称之为基元类型或简单类型。
&&&&&&&& 如果动作方法的参数是复合类型,DefaultModelBinder类将用反射来获取public属性集,然后依次逐个绑定。
&&&&&&&& 下面是为演示这一工作机制,在Home控制器中添加的两个新动作方法:
public ActionResult CreatePerson()
return View(new Person());
[HttpPost]
public ActionResult CreatePerson(Person model)
return View("Index", model);
&&&&&&&& 动作方法CreatePerson对应的视图如下:
@model MvcModels.Models.Person
ViewBag.Title = "CreatePerson";
&h2&Create Person&/h2&
@using (Html.BeginForm())
&div&@Html.LabelFor(m =& m.PersonId)@Html.EditorFor(m =& m.PersonId)&/div&
&div&@Html.LabelFor(m =& m.FirstName)@Html.EditorFor(m =& m.FirstName)&/div&
&div&@Html.LabelFor(m =& m.LastName)@Html.EditorFor(m =& m.LastName)&/div&
&div&@Html.LabelFor(m =& m.Role)@Html.EditorFor(m =& m.Role)&/div&
&button type="submit"&提交&/button&
&&&&&&&& 下面是这些修改带来的效果:
&&&&&&&& 在提交表单到CreatePerson动作方法时,形成了一种不同的模型绑定情况。默认模型绑定器发现,动作方法需要一个Person对象,于是会依次处理每个属性。对于每个简单类型的属性,绑定器会试图从请求中找到一个值,如PersonId属性,绑定器会从请求的表单数据中查找到对应的PersonId的数据值。
&&&&&&&& 如果,属性是一个复合类型,那么该过程会针对新类型重复执行&&获取该类型的public属性集,而绑定器也会试图找出所有这些属性的值。不同的是这些属性名是嵌套的。如:Person类的HomeAddress属性是Address类型,在为Line1查找值时,模型绑定器查找的是HomeAddress.Line1的值。
1、创建易于绑定的HTML
&&&&&&&& 下面是针对复合类型的属性对Create.cshtml试图进行的调整:
@model MvcModels.Models.Person
ViewBag.Title = "CreatePerson";
&h2&Create Person&/h2&
@using (Html.BeginForm())
&div&@Html.LabelFor(m =& m.PersonId)@Html.EditorFor(m =& m.PersonId)&/div&
&div&@Html.LabelFor(m =& m.FirstName)@Html.EditorFor(m =& m.FirstName)&/div&
&div&@Html.LabelFor(m =& m.LastName)@Html.EditorFor(m =& m.LastName)&/div&
&div&@Html.LabelFor(m =& m.Role)@Html.EditorFor(m =& m.Role)&/div&
&div&@Html.LabelFor(m =& m.HomeAddress.City)@Html.EditorFor(m =& m.HomeAddress.City)&/div&
&div&@Html.LabelFor(m =& m.HomeAddress.Country)@Html.EditorFor(m =& m.HomeAddress.Country)&/div&
&button type="submit"&提交&/button&
&&&&&&&& 通过这样的修改,辅助器会自动地设置input元素的name标签属性,以便与默认模型绑定器所使用的格式相匹配,如:
&input name="HomeAddress.Country" class="text-box single-line" id="HomeAddress_Country" type="text" value=""&&/input&
&&&&&&&& 这样一来,不需要采取任何特别的手段,就可以确保模型绑定器能够为HomeAddress属性创建Address对象。为此演示,我们对/Views/Home/Index.cshtml视图作如下修改:
@model MvcModels.Models.Person
ViewBag.Title = "Index";
&h2&Person&/h2&
&div&&label&ID:&/label&@Html.DisplayFor(m =& m.PersonId)&/div&
&div&&label&First Name:&/label&@Html.DisplayFor(m =& m.FirstName)&/div&
&div&&label&Last Name:&/label&@Html.DisplayFor(m =& m.LastName)&/div&
&div&&label&Role:&/label&@Html.DisplayFor(m =& m.Role)&/div&
&div&&label&City:&/label&@Html.DisplayFor(m =& m.HomeAddress.City)&/div&
&div&&label&Country:&/label&@Html.DisplayFor(m =& m.HomeAddress.Country)&/div&
效果如下:
2、指定自定义前缀
&&&&&&&& 有时候也需要生成的HTML与一种类型的对象有关,但希望将其绑定到另一个对象。这意味着是包含的前缀与模型绑定器期望的结构不对应,于是对数据不能作适当的处理。
&&&&&&&& 下面在Models文件夹中添加一个新类AddressSummary.cs予以演示这种情况:
using System.Collections.G
using System.L
using System.W
namespace MvcModels.Models
public class AddressSummary
public string City { get; set; }
public string Country { get; set; }
&&&&&&&& 然后,在Home控制器中添加一个新的动作方法,以使用这个新类:
public ActionResult DisplaySummary(AddressSummary summary)
return View(summary);
&&&&&&&& 对应的视图DisplaySummary.cshtml:
@model MvcModels.Models.AddressSummary
ViewBag.Title = "DisplaySummary";
&h2&Display Summary&/h2&
&div&&label&City:&/label&@Html.DisplayFor(m =& m.City)&/div&
&div&&label&Country:&/label&@Html.DisplayFor(m =& m.Country)&/div&
&&&&&&&& 为了演示绑定到不同模型类型时的前缀问题,修改/Views/Home/CreatePerson.cshtml文件中对BeginForm辅助器方法的调用,以便将表单回递给新的DisplaySummary动作方法:
@model MvcModels.Models.Person
ViewBag.Title = "CreatePerson";
&h2&Create Person&/h2&
@using (Html.BeginForm("DisplaySummary", "Home"))
&div&@Html.LabelFor(m =& m.PersonId)@Html.EditorFor(m =& m.PersonId)&/div&
&div&@Html.LabelFor(m =& m.FirstName)@Html.EditorFor(m =& m.FirstName)&/div&
&div&@Html.LabelFor(m =& m.LastName)@Html.EditorFor(m =& m.LastName)&/div&
&div&@Html.LabelFor(m =& m.Role)@Html.EditorFor(m =& m.Role)&/div&
&div&@Html.LabelFor(m =& m.HomeAddress.City)@Html.EditorFor(m =& m.HomeAddress.City)&/div&
&div&@Html.LabelFor(m =& m.HomeAddress.Country)@Html.EditorFor(m =& m.HomeAddress.Country)&/div&
&button type="submit"&提交&/button&
&&&&&&&& 现在启动程序,并导航至/Home/CreatePerson就可以看到了。提交表单后,为City和Country属性输入的值并未显示在DisplaySummary视图生成的HTML中。原因是表单中的name属性具有HomeAddress前缀,这不是模型绑定器在试图绑定AddressSummary类型时要查找的前缀。要解决这个问题,只要对动作方法的参数运用Bind注解属性即可,目的是用它来告诉绑定器应该查找哪一个前缀,如:&
public ActionResult DisplaySummary([Bind(Prefix = "HomeAddress")]AddressSummary summary)
return View(summary);
&&&&&&&& 这样一来,在装配AddressSummary对象的属性时,模型绑定器会查找请求中的HomeAddress.City和HomeAddress. Country的数据值。在该例子中,显示了Person对象各属性的编辑器,但在提交表单数据时,用模型绑定器创建的却是AddressSummary类的实例,如下图所示。这看起来像是对一个简单的问题太长的设置,但这种绑定到不同类型对象的需求却出奇地普遍,且在实际项目中很可能会需要使用这种技术。
3、有选择地绑定属性
&&&&&&&& 在实际项目中,模型类的一些属性可能是敏感的,这时需要采取一些措施隐藏这些属性。如:隐藏属性显示、阻止该属性出现在发送给浏览器的HTML中,或简单地不在视图中添加该属性的编辑器等。
&&&&&&&& 然而,恶意的用户可以在递交表单数据时,简单地编辑发送给服务器的表单数据,然后(从返回数据中)挑出对他们有用的Country属性的值。此时真正要做的事是告诉模型绑定器不要绑定请求中的Country属性值,这可以通过在动作方法参数上使用Bind注解属性来实现。下面这段代码演示了如何在Home控制器的DisplaySummary动作方法中使用这一属性,以防止用户为Country属性提供值:
public ActionResult DisplaySummary([Bind(Prefix = "HomeAddress", Exclude = "Country")]AddressSummary summary)
return View(summary);
&&&&&&&& Exclude属性可以将一些属性排除在模型绑定过程之外。导航至/Home/CreatePerson,输入一些数据,就可以看到效果了(将看到不会显示Country属性的值了)。还有一种做法,就是使用Include属性,用以指定只应该在模型绑定中绑定的属性,而忽略其他属性。
&&&&&&&& 当Bind注解属性被用于一个动作方法参数时,它只会影响动作方法所绑定的类的实例;其他动作方法仍然会尝试绑定该参数类型所定义的所有属性。如果需要影响更加广泛,可以将Bind注解属性运用到模型类本身,如:
using System.Collections.G
using System.L
using System.W
using System.Web.M
namespace MvcModels.Models
[Bind(Include = "City")]
public class AddressSummary
public string City { get; set; }
public string Country { get; set; }
&&&&&&&& 注意:
当Bind注解属性运用于模型类,同时也用于动作方法参数时,只有这两处注解属性都未排除的模型属性,才会被包含在绑定过程中。
Bind注解属性同时用于模型类和动作方法参数时,模型类的绑定优先于动作方法参数的绑定。比如,在模型类上排除了Country,但在动作方法参数中包含了Country,该属性仍然是被排除的。
绑定到数组与集合
1、& 绑定到数组
为了演示这一特性,需要在Home控制器中添加一个新的方法Names:
public ActionResult Names(string[] names)
// 必须检查是否为空,且参数的默认值只能是常数或文字值
names = names ?? new string[0];
return View(names);
&&&&&&&& 创建用例显示数组绑定的视图文件,/Views/Home/Names.cshtml:
@model string[]
ViewBag.Title = "Names";
&h2&Names&/h2&
@if (Model.Length == 0)
using (Html.BeginForm())
for (int i = 0; i & 3; i++)
&div&&label&@(i + 1):&/label&@Html.TextBox("names")&/div&
&button type="submit"&提交&/button&
foreach (string str in Model)
&p&@str&/p&
@Html.ActionLink("返回", "Names")
&&&&&&&& 该视图根据视图模型的数据项数显示不同的内容。如果没有数据项,则显示一个表单,其中有三个input元素:
&form action="/Home/Names" method="post"&
&&&&&&&&&&& &div&&label&1:&/label&&input id="names" name="names" type="text" value="" /&&/div&
&&&&&&&&&&& &div&&label&2:&/label&&input id="names" name="names" type="text" value="" /&&/div&
&&&&&&&&&&& &div&&label&3:&/label&&input id="names" name="names" type="text" value="" /&&/div&
&&&&&&& &button type="submit"&提交&/button&
&&&&&&&& 递交该表单时,默认的模型绑定器将能够查找与动作方法中的字符串数组同名的数据项。就该示例,会将所有input元素的内容聚集到一起,如下图:
2、& 绑定到集合
对于.NET的集合类一样可以绑定。如:
public ActionResult Names(IList&string& names)
// 必须检查是否为空,且参数的默认值只能是常数或文字值
names = names ?? new List&string&();
return View(names);
&&&&&&&& 注意,这里使用的是IList接口,也就是说,可以不指定其具体的实现类(当然也可以指定,这就看个人喜好了)。下面是对应的视图类的修改:
@model IList&string&
ViewBag.Title = "Names";
&h2&Names&/h2&
@if (Model.Count == 0)
using (Html.BeginForm())
for (int i = 0; i & 3; i++)
&div&&label&@(i + 1):&/label&@Html.TextBox("names")&/div&
&button type="submit"&提交&/button&
foreach (string str in Model)
&p&@str&/p&
@Html.ActionLink("返回", "Names")
&&&&&&&& Names动作方法的功能并没有发生变化,只是现在使用的是集合类而不是数组。
3、绑定到自定义模型类型集合
&&&&&&&& 我们还可以将一些单个的数据属性绑定到一个自定义类型的数组,如上面的AddressSummary模型类。下面是为控制器添加的新的动作方法:
public ActionResult Address(IList&AddressSummary& address)
address = address ?? new List&AddressSummary&();
return View(address);
&&&&&&&& 对应的视图文件是/Views/Home/Address.cshtml,内容如下:
@using MvcModels.Models
@model IList&AddressSummary&
ViewBag.Title = "Address";
&h2&Address&/h2&
@if (Model.Count() == 0)
using (Html.BeginForm())
for (int i = 0; i & 3; i++)
&fieldset&
&legend&Address @(i + 1)&/legend&
&div&&label&City:&/label&@Html.Editor("[" + i + "].City")&/div&
&div&&label&Country:&/label&@Html.Editor("[" + i + "].Country")&/div&
&/fieldset&
&button type="submit"&提交&/button&
foreach (AddressSummary address in Model)
&p&@address.City,@address.Country&/p&
@Html.ActionLink("返回", "Address")
&&&&&&&& 如果模型集合中无数据项,该视图渲染的结果如下:
&form action="/Home/Address" method="post"&
&&&&&&&&&&& &fieldset&
&&&&&&&&&&&&&&& &legend&Address 1&/legend&
&&&&&&&&&&&&&&& &div&
&&&&&&&&&&&&&&& &label&City:&/label&
&&&&&&&&&&&&&&& &input class="text-box single-line" name="[0].City" type="text" value="" /&&/div&
&&&&&&&&&&&&&&& &div&
&&&&&&&&&&&&&&& &label&Country:&/label&
&&&&&&&&&&&&&&& &input class="text-box single-line" name="[0].Country" type="text" value="" /&&/div&
&&&&&&&&&&& &/fieldset&
&&&&&&&&&&& &fieldset&
&&&&&&&&&&&&&&& &legend&Address 2&/legend&
&&&&&&&&&&&&&&& &div&
&&&&&&&&&&&&&&& &label&City:&/label&
&&&&&&&&&&&&&&& &input class="text-box single-line" name="[1].City" type="text" value="" /&&/div&
&&&&&&&&&&&&&&& &div&
&&&&&&&&&&&&&&& &label&Country:&/label&
&&&&&&&&&&&&&&& &input class="text-box single-line" name="[1].Country" type="text" value="" /&&/div&
&&&&&&&&&&& &/fieldset&
&&&&&&&&&&& &fieldset&
&&&&&&&&&&&&&&& &legend&Address 3&/legend&
&&&&&&&&&&&&&&& &div&
&&&&&&&&&&&&&&& &label&City:&/label&
&&&&&&&&&&&&&&& &input class="text-box single-line" name="[2].City" type="text" value="" /&&/div&
&&&&&&&&&&&&&&& &div&
&&&&&&&&&&&&&&& &label&Country:&/label&
&&&&&&&&&&&&&&& &input class="text-box single-line" name="[2].Country" type="text" value="" /&&/div&
&&&&&&&&&&& &/fieldset&
&&&&&&& &button type="submit"&提交&/button&
&&&&&&&& 在递交这个表单时,默认模型绑定器知道它需要创建一个AddressSummary对象集合,并用name标签属性中的数组索引前缀获取对象的属性值。以[0]为前缀的那些属性表示第一个AddressSummary对象,以[1]为前缀的那些属性表示第二个AddressSummary对象,依次类推。
&&&&&&&& Address视图为三个这样的索引对象定义了input元素,并在模型集合含有数据项时显示它们。在能正常演示之前,还需要从AddressSummary模型类中删除Bind注解属性,否则,模型绑定器会忽略Country属性:
// 该注解属性已被注掉
//[Bind(Include = "City")]
public class AddressSummary
public string City { get; set; }
public string Country { get; set; }
&&&&&&&& 启动程序后,输入一些数据,并提交后模型绑定器会找到并处理被索引的数据值,并用它们创建AddressSummary对象的集合,然后将该集合回递给视图,并显示结果,效果如图:
手工调用模型绑定
&&&&&&&& 当动作方法定义了参数,模型绑定过程将是自动执行的。但这一过程也可以自行控制。下面就通过手动调用,来进一步明确地控制如何实例化模型对象、从何处获取数据,以及如何处理数据解析错误等。
&&&&&&&& 首先,将Home控制中的Address动作方法修改为手工调用绑定过程:
public ActionResult Address()
IList&AddressSummary& address = new List&AddressSummary&();
UpdateModel(address);
return View(address);
&&&&&&&& 上面代码中的UpdateModel方法以上一条语句定义的模型对象为参数,并试图用标准的绑定过程来获取其public属性的值。
&&&&&&&& 当手工调用绑定过程时,可以将绑定过程限制到一个单一的数据源。默认情况下,绑定器会查看四个地方:表单数据、路由数据、查询字符串和上传文件。下面演示了如何将绑定器限制到表单数据:
public ActionResult Address()
IList&AddressSummary& address = new List&AddressSummary&();
UpdateModel(address, new FormValueProvider(ControllerContext));
return View(address);
&&&&&&&& UpdateModel这一重载版本以IValueProvider接口的一个实现为参数,该实现也成为绑定过程的唯一数据。四个默认数据位置中的每一个都是由一个IValueProvider实现表示的,如下表:
IValueProvider实现
Request.Form
FormValueProvider
都以ControllerContext为构造器参数,这是Controller类的一个属性
RouteData.Values
RouteDataValueProvider
Request.QueryString
QueryStringValueProvider
Request.Files
HttpFileCollectionValueProvider
   限制数据源最常用的方式是只查找表单值。可以用一个更优美的绑定技巧,而不必创建FormValueProvider实例:
public ActionResult Address(FormCollection
IList&AddressSummary& address = new List&AddressSummary&();
UpdateModel(address, formData);
return View(address);
&&&&&&&& FormCollection类也实现了IValueProvider接口,而且,如果把该动作方法定义成以这个类型为参数,那么绑定器将提供一个能够直接传递给UpdateModel方法的对象。
   UpdateModel方法的一些其他重载版本允许指定一个搜索前缀,并指定绑定过程中应当包含哪些模型属性。
处理绑定错误
&&&&&&&& 在实际项目中,难免会有些无法绑定的值,所以,当明确地调用模型绑定时,需要负责处理诸如此类的错误。模型绑定器会通过抛出InvalidOperationException异常来表示绑定错误,其中细节通过ModelState特性进行检查,所以,在使用UpdateModel方法时,必须做好捕捉该异常的准备,并用ModelState将错误消息展示给用户:
public ActionResult Address(FormCollection formData)
IList&AddressSummary& address = new List&AddressSummary&();
UpdateModel(address, formData);
catch (InvalidOperationException ex)
// 给用户提供反馈
return View(address);
&&&&&&&& 另一个办法是使用TryUpdateModel方法,如果绑定成功将返回true,否则返回false,如:
public ActionResult Address(FormCollection formData)
IList&AddressSummary& address = new List&AddressSummary&();
if (TryUpdateModel(address, formData))
// 正常处理
// 给用户提供反馈
return View(address);
&&&&&&&& 使用TryUpdateModel方法的好处是不需要我们单独捕捉异常了。但这在模型绑定过程方面,与UpdateModel方法没有功能上的差异。
提示:当自动调用模型绑定时,绑定错误不会发出异常信号。因此,必须通过ModelState.IsValid属性来检查结果。
定制模型绑定系统
&&&&&&&& 还有一种绑定方式,就是自定义模型绑定系统来实现绑定系统的定制。
创建自定义的值提供器
&&&&&&&& 创建一个自定义的值提供器就可以将自己的数据源添加到模型绑定过程了。值提供器需要实现IValueProvider接口:
namespace System.Web.Mvc
public interface IValueProvider
bool ContainsPrefix(string prefix);
ValueProviderResult GetValue(string key);
&&&&&&&& ContainsPrefix方法由模型绑定器调用,以确定这个值提供器是否可以解析给定前缀的数据。
&&&&&&&& GetValue方法返回给定数据键的值,如果无法得到合适的数据时,则返回null。
&&&&&&&& 下面是我们在Infrastructure文件夹中添加的一个自定义的值提供器CountryValueProvider:
using System.Collections.G
using System.L
using System.W
using System.Web.M
using System.G
namespace MvcModels.Infrastructure
public class CountryValueProvider : IValueProvider
public bool ContainsPrefix(string prefix)
return prefix.ToLower().IndexOf("country") & -1;
public ValueProviderResult GetValue(string key)
if (ContainsPrefix(key))
return new ValueProviderResult("USA", "USA", CultureInfo.InvariantCulture);
return null;
&&&&&&&& 上面代码对请求Country属性的值进行响应,并总是返回USA。对所有其他的请求,返回null,来表示无法提供数据。
必须将数据值作为一个ValueProviderResult类来返回。这个类有三个构造参数:
第一个参数是与请求键关联的数据项;
第二个参数是该数据的安全显示形式;
最后一个参数是与该值相关的文化信息(如这里指定了InvariantCulture)。
   在有了值提供器后,还需要对其进行注册,这里采用工厂类为其注册的方式,该工厂类(CustomValueProviderFactory.cs)位于Infrastructure文件夹,该类派生于抽象类ValueProviderFactory,具体内容如下:
using System.Collections.G
using System.L
using System.W
using System.Web.M
namespace MvcModels.Infrastructure
public class CustomValueProviderFactory : ValueProviderFactory
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
return new CountryValueProvider();
&&&&&&&& 当模型绑定器要为绑定过程获取值时,会调用这个GetValueProvider方法。上述示例代码中简单地创建并返回了CountryValueProvider类的一个实例,但可以使用controllerContext参数提供的数据创建不同的值提供器,以便对不同类型的请求进行响应。
&&&&&&&& 剩下的就是在Global.asax的Application_Start方法中注册这个工厂类了:
using System.Collections.G
using System.L
using System.W
using System.Web.H
using System.Web.M
using System.Web.O
using System.Web.R
using MvcModels.I
namespace MvcModels
// 注意: 有关启用 IIS6 或 IIS7 经典模式的说明,
// 请访问 /?LinkId=9394801
public class MvcApplication : System.Web.HttpApplication
protected void Application_Start()
AreaRegistration.RegisterAllAreas();
ValueProviderFactories.Factories.Insert(0,new CustomValueProviderFactory());
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
&&&&&& 这里注册的时候将自定义值提供器注册工厂类放在了第一个位置,目的是为了能够让自定义值提供器被优先考查。如果是需要将自定义的值提供器作为备选方案,则可以是Add方法把工厂类追加到集合的末尾,如:
ValueProviderFactories.Factories.Add(0,new CustomValueProviderFactory())
&&&&&&&& 现在可以测试我们定义的这个值提供器了,但是,在此之前仍需修改一处,那就是Address动作方法,以使模型绑定器为模型属性值不只考察表单数据,如下:
public ActionResult Address()
IList&AddressSummary& address = new List&AddressSummary&();
UpdateModel(address);
return View(address);
效果如下图:
创建自定义模型绑定器
&&&&&&&& 通过创建一个自定义模型绑定器可以覆盖默认绑定器的行为。自定义模型绑定器需要实现IModelBinder接口。
&&&&&&&& 在Infrastructure文件夹中添加自定义绑定器AddressSummaryBinder.cs:
using MvcModels.M
using System.Collections.G
using System.L
using System.W
using System.Web.M
namespace MvcModels.Infrastructure
public class AddressSummaryBinder : IModelBinder
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
AddressSummary model = (AddressSummary)bindingContext.Model ?? new AddressSummary();
model.City = GetValue(bindingContext, "City");
model.Country = GetValue(bindingContext, "Country");
private string GetValue(ModelBindingContext context, string name)
name = (context.ModelName == "" ? "" : context.ModelName + ".") +
ValueProviderResult result = context.ValueProvider.GetValue(name);
if (result == null || result.AttemptedValue == "")
return "&Not Specified&";
return (string)result.AttemptedV
&&&&&&&& 当MVC框架需要一个模型绑定器所支持的模型类型的实例时,将调用BindModel方法。建议:一个模型绑定器中可以创建多个类型的支持,但最好在一个绑定器中仅支持一个类型。
&&&&&&&& BindModel方法简介:
&&&&&&&& 参数:
ControllerContext:可以用于获取当前请求的细节;
ModelBindingContext:用于提供当前寻找的模型对象的细节,并能访问MVC应用程序中其他模型绑定工具,其中一些最有用的属性如下:
    &Model:如果手工调用了绑定,可返回传递给UpdateModel方法的模型对象;
    &ModelName:返回绑定模型的名称;
    &ModelType:返回被创建模型的类型;
    &ValueProvider:返回能用于从请求中获取数据值的IValueProvider实现。
上述示例功能说明:
当调用BindModel方法时,检测是否已设置了ModelBindingContext的Model属性,如果已设置,则使用当前模型为将要位置生成数据值的对象,否则仅创建一个新的AddressSummary实例;
通过私有的GetValue方法分别获取City和Country的值,然后返回给AddressSummary类型对象相应的属性;
在GetValue方法中使用ValueProviderResult. ValueProvider属性获取的IValueProvider实现,以获取模型对象属性的值。
ModelName属性可以指出正在寻找的属性名称是否需要追加一个前缀。比如我们的动作方法在试图创建AddressSummary对象的集合,这就是说各个input元素将具有附带了[0]和[1]等前缀的name属性值。这样,在请求中的值将是[0].City、[0].Country等。
如果无法为某一属性找到值或该属性为空字符串时,便提供一个默认值:&Not Specified&。
注册自定义模型绑定器
&&&&&&&& 如果要是该模型绑定器能够正常工作,需要对其进行注册。这可以在Global.asax的Application_Start方法中完成:
using System.Collections.G
using System.L
using System.W
using System.Web.H
using System.Web.M
using System.Web.O
using System.Web.R
using MvcModels.I
using MvcModels.M
namespace MvcModels
// 注意: 有关启用 IIS6 或 IIS7 经典模式的说明,
// 请访问 /?LinkId=9394801
public class MvcApplication : System.Web.HttpApplication
protected void Application_Start()
AreaRegistration.RegisterAllAreas();
// 此句已被注释
//ValueProviderFactories.Factories.Insert(0, new CustomValueProviderFactory());
ModelBinders.Binders.Add(typeof(AddressSummary), new AddressSummaryBinder());
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
&&&&&& 下面导航至http://localhost:4832/Home/Address,并只填充部分内容,以便可以对这一自定义模型绑定器进行测试,表单被递交时,自定义模型绑定器会对其所有未输入值的属性使用&Not Specified&默认值:
提示:也可以通过在模型类上使用ModelBinder注解属性进行修饰,也可以指定自定义模型绑定器。
阅读(...) 评论()

我要回帖

更多关于 加油卡绑定证件不匹配 的文章

 

随机推荐