如何验证我的自定义模型绑定模式? [英] How to validate my model in a custom model binder?

查看:141
本文介绍了如何验证我的自定义模型绑定模式?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我问一个问题,我有一个逗号分隔的数值<一个href=\"http://stackoverflow.com/questions/23098059/comma-delimited-numeric-value-fails-validation-in-asp-net-mvc-4\">here.

由于一些答复,我试图努力实现自己的模型绑定如下:

命名空间MvcApplication1.Core
{
    公共类PropertyModelBinder:DefaultModelBinder
    {
        公众覆盖对象BindModel(ControllerContext controllerContext,ModelBindingContext的BindingContext)
        {
            反对objectModel =新的对象();            如果(bindingContext.ModelType == typeof运算(PropertyModel))
            {
                HTT prequestBase请求= controllerContext.HttpContext.Request;
                串价格= request.Form.Get(价格)更换(的String.Empty)。                ModelBindingContext newBindingContext =新ModelBindingContext()
                {
                    ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(
                        ()=&GT;新PropertyModel()
                        {
                            价格= Convert.ToInt32(价格)
                        },
                        typeof运算(PropertyModel)
                    )
                    的ModelState = bindingContext.ModelState,
                    ValueProvider = bindingContext.ValueProvider
                };                //调用默认的模型绑定这一新的绑定上下文
                返回base.BindModel(controllerContext,newBindingContext);
            }
            其他
            {
                返回base.BindModel(controllerContext,BindingContext中);
            }
        }        //保护覆盖对象CreateModel(ControllerContext controllerContext,ModelBindingContext的BindingContext,类型modelType)
        // {
        // //返回base.CreateModel(controllerContext,BindingContext中,modelType);
        // PropertyModel模式=新PropertyModel();        //如果(modelType == typeof运算(PropertyModel))
        // {
        //模型=(PropertyModel)base.CreateModel(controllerContext,BindingContext中,modelType);
        // Htt的prequestBase请求= controllerContext.HttpContext.Request;
        //串价格= request.Form.Get(价格)更换(的String.Empty)。
        // model.Price = Convert.ToInt32(价);
        //}        //回归模型;
        //}
    }
}

和更新我的控制器类这样的:

命名空间MvcApplication1.Controllers
{
    公共类PropertyController:控制器
    {
        公众的ActionResult编辑()
        {
            PropertyModel模式=新PropertyModel
            {
                AGENTNAME =李四,
                BuildingStyle =殖民地
                BuiltYear = 1978年,
                价格= 650000,
                ID = 1
            };            返回查看(模型);
        }        [HttpPost]
        公众的ActionResult编辑([ModelBinder的(typeof运算(PropertyModelBinder))] PropertyModel模型)
        {
            如果(ModelState.IsValid)
            {
                //保存属性信息。
            }            返回查看(模型);
        }        公众的ActionResult关于()
        {
            ViewBag.Message =你的应用描述页面。            返回查看();
        }        公众的ActionResult联系()
        {
            ViewBag.Message =您的联系页面。            返回查看();
        }
    }
}

现在,如果我输入的价格用逗号,我的自定义模型粘结剂将删除逗号,这就是我想要的,但仍然验证失败。所以,问题是:如何做自定义的验证在我的自定义模型绑定,这样可避免用​​逗号捕获的价格值吗?换句话说,我怀疑我需要做更多的在我的自定义模型粘结剂,但不知道如何和什么。谢谢你。

更新:

所以,我试图@马的解决方案在 http://stackoverflow.com/a/2592430/97109 和更新我的模型绑定如下:

