4

我正在建立一个提出问题并接收答案的系统。每个问题都可以有自己类型的答案。让我们暂时限制StringDateTime。在域中,问题以下列方式表示:

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

    public string Caption
    {
        get;
        set;
    }

    public AnswerType
    {
        get;
        set;
    }
}

, 在AnswerType哪里

enum AnswerType
{
    String,
    DateTime
}

请注意,实际上我有更多的答案类型。

我想出了一个创建 MVC 模型的想法,从 Question 派生并为其添加 Answer 属性。所以它必须是这样的:

public class QuestionWithAnswer<TAnswer> : Question
{
    public TAnswer Answer
    {
        get;
        set;
    }
}

从这里开始问题。我想有一个通用的视图来绘制任何问题,所以它需要是这样的:

@model QuestionWithAnswer<dynamic>

<span>@Model.Caption</span>
@Html.EditorFor(m => m.Answer)

因为String我想在这里有简单的输入,因为DateTime我要定义我自己的视图。我可以从控制器传递具体模型。但问题是,在渲染阶段,自然无法确定 Answer 的类型,特别是如果它最初是null(默认为String),因此不为 中的所有属性EditorFor绘制任何内容String和输入DateTime

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

4

2 回答 2

6

我个人不喜欢这样:

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
        ...
    }
}

然后是主Index.cshtml视图:

@model QuestionViewModel[]

@using (Html.BeginForm())
{
    <ul>
        @for (int i = 0; i < Model.Length; i++)
        {
            @Html.HiddenFor(x => x[i].Answer.Type)
            @Html.HiddenFor(x => x[i].Id)
            <li>
                @Html.DisplayFor(x => x[i].Caption)
                @Html.EditorFor(x => x[i].Answer)
            </li>
        }
    </ul>
    <input type="submit" value="OK" />
}

现在我们可以为我们的答案提供编辑器模板:

~/Views/Home/EditorTemplates/StringAnswer.cshtml

@model StringAnswer

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

~/Views/Home/EditorTemplates/DateAnswer.cshtml

@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;
    }
}

将在以下位置注册Application_Start

ModelBinders.Binders.Add(typeof(AnswerViewModel), new AnswerModelBinder());
于 2011-07-08T11:33:22.557 回答
1

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

@Html.EditorFor(m => m.Answer, @Model.AnswerType)

在您的 EditorTemplates 文件夹中,只需为每个 AnswerTypes 定义一个视图。即“字符串”、“日期时间”等。

编辑:至于 String 的 Answer 对象为空,我会在其中放置一个占位符对象,以便“String”编辑器模板中的模型不为空。

于 2011-07-08T11:25:47.640 回答