zl程序教程

您现在的位置是:首页 >  后端

当前栏目

通过实例模拟ASP.NET MVC的Model绑定机制:数组

2023-09-27 14:27:56 时间

[续《通过实例模拟ASP.NET MVC的Model绑定机制:简单类型+复杂类型]》]基于数组和集合类型的Model绑定机制比较类似,对于绑定参数类型或者参数类型的某个属性为数组或者集合,如果ValueProvider根据对应的Key能够匹配多条数据,那么这些数据最终将会转换为绑定的数组/集合的元素。此外,针对数组/集合的Model绑定还支持基于索引的方式。[源代码从这里下载][本文已经同步到《How ASP.NET MVC Works?》中]

一、基于名称的数组绑定

对于针对NameValueConllectionProvider来说,通过GetValue方法得到的ValueProviderResult的RawValue总是一个字符串数组(不论是否具有多条数据于指定的Key相匹配,如果只有一条匹配的数据,RawValue就是一个具有一个元素的字符串数组)。当我们调用ValueProviderResult的ConvertTo方法将提供的值转换成某种类型时,如果目标类型是数组或者集合,那么RawValue代表的字符串数组元素将会转换成目标对象的元素;如果目标类型不属于集合,那么参与数据转换的仅仅是RawValue数组的第1个元素。

如下面的代码片断所示,在默认的HomeController的默认Action方法Index中,我们创建了一个NameValueCollectionValueProvider对象,作为数据源的NameValueCollection中包含了三个同名(foo)数据条目。我们调用它的GetValue方法得到一个ValueProviderResult对象,然后我们将该对象的RawValue呈现出来。最后我们调用该ValueProviderResult对象的ConvertTo对象将提供的值转换为int[]和int,并将转换后的值呈现出来。

 1: public class HomeController : Controller

假设针对具有如下定义的Action方法ActionMethod提交的标单具有如上的输入元素,在三个文本框中输入的字符串将绑定到foo参数,而通过三个文件输入元素上传得文件将会绑定给bar参数。


现在我们对用于模拟默认Model绑定的自定义DefaultModelBinder进行进一步完善,使之对基于名称的数组绑定提供支持。如下面的代码片断所示,我们在BindModel方法中添加了针对数组类型的Model绑定代码,而具体的实现定义在BindArrayModel方法中。


 11: ModelMetadata modelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() = null, parameterType);

 22: foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(parameterType))

 24: string key = string.IsNullOrEmpty(prefix) ? property.Name : prefix + "." + property.Name;

 34: IEnumerable enumerable = this.ValueProvider.GetValue(prefix).ConvertTo(parameterType) as IEnumerable;

 43: Array array = Array.CreateInstance(parameterType.GetElementType(), list.Count);

定义在BindArrayModel方法中针对数组的Model绑定逻辑很简单,我们直接通过ValueProvider将通过指定前缀得到的数据值转换为IEnumerable类型,并进一步添加到一个List object 对象中,最终我们将该List object 对象的元素拷贝到一个创建的数组对象并将其作为Model对象返回。

为了演示针对数组的Model绑定,我们按照如下的方式修改了Action方法。该方法具有两个参数foo和bar,前者是一个字符串数组,后者的类型Bar的Baz属性是一个整型数组。在Action方法中,我们将foo参数和bar参数的Baz属性代表数组元素呈现出来。


 14: return new NameValueCollectionValueProvider(requestData, CultureInfo.InvariantCulture);

 20: Array.ForEach(foo,item= Response.Write(" nbsp; nbsp; nbsp; nbsp;"+ item + " br/ "));

 22: Array.ForEach(bar.Baz, item = Response.Write(" nbsp; nbsp; nbsp; nbsp;" + item + " br/ "));

通过GetValueProvider方法提供的NameValueCollectionValueProvider具有针对这两个参数的数据源,从上面的代码片断所示,针对参数foo和bar的数据项具有相同的名称(foo和bar.baz)。该程序运行之后会在浏览器中得到如下所示的输出结果。



二、基于索引的数组绑定

对于存在于作为ValueProvider数据源的NameValueCollection/Dictionary string, object 中的数据项来说,如果它们绑定的对象是一个数组,可以采用相同的名称。这样的Model绑定方式仅仅是用于元素为简单类型的数组。除此之外,也可以采用格式为“[index]”的基于索引的前缀来表示。

ValueProvider基于索引的匹配策略也可以通过HtmlHelper TModel 的模板方法EditorFor来体现。如下面的代码片断所示,在一个Model类型为Contact数组的强类型View中,我们调用HtmlHelper TModel 的扩展方法EditorFor将数组的前两个元素的相关信息以编辑模式呈现出来。


下面的XML片断代表了上面这段代码在最终生成的HTML中对应的6个类型为“text”的 input 元素,我们可以清楚地看到它们的名称被添加了[0]和[1]这样的索引前缀。如果这些元素存在于一个提交的标单中,并且目标Action方法包含一个匹配的Contact数组类型的参数,Model绑定系统将最终生成两个元素的Contact数组作为其参数值,数组中元素的顺序与索引数值保持一致。