命名空间MvcApplication1.Core
{
    公共类PropertyModelBinder:DefaultModelBinder
    {
        公众覆盖对象BindModel(ControllerContext controllerContext,ModelBindingContext的BindingContext)
        {
            反对objectModel =新的对象();            如果(bindingContext.ModelType == typeof运算(PropertyModel))
            {
                HTT prequestBase请求= controllerContext.HttpContext.Request;
                串价格= request.Form.Get(价格)更换(的String.Empty)。                ModelBindingContext newBindingContext =新ModelBindingContext()
                {
                    ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(
                        ()=&GT;新PropertyModel()
                        {
                            价格= Convert.ToInt32(价格)
                        },
                        typeof运算(PropertyModel)
                    )
                    的ModelState = bindingContext.ModelState,
                    ValueProvider = bindingContext.ValueProvider
                };                //调用默认的模型绑定这一新的绑定上下文
                对象o = base.BindModel(controllerContext,newBindingContext);
                newBindingContext.ModelState.Remove(价格);
                newBindingContext.ModelState.Add(价格,新的ModelState());
                newBindingContext.ModelState.SetModelValue(价格,新ValueProviderResult(价格,价格,NULL));
                返回O;
            }
            其他
            {
                返回base.BindModel(controllerContext,BindingContext中);
            }
        }        //保护覆盖对象CreateModel(ControllerContext controllerContext,ModelBindingContext的BindingContext,类型modelType)
        // {
        // //返回base.CreateModel(controllerContext,BindingContext中,modelType);
        // PropertyModel模式=新PropertyModel();        //如果(modelType == typeof运算(PropertyModel))
        // {
        //模型=(PropertyModel)base.CreateModel(controllerContext,BindingContext中,modelType);
        // Htt的prequestBase请求= controllerContext.HttpContext.Request;
        //串价格= request.Form.Get(价格)更换(的String.Empty)。
        // model.Price = Convert.ToInt32(价);
        //}        //回归模型;
        //}
    }
}

据八九不离十工作,但如果我输入0价格,型号回来是有效的,这是错误的,因为我有一个范围注释它说,最低价格是1。在我无计可施。

更新:

为了用复合类型来测试自定义模型粘合剂。我创建了以下视图模型类:


