MVC Model Binding

本文介绍了ASP.NET MVC框架中模型绑定的基本原理及六个实用技巧,包括使用模型绑定代替Request.Form、自定义模型绑定器、通过继承实现定制绑定、利用数据注解进行验证、分离绑定与验证阶段以及理解绑定器与环境的关系。

6 Tips for ASP.NET MVC Model Binding

Model binding in the ASP.NET MVC framework is simple. Your action methods need data, and the incoming HTTP request carries the data you need. The catch is that the data is embedded into POST-ed form values, and possibly the URL itself. Enter the DefaultModelBinder, which can magically convert form values and route data into objects. Model binders allow your controller code to remain cleanly separated from the dirtiness of interrogating the request and its associated environment.

  

Here are some tips on how to take advantage of model binding in your MVC projects.

Tip #1: Prefer Binding Over Request.Form

If you are writing your actions like this ..

[AcceptVerbs(HttpVerbs.Post)]

public ActionResult Create()

{

Recipe recipe = new Recipe();

recipe.Name = Request.Form["Name"];

 

// ...

 

return View();

}

.. then you are doing it all wrong. The model binder can save you from using the Request and HttpContext properties – those properties make the action harder to read and harder to test. One step up would be to use a FormCollection parameter instead:

public ActionResult Create(FormCollection values)

{

Recipe recipe = new Recipe();

recipe.Name = values["Name"];

 

// ...

 

return View();

}

With the FormCollection you don't have to dig into the Request object, and sometimes you need this low level of control. But, if all of your data is in Request.Form, route data, or the URL query string, then you can let model binding work its magic:

[AcceptVerbs(HttpVerbs.Post)]

public ActionResult Create(Recipe newRecipe)

{

// ...

 

return View();

}

In this example, the model binder will create your newRecipe object and populate it with data it finds in the request (by matching up data with the recipe's property names). It's pure auto-magic. There are many ways to customize the binding process with "white lists", "black lists", prefixes, and marker interfaces. For more control over when the binding takes place you can use the  UpdateModel and TryUpdateModel methods. Just beware of unintentional binding – see Justin Etheredge's Think Before You Bind.

Tip #2: Custom model binders

Model binding is also one of the extensibility points in the MVC framework. If you can't use the default binding behavior you can provide your own model binders, and mix and match binders. To implement a custom model binder you need to implement the IModelBinder interface. There is only method involved - how hard can it be?

public interface IModelBinder

{

object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext);

}

Once you get neck deep into model binding, however, you'll discover that the simple IModelBinder interface doesn't fully describe all the implicit contracts and side-effects inside the framework.  If you take a step back and look at the bigger picture you'll see that model binding is but one move in a carefully orchestrated dance between the model binder, the ModelState, and the HtmlHelpers. You can pick up on some of these implicit behaviors by reading the unit tests for the default model binder.

Scott Hanselman demonstrates a non-trivial model binder in his post "Splitting DateTime – Unit Testing ASP.NET MVC Custom Model Binders". One detail I want to call out about Scott's DateTime splitter is how you still don't need to use Request.Form when building the model. Inside the GetA method you'll see how Scott uses the binding context's ValueProvider property to fetch data. The ValueProvider represents an amalgamation of all data from the request's posted form values, routing data, and query string. Scott's example is great, but it is missing one detail – the propagation of binding errors.

If the default model binder has problems putting data into your object, it will place the error messages and the erroneous data value into ModelState. You can check ModelState.IsValid to see if binding problems are present, and use ModelState.AddModelError to inject your own error messages. See this very simple tutorial for more information on how ModelState and HtmlHelpers can work together to present validation errors to the user. 

If you scroll down the comments to Scott's post you'll see Sebastien Crocquesel's patch for Scott's code. If a conversion fails, Sebastien's code will use ModelState.AddModelError to propagate the error. Both the controller action and the view can look in ModelState to see if there was a binding problem. The controller would need to check ModelState for errors before saving stuff into the database, while the view can check ModelState for errors to give the user validation feedback. One important note is that the HtmlHelpers you use in a view will require ModelState to hold both a value (via ModelState.SetModelValue) and the error (via AddModelError) or you'll have runtime errors (null reference exceptions). The following code can demonstrate the problem:

[AcceptVerbs(HttpVerbs.Post)]

public ActionResult Create(FormCollection Form)

