MVC3 Editor для динамического свойства (или требуется обходной путь) - PullRequest
4 голосов
/ 08 июля 2011

Я строю систему, которая задает вопросы и получает ответы на них. Каждый вопрос может иметь свой тип ответа. Давайте пока ограничимся 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;
    }
}

И тут начинаются проблемы. Я хочу иметь общий вид для рисования любого вопроса, поэтому он должен выглядеть примерно так:

@model QuestionWithAnswer<dynamic>

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

Для String Я хочу, чтобы здесь был простой ввод, для DateTime Я собираюсь определить свое собственное представление. Я могу передать конкретную модель от контроллера. Но проблема в том, что на этапе рендеринга, естественно, он не может определить тип ответа, особенно если он изначально null (по умолчанию для String), поэтому EditorFor ничего не рисует для String и вводит для всех свойства в DateTime.

Я понимаю природу проблемы, но есть ли какой-нибудь элегантный обходной путь? Или я должен реализовать свою собственную логику для выбора имени представления редактора, основываясь на типе элемента управления (большой урод switch)?

Ответы [ 2 ]

5 голосов
/ 08 июля 2011

Лично мне это не нравится:

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());
1 голос
/ 08 июля 2011

Вы все еще можете использовать Html.EditorFor (..), но указать второй параметр, который является именем шаблона редактора. У вас есть свойство объекта Question, которое представляет собой AnswerType, поэтому вы можете сделать что-то вроде ...

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

В вашей папке EditorTemplates просто определите представление для каждого из типов ответа. то есть "String", "DateTime" и т. д.

РЕДАКТИРОВАТЬ: Поскольку объект ответа является пустым для String, я бы поместил туда объект-заполнитель, просто чтобы модель в шаблоне редактора «String» не была нулевой.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...