使用System.ComponentModel.DataAnnotations;命名空间MvcApplication1.Models
{
    公共类PropertyRegistrationViewModel
    {
        公共PropertyRegistrationViewModel()
        {        }        公共属性属性{搞定;组; }
        公共代理代理{搞定;组; }
    }    公共类属性
    {
        公众诠释HouseNumber {搞定;组; }
        公共字符串街{搞定;组; }
        公共字符串城{搞定;组; }
        公共字符串状态{搞定;组; }
        公共字符串邮编{搞定;组; }        [必需(的ErrorMessage =你必须输入价格。)
        [范围(1000,10000000的ErrorMessage =坏的价格。)]
        公众诠释价格{搞定;组; }
    }    公共类代理
    {
        公共字符串名字{获得;组; }
        公共字符串名字{获得;组; }
        [必需(的ErrorMessage =你必须输入您的年销售额。)]
        [范围(10000,5000000的ErrorMessage =坏的范围。)
        公众诠释AnnualSales {搞定;组; }        公共地址地址{搞定;组; }
    }    公共类地址
    {
        公共字符串1号线{搞定;组; }
        公共字符串2号线{搞定;组; }
    }
}

而这里是控制器:


使用MvcApplication1.Core;
使用MvcApplication1.Models;
使用System.Web.Mvc;命名空间MvcApplication1.Controllers {
    公共类这个RegistrationController:控制器
    {
        公众的ActionResult指数(){
            PropertyRegistrationViewModel视图模型=新PropertyRegistrationViewModel();
            返回视图(视图模型);
        }        [HttpPost]
        公众的ActionResult指数([ModelBinder的(typeof运算(PropertyRegistrationModelBinder))] PropertyRegistrationViewModel视图模型)
        {
            如果(ModelState.IsValid)
            {
                //保存注册。
            }            返回视图(视图模型);
        }
    }
}

下面是自定义模型绑定实现:


使用MvcApplication1.Models;
使用系统;
使用System.Collections.Generic;
使用System.Linq的;
使用的System.Web;
使用System.Web.Mvc;命名空间MvcApplication1.Core
{
    公共类PropertyRegistrationModelBinder:DefaultModelBinder
    {
        保护覆盖对象GetPropertyValue(
            ControllerContext controllerContext,
            ModelBindingContext BindingContext中,
            System.ComponentModel.PropertyDescriptor的PropertyDescriptor,
            IModelBinder propertyBinder)
        {
            如果(propertyDescriptor.ComponentType == typeof运算(PropertyRegistrationViewModel))
            {
                如果(propertyDescriptor.Name ==物业)
                {
                    变种价格= bindingContext.ValueProvider.GetValue(Property.Price)AttemptedValue.Replace(,,的String.Empty)。
                    VAR属性=新的属性();                    //问题1:价格是我要修改的唯一属性。有什么办法
                    //这样我就不必手动填充属性的其余部分,像这样?
                    property.Price = string.IsNullOrWhiteSpace(价格)? 0:Convert.ToInt32(价);
                    property.HouseNumber = Convert.ToInt32(bindingContext.ValueProvider.GetValue(Property.HouseNumber)AttemptedValue);
                    property.Street = bindingContext.ValueProvider.GetValue(Property.Street)AttemptedValue。
                    property.City = bindingContext.ValueProvider.GetValue(Property.City)AttemptedValue。
                    property.State = bindingContext.ValueProvider.GetValue(Property.State)AttemptedValue。
                    property.Zip = bindingContext.ValueProvider.GetValue(Property.Zip)AttemptedValue。                    //我认为,当这个属性对象的回报,我们的价格属性的注解
                    //将模型绑定兑现,但它并没有相应的验证。
                    返回财产;
                }                如果(propertyDescriptor.Name ==代理)
                {
                    变种销售= bindingContext.ValueProvider.GetValue(Agent.AnnualSales)AttemptedValue.Replace(,,的String.Empty)。
                    VAR剂=新代理();                    //问题2:AnnualSales是我需要验证之前处理的唯一属性,
                    //有没有什么办法可以避开繁琐填充属性的休息吗?
                    agent.AnnualSales = string.IsNullOrWhiteSpace(销售)? 0:Convert.ToInt32(销售);
                    agent.FirstName = bindingContext.ValueProvider.GetValue(Agent.FirstName)AttemptedValue。
                    agent.LastName = bindingContext.ValueProvider.GetValue(Agent.LastName)AttemptedValue。                    VAR地址=新地址();
                    address.Line1 = bindingContext.ValueProvider.GetValue(Agent.Address.Line1)AttemptedValue +中华民国。
                    address.Line2 = bindingContext.ValueProvider.GetValue(Agent.Address.Line2)AttemptedValue +MD。
                    agent.Address =地址;                    //我认为,当这个代理对象的回报,我们的AnnualSales财产的注解
                    //将模型绑定兑现,但它并没有相应的验证。
                    返回剂;
                }
            }
            返回base.GetPropertyValue(controllerContext,BindingContext中,PropertyDescriptor的,propertyBinder);
        }        保护覆盖无效OnModelUpdated(ControllerContext controllerContext,ModelBindingContext的BindingContext)
        {
            VAR模型= bindingContext.Model为PropertyRegistrationViewModel;
            //为了验证我们的模型,我们似乎将此处手动验证。
            base.OnModelUpdated(controllerContext,BindingContext中);
        }
    }
}

这里是的Razor视图:

@model MvcApplication1.Models.PropertyRegistrationViewModel
@ {
    ViewBag.Title =产权登记;
}

&LT; H2&GT;产权登记和LT; / H&GT;
&LT; P&GT;在下面输入您的财产和代理信息&LT; / P&GT;

@using(Html.BeginForm(指数,登记))
{
    @ Html.ValidationSummary();结果
    &LT; H4&GT;物业信息&LT; / H4&GT;
    &LT;文字和GT;门牌号LT; /文字和GT; @ Html.TextBoxFor(M = GT; m.Property.HouseNumber)LT; BR /&GT;
    &LT;文字和GT;街道&LT; /文字和GT; @ Html.TextBoxFor(M = GT; m.Property.Street)LT; BR /&GT;
    &LT;文字和GT;城市&LT; /文字和GT; @ Html.TextBoxFor(M = GT; m.Property.City)LT; BR /&GT;
    &LT;文字和GT;州及LT; /文字和GT; @ Html.TextBoxFor(M = GT; m.Property.State)LT; BR /&GT;
    &LT;文字和GT;邮编及LT; /文字和GT; @ Html.TextBoxFor(M = GT; m.Property.Zip)LT; BR /&GT;
    &LT;文字和GT;价格&LT; /文字和GT; @ Html.TextBoxFor(M = GT; m.Property.Price)LT; BR /&GT;
    &LT; H4&GT;代理信息&LT; / H4&GT;
    &LT;文本&gt;首先名称和LT; /文字和GT; @ Html.TextBoxFor(M = GT; m.Agent.FirstName)LT; BR /&GT;
    &LT;文字和GT;姓&LT; /文字和GT; @ Html.TextBoxFor(M = GT; m.Agent.LastName)LT; BR /&GT;
    &LT;文字和GT;年销售LT; /文字和GT; @ Html.TextBoxFor(M = GT; m.Agent.AnnualSales)LT; BR /&GT;
    &LT;文字和GT;代理地址L1&LT; /文字和GT; @ Html.TextBoxFor(M = GT; m.Agent.Address.Line1)LT; BR /&GT;
    &LT;文字和GT;代理地址L2&LT; /文字和GT; @ Html.TextBoxFor(M = GT; m.Agent.Address.Line2)LT; BR /&GT;
    &LT;输入类型=提交值=提交名称=提交/&GT;
}

这里是Global.asax文件,我连线了自定义模型粘合剂。顺便说一句,似乎是不需要这一步,怎么我注意到它仍然工作没有这一步。


使用MvcApplication1.Core;
使用MvcApplication1.Models;
使用System.Web.Http;
使用System.Web.Mvc;
使用System.Web.Optimization;
使用System.Web.Routing;命名空间MvcApplication1 {
    //注意:有关启用IIS6 IIS7或经典模式的指令,
    //参观http://go.microsoft.com/?LinkId=9394801    公共类MvcApplication:System.Web.HttpApplication {
        保护无效的Application_Start(){
            AreaRegistration.RegisterAllAreas();            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
            AuthConfig.RegisterAuth();
            ModelBinders.Binders.Add(typeof运算(PropertyRegistrationViewModel),新PropertyRegistrationModelBinder());
        }
    }
}

也许我做错了什么还是远远不够的。我注意到以下几个问题:



  1. 虽然我只需要修改的属性对象的价格值,看来我要不厌其烦地填充模型绑定的所有其他属性。我要为代理财产的AnnualSales财产做同样的。有反正这可以在模型绑定避免?


  2. 我原以为默认BindModel方法将履行我们的对象的属性的注释,并相应地对其进行验证调用GetPropertyValue后,但事实并非如此。如果我输入一些价值的出路范围价格Property对象或代理对象的AnnualSales,型号回来为有效。换句话说,该范围说明被忽略。我知道我可以在自定义模型粘合剂覆盖OnModelUpdated对其进行验证,但太多的工作,再加上,我在的地方注释,为什么不模型绑定的默认实现尊敬他们,只是因为我重写部分呢?

@dotnetstep:你能抛出一些见解呢?谢谢你。


解决方案

  [HttpPost]
    公众的ActionResult编辑([ModelBinder的(typeof运算(PropertyModelBinder))] PropertyModel模型)
    {
        ModelState.Clear();
        TryValidateModel(模型);
        如果(ModelState.IsValid)
        {
            //保存属性信息。
        }        返回查看(模型);
    }

希望这将有助于。

您也可以尝试@Ryan解决方案,以及

这可能是您的自定义模型绑定器。 (在这种情况下,你并不需要更新您的编辑操作结果正如我上面提出的建议)

 公共类PropertyModelBinder:DefaultModelBinder
{    保护覆盖对象GetPropertyValue(ControllerContext controllerContext,ModelBindingContext的BindingContext,System.ComponentModel.PropertyDescriptor的PropertyDescriptor,IModelBinder propertyBinder)
    {
        如果(propertyDescriptor.ComponentType == typeof运算(PropertyModel))
        {
            如果(propertyDescriptor.Name ==价格)
            {
                VAR OBJ = bindingContext.ValueProvider.GetValue(价格);
                返回Convert.ToInt32(obj.AttemptedValue.ToString()替换(,,));
            }
        }
        返回base.GetPropertyValue(controllerContext,BindingContext中,PropertyDescriptor的,propertyBinder);
    }
}

当你已经更新了你的范围为绑定。我在发表了我的建议。此外,如果你使用ModelBinder的物业和代理比你能做到这样。

  //在Global.asax中
ModelBinders.Binders.Add(typeof运算(属性),新PropertyRegistrationModelBinder());
ModelBinders.Binders.Add(typeof运算(代理),新PropertyRegistrationModelBinder());//更新ModelBinder的这个样子。 公共类PropertyRegistrationModelBinder:DefaultModelBinder
{
    保护覆盖对象GetPropertyValue(ControllerContext controllerContext,ModelBindingContext的BindingContext,System.ComponentModel.PropertyDescriptor的PropertyDescriptor,IModelBinder propertyBinder)
    {
        如果(propertyDescriptor.ComponentType == typeof运算(物业)|| propertyDescriptor.ComponentType == typeof运算(代理))
        {
            如果(propertyDescriptor.Name ==价格|| propertyDescriptor.Name ==AnnualSales)
            {
                VAR值= bindingContext.ValueProvider.GetValue(bindingContext.ModelName).AttemptedValue.Replace(的String.Empty);
                返回string.IsNullOrEmpty(价值)? 0:Convert.ToInt32(值);
            }
        }
        返回base.GetPropertyValue(controllerContext,BindingContext中,PropertyDescriptor的,propertyBinder);
    }
}

此外,我想说,你能找到与此相关的许多信息,你也可以做同样的事情很多方面。就像如果你介绍了适用于类属性的绑定同样的方式新的属性你类级别应用ModelBinder的。

I asked about an issue I have with comma delimited numeric values here.

Given some of the replies, I attempted to try to implement my own model binder as follows:

namespace MvcApplication1.Core
{
    public class PropertyModelBinder : DefaultModelBinder
    {
        public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            object objectModel = new object();

            if (bindingContext.ModelType == typeof(PropertyModel))
            {
                HttpRequestBase request = controllerContext.HttpContext.Request;
                string price = request.Form.Get("Price").Replace(",", string.Empty);

                ModelBindingContext newBindingContext = new ModelBindingContext()
                {
                    ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(
                        () => new PropertyModel() 
                        {
                            Price = Convert.ToInt32(price)
                        },
                        typeof(PropertyModel)       
                    ),
                    ModelState = bindingContext.ModelState,
                    ValueProvider = bindingContext.ValueProvider
                };

                // call the default model binder this new binding context
                return base.BindModel(controllerContext, newBindingContext);
            }
            else
            {
                return base.BindModel(controllerContext, bindingContext);
            }
        }

        //protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
        //{
        //    //return base.CreateModel(controllerContext, bindingContext, modelType);
        //    PropertyModel model = new PropertyModel();

        //    if (modelType == typeof(PropertyModel))
        //    {
        //        model = (PropertyModel)base.CreateModel(controllerContext, bindingContext, modelType);
        //        HttpRequestBase request = controllerContext.HttpContext.Request;
        //        string price = request.Form.Get("Price").Replace(",", string.Empty);
        //        model.Price = Convert.ToInt32(price);
        //    }

        //    return model;
        //}
    }
}

