asp.net-mvc-3 - ASP.NET mvc多态模型绑定

  显示原文与译文双语对照的内容
108 0

在更早版本的MVC之前,这个问题已经被问到了。 这个博客条目也有 ,关于解决这个问题的方法。 我想知道MVC3是否引入了任何可以能有帮助的东西,或者是否有它的他选项。

简单的说,这是情况。 我有一个抽象的基础模型和 2个具体子类。 我有一个强类型视图,用 EditorForModel() 呈现模型。 然后我有定制模板来渲染每个具体类型。

问题在发布时出现。 如果将post操作方法作为参数使用,那么MVC就不能创建 of ( 我也不想这样做我希望它能创造出具体的混凝土类型)的抽象版本。 如果我创建了多个仅由参数签名不同的post操作方法,那么MVC会抱怨它是不明确的。

就我所知,我有一些选择来解决这个问题。 我不喜欢任何原因,但我将在这里列出它们:

  • 创建自定义模型活页夹,如Darin在我链接到的第一个帖子中建议的那样。
  • 创建一个鉴别器属性作为链接的第二个帖子,建议。
  • 根据类型发布到不同的操作方法

我不喜欢 1,因为它基本上是隐藏的配置。 一些它的他开发人员在代码上工作可以能并不知道它是什么并浪费了大量的时间来改变事情。

我不喜欢 2因为它看起来有点乱。 但是,我正朝着这种方法前进。

我不喜欢 3因为这意味着违背了。

任何其他建议?

编辑:

我决定用darin的方法,但稍微改变了一下。 我把它添加到了我的抽象模型中:


[HiddenInput(DisplayValue = false)]


public string ConcreteModelType { get { return this.GetType().ToString(); }}



然后在我的DisplayForModel() 中自动生成一个隐藏。 你唯一要记住的是,如果你没有使用 DisplayForModel(),你必须自己加入。

时间: 原作者:

90 2

我显然选择选项 1 (: - ),让我尝试更详细地阐述它,使它更容易被破坏,并避免硬编码的具体实例在模型活页夹中。 它的思想是将具体类型传递到隐藏字段中,并使用反射来实例化具体类型。

假设你有以下视图模型:


public abstract class BaseViewModel


{


 public int Id { get; set; }


}



public class FooViewModel : BaseViewModel


{


 public string Foo { get; set; }


}



以下控制器:


public class HomeController : Controller


{


 public ActionResult Index()


 {


 var model = new FooViewModel { Id = 1, Foo ="foo" };


 return View(model);


 }



 [HttpPost]


 public ActionResult Index(BaseViewModel model)


 {


 return View(model);


 }


}



相应的Index 视图:


@model BaseViewModel


@using (Html.BeginForm())


{


 @Html.Hidden("ModelType", Model.GetType()) 


 @Html.EditorForModel()


 <input type="submit" value="OK"/>


}



还有 ~/Views/Home/EditorTemplates/FooViewModel.cshtml 编辑器模板:


@model FooViewModel


@Html.EditorFor(x => x.Id)


@Html.EditorFor(x => x.Foo)



现在,我们可以拥有以下定制模型活页夹:


public class BaseViewModelBinder : DefaultModelBinder


{


 protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)


 {


 var typeValue = bindingContext.ValueProvider.GetValue("ModelType");


 var type = Type.GetType(


 (string)typeValue.ConvertTo(typeof(string)),


 true


 );


 if (!typeof(BaseViewModel).IsAssignableFrom(type))


 {


 throw new InvalidOperationException("Bad Type");


 }


 var model = Activator.CreateInstance(type);


 bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, type);


 return model;


 }


}



实际类型是从 ModelType 隐藏字段的值推断出的。 它不是硬编码的,意味着稍后可以添加它的他子类型,而不必触摸这里模型活页夹。

同样的技术可以很容易地应用到基本视图模型的集合中。

原作者:
119 1

我刚刚想到了一个有趣的解决这个问题的方法。 不使用像这样的参数bsed模型绑定:


[HttpPost]


public ActionResult Index(MyModel model) {...}



我可以使用 TryUpdateModel() 来决定在代码中绑定哪种类型的模型。 例如我执行如下操作:


[HttpPost]