{

// this is the wrong approach ...

if (Form["Name"].Trim().Length == 0)

ModelState.AddModelError("Name", "Name is required");

 

return View();

}

The above code creates a model error without ever setting a model value. It has other problems, too, but it will create exceptions if you render the following view.

<%= Html.TextBox("Name", Model.Name) %>

Even though you've specified Model.Name as the value for the textbox, the textbox helper will see the model error and attempt to display the "attempted value" that the user tried to put in the model. If you didn't set the model value in model state you'll see a null reference exception.

Tip #3: Custom Model Binding via Inheritance

If you've decided to implement a custom model binder, you might be able to cut down on the amount of work required by inheriting from DefaultModelBinder and adding some custom logic. In fact, this should be your default plan until you are certain you can't subclass the default binder to achieve the functionality you need. For example, suppose you just want to have some control over the creation of your model object. The DefaultModelBinder will create object's using Activator.CreateInstance and the model's default constructor. If you don't have a default constructor for your model, you can subclass the DefaultModelBinder and override the CreateModel method.

Jimmy Bogard has an example of sub classing the DefaultModelBinder in his post titled "A Better Model Binder".

Tip #4: Using Data Annotations for Validation

Brad Wilson explains everything beautifully in this post: DataAnnotations and ASP.NET MVC.

I encourage you to go read Brad's post, but if you are in a hurry, here is a summary:

.NET 3.5 SP1 shipped a System.ComponentModel.DataAnnotations assembly that looks to play a central role as we move forward with the .NET framework. By using data annotations and the DataAnnotationsModelBinder, you can take care of most of your server-side validation by simply decorating your model with attributes.

public class Recipe

{

[Required(ErrorMessage="We need a name for this dish.")]

[RegularExpression("^Bacon")]

public string Name { get; set; }

 

// ...

}

The DataAnnotationsModelBinder is also a great sample to read and understand how to effectively subclass the default model binder.

Tip #5 : Recognize Binding and Validation As Two Phases

Binding is about taking data from the environment and shoving it into the model, while validation is checking the model to make sure it meets our expectations. These are different different operations, but model binding tends to blur the distinction. If you want to perform validation and binding together in a model binder, you can – it's exactly what the DataAnnotationsModelBinder will do. You can also find samples like Automatic Model Validation with ASP.NET MVC, xVal, Castle, and a Custom Binder (John McDowall), and Enterprise Library Validation Application Block with MVC Binders (Steve Michelotti).  However, one thing that is often overlooked is how the DefaultModelBinder itself separates the binding and validation phases. If all you need is simple property validation, then all you need to do is override the OnPropertyValidating method of the DefaultModelBinder.

   

Tip #6: Binders Are About The Environment

Earlier I said that "model binders allow your controller code to remain cleanly separated from the dirtiness of interrogating the request and its associated environment". Generally, when we think of binder we think of moving data from the routing data and posted form values into the model. However, there is no restriction of where you find data for your model. The context of a web request is rich with information about the client. A good example is another Scott Hanselman post on automatically binding the user's identity into a model see: IPrincipal (User) ModelBinder in ASP.NET MVC for easier testing.

In Conclusion

Model binding is beautiful magic, so take advantage of the built-in magic when you can. I think the topic of model binding could use it's own dedicated web site. It would be a very boring web site with lots of boring code, but model binding has many subtleties. For instance, we never even got to the topic of culture in this post.

Do you have any model binding tips?