And updated my controller class as this:

namespace MvcApplication1.Controllers
{
    public class PropertyController : Controller
    {
        public ActionResult Edit()
        {
            PropertyModel model = new PropertyModel
            {
                AgentName = "John Doe",
                BuildingStyle = "Colonial",
                BuiltYear = 1978,
                Price = 650000,
                Id = 1
            };

            return View(model);
        }

        [HttpPost]
        public ActionResult Edit([ModelBinder(typeof(PropertyModelBinder))] PropertyModel model)
        {
            if (ModelState.IsValid)
            {
                //Save property info.              
            }

            return View(model);
        }

        public ActionResult About()
        {
            ViewBag.Message = "Your app description page.";

            return View();
        }

        public ActionResult Contact()
        {
            ViewBag.Message = "Your contact page.";

            return View();
        }
    }
}

Now, if I enter the price with commas, my custom model binder will remove the commas, that's what I want, but validation still fails. So, question is: How to do custom validation in my custom model binder such that the captured price value with commas can be avoided? In other words, I suspect that I need to do more in my custom model binder, but don't know how and what. Thanks.

Update:

So, I tried @mare's solution at http://stackoverflow.com/a/2592430/97109 and updated my model binder as follows:

namespace MvcApplication1.Core
{
    public class PropertyModelBinder : DefaultModelBinder
    {
        public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            object objectModel = new object();