基于数组的Model绑定采用“基零索引”,即将作为数组下边界的索引前缀必须是“[0]”。此外,还要求索引在数值上必须是连续的。举个简单的例子,假设提交的标单中具有如下6个类型为“hidden”的 input 元素,它们采用了基于索引的命名,并且从数字上看索引不是连续的(缺了一个[3])。


如果提供的标单对应如下所示的Action方法,上述的 input 元素值将会绑定到字符串数组类型的参数array上。由于索引值不具有连续性,会导致后面的三个 input 元素值(“123”、“456”和“789”)会被丢弃,也就是说绑定后的array参数值仅仅具有三个元素(“foo”、“bar”和“baz”)。


除了采用基零整数作为数组索引之外,我们还可以采用任意字符串作为其索引,但是作为索引的字符串需要和数组元素值一样存在于ValueProvider的数据源中。索引数据项名称为“index”,并且与数组元素数据项具有相同的前缀。同样以上面这个参数类型为字符串数组的Action方法为例,我们可以通过提交具有如下内容的表单来调用这个Action方法并为之提供相应的参数值。


被提交标单中三个类型为“text”的 input 元素值将会绑定到目标Action方法的字符串参数array。它们通过基于字符串的索引进行命名,而作为索引的字符串通过类型为“hidden”的 input 元素和作为参数绑定的数据一并提交。这些用于定义索引字符串的 input 元素一并命名为“index”。

现在我们对用于模拟默认Model绑定的自定义DefaultModelBinder进行进一步完善,使之支持基于索引的数组绑定。如下的代码片断所示,我们在用于进行数组绑定的BindArrayModel方法中添加了额外的代码用于提取索引值(整型和字符串类型)列表,并且根据这行索引值生成相应的前缀和对应的Key通过ValueProvider得到针对数组元素的值。得到的值被添加到预先创建的对象列表中并最终成为作为参数值的数组对象的元素。


 11: ModelMetadata modelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() = null, parameterType);

 21: foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(parameterType))

 23: string key = string.IsNullOrEmpty(prefix) ? property.Name : prefix + "." + property.Name;

 32: if (!string.IsNullOrEmpty(prefix) this.ValueProvider.ContainsPrefix(prefix))

 34: IEnumerable enumerable = this.ValueProvider.GetValue(prefix).ConvertTo(parameterType) as IEnumerable;

 55: object[] array = (object[])Array.CreateInstance(parameterType.GetElementType(), list.Count);

 59: private IEnumerable string GetIndexes(string prefix, out bool numericIndex)

 61: string key = string.IsNullOrEmpty(prefix)?"index": prefix+"."+"index";

索引列表的获取通过方法GetIndexes实现。由于作为索引值的数据项以“index”命名,所以该方法在此基础上加上传入的前缀作为key调用ValueProvider的GetValue方法可以直接得到针对指定前缀的所有字符串类型的索引值。而针对基零整数的索引列表则通过GetZeroBasedIndexes方法返回。

我们现在将自定义的DefaultModelBinder用于进行基于数组的Model绑定,在之前演示实例的基础上我们对Action方法作了如下的修改,使之具有一个Contact数组类型的参数。在该Action方法中,我们将作为数组元素的Contact对象相关信息呈现出来。对于通过GetValueProvider方法提供的NameValueCollectionValueProvider来说,我们以基零整数的方式提供了两个Contact对象的数据。


 16: return new NameValueCollectionValueProvider(requestData, CultureInfo.InvariantCulture);

 24: Response.Write(string.Format("{0}: {1} br/ ", "PhoneNo", contact.PhoneNo));

 25: Response.Write(string.Format("{0}: {1} br/ br/ ", "EmailAddress", contact.EmailAddress));

运行我们的程序之后会在浏览器中得到如下所示的输出结果,可见目标Action的数组参数通过我们自定义的DefaultModelBinder得到了正确地绑定。(S517)


上面这个例子演示了针对基零整数作为索引的数组绑定,DefaultModelBinder同样支持针对任意字符串作为索引的数组绑定。在下面的代码片断中,我们修改了GetValueProvider方法使创建的NameValueCollectionValueProvider以字符串索引的方式为Contact数组提供数据。程序运行之后,我们可以在浏览器中得到相同的输出结果。


 18: return new NameValueCollectionValueProvider(requestData, CultureInfo.InvariantCulture);

通过实例模拟ASP.NET MVC的Model绑定的机制:简单类型+复杂类型
通过实例模拟ASP.NET MVC的Model绑定的机制:数组
通过实例模拟ASP.NET MVC的Model绑定的机制:集合+字典

作者:蒋金楠
微信公众账号:大内老A
微博:www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 原文链接

ASP.NET Core MVC 之模型(Model) ASP.NET Core MVC 之模型(Model) 1.模型绑定 ASP.NET Core MVC 中的模型绑定将数据从HTTP请求映射到操作方法参数。参数既可以是简单类型,也可以是复杂类型。
菜鸟入门【ASP.NET Core】15:MVC开发:ReturnUrl实现、Model后端验证 、Model前端验证 ReturnUrl实现 我们要实现returnUrl,我们需要在注册(Register)方法中接收传进的returnUrl并给它默认值null,然后将它保存在ViewData里面 然后我们定义一个内部方法来判断跳转returnUrl