MVC3 EditorFor动态属性(或需要解决方法)

发布于 2024-11-19 05:31:07 字数 1256 浏览 1 评论 0原文

我正在构建一个提出问题并接收答案的系统。每个问题都可以有自己类型的答案。现在我们将其限制为 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)?

I am building a system which asks questions and receives answers to them. Each question can have an aswer of its own type. Let's limit it to String and DateTime for now. In Domain, question is represented the following way:

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

    public string Caption
    {
        get;
        set;
    }

    public AnswerType
    {
        get;
        set;
    }
}

, where AnswerType is

enum AnswerType
{
    String,
    DateTime
}

Please note that actually I have much more answer types.

I came up with an idea of creating a MVC model, deriving from Question and adding Answer property to it. So it has to be something like this:

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

And here start the problems. I want to have a generic view to draw any question, so it needs to be something like that:

@model QuestionWithAnswer<dynamic>

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

For String I want to have simple input here, for DateTime I am going to define my own view. I can pass the concrete model from the controller. But the problem is that on the rendering stage, naturally, it cannot determine the type of Answer, especially if it is initially null (default for String), so EditorFor draws nothing for String and inputs for all properties in DateTime.

I do understand the nature of the problem, but is there any elegant workaround? Or I have to implement my own logic for selecting editor view name basing on control type (big ugly switch)?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

满地尘埃落定 2024-11-26 05:31:07

我个人不喜欢这样:

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());

Personally I don't like this:

enum AnswerType
{
    String,
    DateTime
}

I prefer using .NET type system. Let me suggest you an alternative design. As always we start by defining out view models:

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

then a controller:

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

then the main Index.cshtml view:

@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" />
}

and now we can have editor templates for our answers:

~/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)

and the last piece is a custom model binder for our answers:

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

which will be registered in Application_Start:

ModelBinders.Binders.Add(typeof(AnswerViewModel), new AnswerModelBinder());
关于从前 2024-11-26 05:31:07

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

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

在您的 EditorTemplates 文件夹中只需为每个 AnswerType 定义一个视图。即“String”、“DateTime”等。

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

You can still use the Html.EditorFor(..), but specify a second parameter which is the name of the editor template. You have a property on the Question object that is the AnswerType, so you could do something like...

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

The in your EditorTemplates folder just define a view for each of the AnswerTypes. ie "String", "DateTime", etc.

EDIT: As far as the Answer object being null for String, i would put a placeholder object there just so the model in you "String" editor template is not null.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文