            if (bindingContext.ModelType == typeof(PropertyModel))
            {
                HttpRequestBase request = controllerContext.HttpContext.Request;
                string price = request.Form.Get("Price").Replace(",", string.Empty);

                ModelBindingContext newBindingContext = new ModelBindingContext()
                {
                    ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(
                        () => new PropertyModel() 
                        {
                            Price = Convert.ToInt32(price)
                        },
                        typeof(PropertyModel)    
                    ),
                    ModelState = bindingContext.ModelState,
                    ValueProvider = bindingContext.ValueProvider
                };

                // call the default model binder this new binding context
                object o = base.BindModel(controllerContext, newBindingContext);
                newBindingContext.ModelState.Remove("Price");
                newBindingContext.ModelState.Add("Price", new ModelState());
                newBindingContext.ModelState.SetModelValue("Price", new ValueProviderResult(price, price, null));
                return o;
            }
            else
            {
                return base.BindModel(controllerContext, bindingContext);
            }
        }

        //protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
        //{
        //    //return base.CreateModel(controllerContext, bindingContext, modelType);
        //    PropertyModel model = new PropertyModel();

        //    if (modelType == typeof(PropertyModel))
        //    {
        //        model = (PropertyModel)base.CreateModel(controllerContext, bindingContext, modelType);
        //        HttpRequestBase request = controllerContext.HttpContext.Request;
        //        string price = request.Form.Get("Price").Replace(",", string.Empty);
        //        model.Price = Convert.ToInt32(price);
        //    }