.net8 .netcore dbug: Microsoft.AspNetCore.Mvc.Razor.Compilation.DefaultViewCompiler[4] Initializing Razor view compiler with no compiled views. dbug: Microsoft.AspNetCore.Mvc.ModelBinding.ModelBinderFactory[12] Registered model binder providers, in the following order: Microsoft.AspNetCore.Mvc.ModelBinding.Binders.BinderTypeModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ServicesModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.BodyModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.HeaderModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.FloatingPointTypeModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.EnumTypeModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.DateTimeModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.SimpleTypeModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.TryParseModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.CancellationTokenModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ByteArrayModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.FormFileModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.FormCollectionModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.KeyValuePairModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.DictionaryModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ArrayModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.CollectionModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexObjectModelBinderProvider info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[63] User profile is available. Using 'C:\Users\Administrator\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest. dbug: Microsoft.Extensions.Hosting.Internal.Host[1] Hosting starting dbug: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository[37] Reading data from file 'C:\Users\Administrator\AppData\Local\ASP.NET\DataProtection-Keys\key-3112a98c-0ea6-4bbd-b81a-7371dd2684ac.xml'. dbug: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository[37] Reading data from file 'C:\Users\Administrator\AppData\Local\ASP.NET\DataProtection-Keys\key-7973feae-6c2e-433e-b462-385567895847.xml'. dbug: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[18] Found key {3112a98c-0ea6-4bbd-b81a-7371dd2684ac}. dbug: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[18] Found key {7973feae-6c2e-433e-b462-385567895847}. dbug: Microsoft.AspNetCore.DataProtection.KeyManagement.DefaultKeyResolver[13] Considering key {7973feae-6c2e-433e-b462-385567895847} with expiration date 2025-11-16 04:16:46Z as default key. dbug: Microsoft.AspNetCore.DataProtection.TypeForwardingActivator[0] Forwarded activator type request from Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiXmlDecryptor, Microsoft.AspNetCore.DataProtection, Version=9.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60 to Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiXmlDecryptor, Microsoft.AspNetCore.DataProtection, Culture=neutral, PublicKeyToken=adb9793829ddae60 dbug: Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiXmlDecryptor[51] Decrypting secret element using Windows DPAPI. dbug: Microsoft.AspNetCore.DataProtection.TypeForwardingActivator[0] Forwarded activator type request from Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=9.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60 to Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Culture=neutral, PublicKeyToken=adb9793829ddae60 dbug: Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.CngCbcAuthenticatedEncryptorFactory[4] Opening CNG algorithm 'AES' from provider '(null)' with chaining mode CBC. dbug: Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.CngCbcAuthenticatedEncryptorFactory[3] Opening CNG algorithm 'SHA256' from provider '(null)' with HMAC. dbug: Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingProvider[2] Using key {7973feae-6c2e-433e-b462-385567895847} as the default key. dbug: Microsoft.AspNetCore.DataProtection.Internal.DataProtectionHostedService[65] Key ring with default key {7973feae-6c2e-433e-b462-385567895847} was loaded during application startup. dbug: Microsoft.AspNetCore.Watch.BrowserRefresh.BlazorWasmHotReloadMiddleware[0] Middleware loaded dbug: Microsoft.AspNetCore.Watch.BrowserRefresh.BrowserScriptMiddleware[0] Middleware loaded. Script /_framework/aspnetcore-browser-refresh.js (16493 B). dbug: Microsoft.AspNetCore.Watch.BrowserRefresh.BrowserScriptMiddleware[0] Middleware loaded. Script /_framework/blazor-hotreload.js (799 B). dbug: Microsoft.AspNetCore.Watch.BrowserRefresh.BrowserRefreshMiddleware[0] Middleware loaded: DOTNET_MODIFIABLE_ASSEMBLIES=debug, __ASPNETCORE_BROWSER_TOOLS=true info: Microsoft.Hosting.Lifetime[14] Now listening on: http://localhost:5080 dbug: Microsoft.AspNetCore.Hosting.Diagnostics[13] Loaded hosting startup assembly Site dbug: Microsoft.AspNetCore.Hosting.Diagnostics[13] Loaded hosting startup assembly Microsoft.WebTools.ApiEndpointDiscovery dbug: Microsoft.AspNetCore.Hosting.Diagnostics[13] Loaded hosting startup assembly Microsoft.AspNetCore.Watch.BrowserRefresh dbug: Microsoft.AspNetCore.Hosting.Diagnostics[13] Loaded hosting startup assembly Microsoft.WebTools.BrowserLink.Net info: Microsoft.Hosting.Lifetime[0] Application started. Press Ctrl+C to shut down. info: Microsoft.Hosting.Lifetime[0] Hosting environment: Development info: Microsoft.Hosting.Lifetime[0] Content root path: C:\Users\Administrator\Desktop\BaseSiteLib\Site dbug: Microsoft.Extensions.Hosting.Internal.Host[2] Hosting started 运行时打印出这些是有什么问题吗
11-15
FluentValidation.AsyncValidatorInvokedSynchronouslyException: Validator "UserRequestValidate" can't be used with ASP.NET automatic validation as it contains asynchronous rules. ASP.NET's validation pipeline is not asynchronous and can't invoke asynchronous rules. Remove the asynchronous rules in order for this validator to run. at FluentValidation.AbstractValidator`1.Validate(ValidationContext`1 context) in /_/src/FluentValidation/AbstractValidator.cs:line 207 at FluentValidation.AbstractValidator`1.FluentValidation.IValidator.Validate(IValidationContext context) in /_/src/FluentValidation/AbstractValidator.cs:line 153 at FluentValidation.AspNetCore.FluentValidationModelValidator.Validate(ModelValidationContext mvContext) in /_/src/FluentValidation.AspNetCore/FluentValidationModelValidatorProvider.cs:line 146 at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.ValidateNode() at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitComplexType(IValidationStrategy defaultStrategy) at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitImplementation(ModelMetadata& metadata, String& key, Object model) at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.Visit(ModelMetadata metadata, String key, Object model) at FluentValidation.AspNetCore.FluentValidationVisitor.<>n__1(ModelMetadata metadata, String key, Object model, Boolean alwaysValidateAtTopLevel, Object container) at FluentValidation.AspNetCore.FluentValidationVisitor.<>c__DisplayClass2_0.<Validate>g__BaseValidate|0() in /_/src/FluentValidation.AspNetCore/FluentValidationVisitor.cs:line 45 at FluentValidation.AspNetCore.FluentValidationVisitor.ValidateInternal(ModelMetadata metadata, String key, Object model, Func`1 continuation) in /_/src/FluentValidation.AspNetCore/FluentValidationVisitor.cs:line 63 at FluentValidation.AspNetCore.FluentValidationVisitor.Validate(ModelMetadata metadata, String key, Object model, Boolean alwaysValidateAtTopLevel, Object container) in /_/src/FluentValidation.AspNetCore/FluentValidationVisitor.cs:line 47 at Microsoft.AspNetCore.Mvc.ModelBinding.ObjectModelValidator.Validate(ActionContext actionContext, ValidationStateDictionary validationState, String prefix, Object model, ModelMetadata metadata, Object container) at Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder.EnforceBindRequiredAndValidate(ObjectModelValidator baseObjectValidator, ActionContext actionContext, ParameterDescriptor parameter, ModelMetadata metadata, ModelBindingContext modelBindingContext, ModelBindingResult modelBindingResult, Object container) at Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder.BindModelAsync(ActionContext actionContext, IModelBinder modelBinder, IValueProvider valueProvider, ParameterDescriptor parameter, ModelMetadata metadata, Object value, Object container) at Microsoft.AspNetCore.Mvc.Controllers.ControllerBinderDelegateProvider.<>c__DisplayClass0_0.<<CreateBinderDelegate>g__Bind|0>d.MoveNext() --- End of stack trace from previous location --- at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker) at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext) at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider) at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context) 是什么问题
05-31
计及风电并网运行的微电网及集群电动汽车综合需求侧响应的优化调度策略研究(Matlab代码实现)内容概要:本文研究了计及风电并网运行的微电网及集群电动汽车综合需求侧响应的优化调度策略,并提供了基于Matlab的代码实现。研究聚焦于在高渗透率可再生能源接入背景下,如何协调微电网内部分布式电源、储能系统与大规模电动汽车充电负荷之间的互动关系,通过引入需求侧响应机制,建立多目标优化调度模型,实现系统运行成本最小化、可再生能源消纳最大化以及电网负荷曲线的削峰填谷。文中详细阐述了风电出力不确定性处理、电动汽车集群充放电行为建模、电价型与激励型需求响应机制设计以及优化求解算法的应用。; 适合人群:具备一定电力系统基础知识和Matlab编程能力的研究生、科研人员及从事新能源、微电网、电动汽车等领域技术研发的工程师。; 使用场景及目标:①用于复现相关硕士论文研究成果,深入理解含高比例风电的微电网优化调度建模方法;②为开展电动汽车参与电网互动(V2G)、需求侧响应等课题提供仿真平台和技术参考;③适用于电力系统优化、能源互联网、综合能源系统等相关领域的教学与科研项目开发。; 阅读建议:建议读者结合文中提供的Matlab代码进行实践操作,重点关注模型构建逻辑与算法实现细节,同时可参考文档中提及的其他相关案例(如储能优化、负荷预测等),以拓宽研究视野并促进交叉创新。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值