public ActionResult Index() {...}


{


 MyModel model;


 if (ViewData.SomeData == Something) {


 model = new MyDerivedModel();


 } else {


 model = new MyOtherDerivedModel();


 }



 TryUpdateModel(model);



 if (Model.IsValid) {...}



 return View(model);


}



不管我如何处理,这个模型都可以用它来计算,或者使用 is 来计算正确的映射,或者使用来计算出正确的映射。

我猜那些从今天从 1开始就没有使用MVC的人忘记了 UpdateModelTryUpdateModel,但它仍然有它的用途。

原作者:
95 0

我花了一个很好的时间来解答一个相关的问题。

对于我来说,我有一个抽象基类型,用于许多不同的视图模型类型。 在主视图模型中,我有一个抽象基类型的属性:


class View


{


 public AbstractBaseItemView ItemView { get; set; }


}



我有许多子类型 AbstractBaseItemView,其中很多都定义了自己的专用属性。

但是model绑定到itemview的属性类型,而忽略特定于正在使用的AbstractBaseItemView的属性的声明属性类型,这只是为了查看。

解决这个问题的方法并不完美:


using System.ComponentModel;


using System.ComponentModel.DataAnnotations;



//...



public class ModelBinder : DefaultModelBinder


{


//...



 override protected ICustomTypeDescriptor GetTypeDescriptor(ControllerContext controllerContext, ModelBindingContext bindingContext)


 {


 if (bindingContext.ModelType.IsAbstract && bindingContext.Model!= null)


 {


 var concreteType = bindingContext.Model.GetType();



 if (Nullable.GetUnderlyingType(concreteType) == null)


 {


 return new AssociatedMetadataTypeTypeDescriptionProvider(concreteType).GetTypeDescriptor(concreteType);


 }


 }



 return base.GetTypeDescriptor(controllerContext, bindingContext);


 }



//...


}



这个改动看起来很复杂,而且非常简单,似乎可以工作,但不需要,因为我认为它可以带来相当大的安全风险,因为它不可能让模型绑定器产生任何安全风险。

函数也仅在声明的属性类型是抽象类型,比如 an抽象类或者接口时才有效。

有关 related related在我这里看到的其他实现override可能 override CreateModel(),当发布全新对象,并且遇到same问题i 当声明的属性类型是抽象类型时,。 因此,你很可能无法在现有的模型对象上具体类型的具体特性,但是只创建新的属性。

换句话说,你可以能需要将这里工作集成到活页夹中,以便能够在绑定前正确编辑对象。 我认为是一种更安全的方法,因为我控制了什么具体类型,因这里控制器/动作可以间接地指定具体的类型。

我希望这对其他人有帮助。

原作者:
67 0

使用on的方法区分你的模型类型,我建议你使用自定义 RouteHandler 区分你的控制器的唯一命名操作。 例如如果你在控制器中有两个具体模型Foo和 Bar,请在控制器中进行 Create 操作,然后执行 CreateFoo(Foo model) 操作和 CreateBar(Bar model) 操作。 然后,创建一个自定义 RouteHandler,如下所示:


public class MyRouteHandler : IRouteHandler


{


 public IHttpHandler GetHttpHandler(RequestContext requestContext)


 {


 var httpContext = requestContext.HttpContext;


 var modelType = httpContext.Request.Form["ModelType"]; 


 var routeData = requestContext.RouteData;


 if (!String.IsNullOrEmpty(modelType))


 {


 var action = routeData.Values["action"];


 routeData.Values["action"] = action + modelType;


 }


 var handler = new MvcHandler(requestContext);


 return handler; 


 }


}



然后,在 Global.asax. cs中,按如下方式更改 RegisterRoutes():


public static void RegisterRoutes(RouteCollection routes) 


{ 


 routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 



 AreaRegistration.RegisterAllAreas(); 



 routes.Add("Default", new Route("{controller}/{action}/{id}", 


 new RouteValueDictionary( 


 new { controller ="Home", 


 action ="Index", 


 id = UrlParameter.Optional }), 


 new MyRouteHandler())); 


} 



然后,如果在返回的表单中定义了一个 ModelType,RouteHandler将将ModelType追加到操作 NAME,允许每个具体模型定义。

...