        //    return model;
        //}
    }
}

It sorta works, but if I enter 0 for price, the model comes back as valid, which is wrong because I have a Range annotation which says that the minimum price is 1. At my wit's end.

Update:

In order to test out a custom model binder with composite types. I've created the following view model classes:

using System.ComponentModel.DataAnnotations;

namespace MvcApplication1.Models
{
    public class PropertyRegistrationViewModel
    {
        public PropertyRegistrationViewModel()
        {

        }

        public Property Property { get; set; }
        public Agent Agent { get; set; }
    }

    public class Property
    {
        public int HouseNumber { get; set; }
        public string Street { get; set; }
        public string City { get; set; }
        public string State { get; set; }
        public string Zip { get; set; }

        [Required(ErrorMessage="You must enter the price.")]
        [Range(1000, 10000000, ErrorMessage="Bad price.")]
        public int Price { get; set; }
    }

    public class Agent
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        [Required(ErrorMessage="You must enter your annual sales.")]
        [Range(10000, 5000000, ErrorMessage="Bad range.")]
        public int AnnualSales { get; set; }

        public Address Address { get; set; }
    }

    public class Address
    {
        public string Line1 { get; set; }
        public string Line2 { get; set; }
    }
}

And here is the controller:

using MvcApplication1.Core;
using MvcApplication1.Models;
using System.Web.Mvc;

namespace MvcApplication1.Controllers {
    public class RegistrationController : Controller
    {
        public ActionResult Index() {
            PropertyRegistrationViewModel viewModel = new PropertyRegistrationViewModel();
            return View(viewModel);
        }

        [HttpPost]
        public ActionResult Index([ModelBinder(typeof(PropertyRegistrationModelBinder))]PropertyRegistrationViewModel viewModel)
        {
            if (ModelState.IsValid)
            {
                //save registration.
            }

            return View(viewModel);
        }
    }
}

