Asp.net mvc 3 MVC3编辑器用于动态属性(或需要解决的问题)

Asp.net mvc 3 MVC3编辑器用于动态属性(或需要解决的问题),asp.net-mvc-3,dynamic,editorfor,Asp.net Mvc 3,Dynamic,Editorfor,我正在建立一个系统,提出问题并得到答案。每个问题都可以有自己类型的答案。现在我们将其限制为String和DateTime。在域中,问题以以下方式表示: public class Question { public int Id { get; set; } public string Caption { get; set; } public AnswerType {

我正在建立一个系统,提出问题并得到答案。每个问题都可以有自己类型的答案。现在我们将其限制为
String
DateTime
。在域中,问题以以下方式表示:

public class Question
{
    public int Id
    {
        get;
        set;
    }

    public string Caption
    {
        get;
        set;
    }

    public AnswerType
    {
        get;
        set;
    }
}
,其中
AnswerType

enum AnswerType
{
    String,
    DateTime
}
请注意,实际上我有更多的答案类型

我提出了一个想法,创建一个MVC模型,从问题中派生并向其添加答案属性。所以它必须是这样的:

public class QuestionWithAnswer<TAnswer> : Question
{
    public TAnswer Answer
    {
        get;
        set;
    }
}
enum AnswerType
{
    String,
    DateTime
}
公共课堂问题及答案:问题
{
公开回答
{
得到;
设置
}
}
这里是问题的开始。我想有一个通用的观点来引出任何问题,所以它必须是这样的:

@model QuestionWithAnswer<dynamic>

<span>@Model.Caption</span>
@Html.EditorFor(m => m.Answer)
@带答案的模型问题
@模型.标题
@EditorFor(m=>m.Answer)
对于
String
我希望这里有简单的输入,对于
DateTime
我将定义我自己的视图。我可以从控制器传递具体模型。但问题是,在呈现阶段,它自然无法确定答案的类型,特别是当它最初为
null
(默认为
String
),因此
EditorFor
不会为
String
DateTime
中所有属性的输入绘制任何内容


我确实理解这个问题的本质,但是有什么优雅的解决方法吗?或者,我必须实现自己的逻辑,根据控件类型选择编辑器视图名称(big-ught
switch
)?

您仍然可以使用Html.EditorFor(..),但指定第二个参数,即编辑器模板的名称。您在问题对象上有一个属性,即AnswerType,因此您可以执行以下操作

@Html.EditorFor(m => m.Answer, @Model.AnswerType)
EditorTemplates文件夹中的仅为每个应答类型定义一个视图。例如“字符串”、“日期时间”等


编辑:就字符串的应答对象为null而言,我会在那里放置一个占位符对象,以便“字符串”编辑器模板中的模型不为null。

我个人不喜欢这样:

public class QuestionWithAnswer<TAnswer> : Question
{
    public TAnswer Answer
    {
        get;
        set;
    }
}
enum AnswerType
{
    String,
    DateTime
}
我更喜欢使用.NET类型的系统。让我给你推荐另一种设计。我们总是从定义视图模型开始:

public abstract class AnswerViewModel
{
    public string Type 
    {
        get { return GetType().FullName; }
    }
}

public class StringAnswer : AnswerViewModel
{
    [Required]
    public string Value { get; set; }
}

public class DateAnswer : AnswerViewModel
{
    [Required]
    public DateTime? Value { get; set; }
}

public class QuestionViewModel
{
    public int Id { get; set; }
    public string Caption { get; set; }
    public AnswerViewModel Answer { get; set; }
}
然后控制器:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        var model = new[]
        {
            new QuestionViewModel
            {
                Id = 1,
                Caption = "What is your favorite color?",
                Answer = new StringAnswer()
            },
            new QuestionViewModel
            {
                Id = 1,
                Caption = "What is your birth date?",
                Answer = new DateAnswer()
            },
        };
        return View(model);
    }

    [HttpPost]
    public ActionResult Index(IEnumerable<QuestionViewModel> questions)
    {
        // process the answers. Thanks to our custom model binder
        // (see below) here you will get the model properly populated
        ...
    }
}
现在,我们可以为答案提供编辑器模板:

public class AnswerModelBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
        var typeValue = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + ".Type");
        var type = Type.GetType(typeValue.AttemptedValue, true);
        var model = Activator.CreateInstance(type);
        bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, type);
        return model;
    }
}
~/Views/Home/EditorTemplates/StringAnswer.cshtml

@model StringAnswer

<div>It's a string answer</div>
@Html.EditorFor(x => x.Value)
@Html.ValidationMessageFor(x => x.Value)
@model DateAnswer

<div>It's a date answer</div>
@Html.EditorFor(x => x.Value)
@Html.ValidationMessageFor(x => x.Value)
最后一件是我们答案的定制模型活页夹:

public class AnswerModelBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
        var typeValue = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + ".Type");
        var type = Type.GetType(typeValue.AttemptedValue, true);
        var model = Activator.CreateInstance(type);
        bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, type);
        return model;
    }
}
将在
应用程序\u Start
中注册:

ModelBinders.Binders.Add(typeof(AnswerViewModel), new AnswerModelBinder());

哈我怎么能忽视这超负荷?!那看起来近乎完美,谢谢!接受答案:)
enum AnswerType{String,DateTime}
是我必须使用的,它来自数据库。当然,我可以把它映射到你的结构上。然而@tkerwood的第一个答案似乎满足了我的需要。我会评估它,如果我发现有什么缺点,你会回到你的。谢谢@Michael Sagalovich,我展示的是视图模型的使用。这是你应该经常使用的。数据库和域实体中的内容并不重要。您需要了解的是,您应该始终使用视图模型,并让控制器操作传递和接收视图模型。如果希望重用某些现有的域类,可以在控制器中完成域模型和视图模型之间的映射。但不要将这些类传递给您的视图。谢谢,伟大的大师:)坦率地说,我有很多理由支持和反对您的说法,支持和反对在视图上使用域模型,但这不是一个问题。@DarinDimitrov我正在使用您的示例代码,这非常有帮助。但是,当我尝试使用
公共行动结果索引(IEnumerable questions)
处理答案时,
问题中的
问题视图模型
不再包含标题。知道为什么吗?@Andreaschwarz,在我的示例中,我只在视图中显示了标题:
@Html.DisplayFor(x=>x[I].caption)
。如果要持久化该值,可以将其存储在隐藏字段中。但就我个人而言,我不会为此烦恼,因为您已经有了足够的ID,可以从您使用的数据存储中检索服务器上的标题。