Here is the custom model binder implementation:

using MvcApplication1.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace MvcApplication1.Core
{
    public class PropertyRegistrationModelBinder : DefaultModelBinder
    {
        protected override object GetPropertyValue(
            ControllerContext controllerContext,
            ModelBindingContext bindingContext,
            System.ComponentModel.PropertyDescriptor propertyDescriptor,
            IModelBinder propertyBinder)
        {
            if (propertyDescriptor.ComponentType == typeof(PropertyRegistrationViewModel))
            {
                if (propertyDescriptor.Name == "Property")
                {  
                    var price = bindingContext.ValueProvider.GetValue("Property.Price").AttemptedValue.Replace(",", string.Empty);
                    var property = new Property();

                    // Question 1: Price is the only property I want to modify. Is there any way 
                    // such that I don't have to manually populate the rest of the properties like so?
                    property.Price = string.IsNullOrWhiteSpace(price)? 0: Convert.ToInt32(price);
                    property.HouseNumber = Convert.ToInt32(bindingContext.ValueProvider.GetValue("Property.HouseNumber").AttemptedValue);
                    property.Street = bindingContext.ValueProvider.GetValue("Property.Street").AttemptedValue;
                    property.City = bindingContext.ValueProvider.GetValue("Property.City").AttemptedValue;
                    property.State = bindingContext.ValueProvider.GetValue("Property.State").AttemptedValue;
                    property.Zip = bindingContext.ValueProvider.GetValue("Property.Zip").AttemptedValue;

                    // I had thought that when this property object returns, our annotation of the Price property
                    // will be honored by the model binder, but it doesn't validate it accordingly.
                    return property;
                }

                if (propertyDescriptor.Name == "Agent")
                {
                    var sales = bindingContext.ValueProvider.GetValue("Agent.AnnualSales").AttemptedValue.Replace(",", string.Empty);
                    var agent = new Agent();

                    // Question 2: AnnualSales is the only property I need to process before validation,
                    // Is there any way I can avoid tediously populating the rest of the properties?
                    agent.AnnualSales = string.IsNullOrWhiteSpace(sales)? 0:  Convert.ToInt32(sales);
                    agent.FirstName = bindingContext.ValueProvider.GetValue("Agent.FirstName").AttemptedValue;
                    agent.LastName = bindingContext.ValueProvider.GetValue("Agent.LastName").AttemptedValue;

                    var address = new Address();
                    address.Line1 = bindingContext.ValueProvider.GetValue("Agent.Address.Line1").AttemptedValue + " ROC";
                    address.Line2 = bindingContext.ValueProvider.GetValue("Agent.Address.Line2").AttemptedValue + " MD";
                    agent.Address = address;

                    // I had thought that when this agent object returns, our annotation of the AnnualSales property
                    // will be honored by the model binder, but it doesn't validate it accordingly.
                    return agent;
                }
            }
            return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
        }

        protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            var model = bindingContext.Model as PropertyRegistrationViewModel;
            //In order to validate our model, it seems that we will have to manually validate it here. 
            base.OnModelUpdated(controllerContext, bindingContext);
        }
    }
}


And here is the Razor view:

@model MvcApplication1.Models.PropertyRegistrationViewModel @{ ViewBag.Title = "Property Registration"; }

<h2>Property Registration</h2> <p>Enter your property and agent information below.</p>

@using (Html.BeginForm("Index", "Registration")) { @Html.ValidationSummary();
<h4>Property Info</h4> <text>House Number</text> @Html.TextBoxFor(m => m.Property.HouseNumber)<br /> <text>Street</text> @Html.TextBoxFor(m => m.Property.Street)<br /> <text>City</text> @Html.TextBoxFor(m => m.Property.City)<br /> <text>State</text> @Html.TextBoxFor(m => m.Property.State)<br /> <text>Zip</text> @Html.TextBoxFor(m => m.Property.Zip)<br /> <text>Price</text> @Html.TextBoxFor(m => m.Property.Price)<br /> <h4>Agent Info</h4> <text>First Name</text> @Html.TextBoxFor(m => m.Agent.FirstName)<br /> <text>Last Name</text> @Html.TextBoxFor(m => m.Agent.LastName)<br /> <text>Annual Sales</text> @Html.TextBoxFor(m => m.Agent.AnnualSales)<br /> <text>Agent Address L1</text>@Html.TextBoxFor(m => m.Agent.Address.Line1)<br /> <text>Agent Address L2</text>@Html.TextBoxFor(m => m.Agent.Address.Line2)<br /> <input type="submit" value="Submit" name="submit" /> }

And here is the global.asax file where I wire up the custom model binder. BTW, it seems this step isn't needed, coz I notice it still works without this step.

using MvcApplication1.Core;
using MvcApplication1.Models;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;

namespace MvcApplication1 {
    // Note: For instructions on enabling IIS6 or IIS7 classic mode, 
    // visit http://go.microsoft.com/?LinkId=9394801

    public class MvcApplication : System.Web.HttpApplication {
        protected void Application_Start() {
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
            AuthConfig.RegisterAuth();
            ModelBinders.Binders.Add(typeof(PropertyRegistrationViewModel), new PropertyRegistrationModelBinder());
        }
    }
}

Maybe I am doing something wrong or not enough. I've noticed the following problems:

  1. Although I only need to modify the Price value of the property object, it seems I have to tediously populate all the other properties in the model binder. I have to do the same for the AnnualSales property of the agent property. Is there anyway this can be avoided in the model binder?
  2. I had thought that the default BindModel method will honor our annotation of our objects' properties and validate them accordingly after it calls GetPropertyValue, but it doesn't. If I enter some value way out of range for Price of the Property object or the AnnualSales of the Agent object, the model comes back as valid. In other words, the Range annotations are ignored. I know I can validate them by overriding OnModelUpdated in the custom model binder, but that's too much work, and plus, I have the annotations in place, why doesn't the default implementation of the model binder honor them just because I am overriding part of it?

@dotnetstep: Could you throw some insights into this? Thank you.

解决方案

    [HttpPost]
    public ActionResult Edit([ModelBinder(typeof(PropertyModelBinder))]PropertyModel model)
    {
        ModelState.Clear();
        TryValidateModel(model);
        if (ModelState.IsValid)
        {
            //Save property info.              
        }

        return View(model);
    }

Hope This will Help.

Also you can try @Ryan solution as well.

This could be your Custom ModelBinder. ( In this case you don't need to update your Edit Action Result As I suggested above)

public class PropertyModelBinder : DefaultModelBinder
{     

    protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder)
    {
        if(propertyDescriptor.ComponentType == typeof(PropertyModel))
        {
            if (propertyDescriptor.Name == "Price")
            {
                var obj=   bindingContext.ValueProvider.GetValue("Price");
                return Convert.ToInt32(obj.AttemptedValue.ToString().Replace(",", ""));
            }
        }
        return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
    }       
}

As you have updated your scope for binding. I have provided my suggestion in comments. Also if you use ModelBinder for Property and Agent than you can do like this.

//In Global.asax
ModelBinders.Binders.Add(typeof(Property), new PropertyRegistrationModelBinder());
ModelBinders.Binders.Add(typeof(Agent), new PropertyRegistrationModelBinder());

//Updated ModelBinder look like this.

 public class PropertyRegistrationModelBinder : DefaultModelBinder
{
    protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder)
    {
        if (propertyDescriptor.ComponentType == typeof(Property) || propertyDescriptor.ComponentType == typeof(Agent))
        {
            if(propertyDescriptor.Name == "Price" || propertyDescriptor.Name == "AnnualSales")
            {                    
                var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).AttemptedValue.Replace(",", string.Empty);
                return string.IsNullOrEmpty(value) ? 0 : Convert.ToInt32(value);
            }
        }            
        return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
    }
} 

Also I would like to say that you are able to find many information related to this and also you can do same thing many ways. Like if you introduce new attribute that apply to class property for binding same way you apply ModelBinder at class level.

这篇关于如何验证我的自定义模型绑定模式?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