MVC2 EditorTemplate для DropDownList - PullRequest
       22

MVC2 EditorTemplate для DropDownList

16 голосов
/ 29 апреля 2010

Большую часть прошедшей недели я провел по колено в новой функциональности шаблонов, встроенной в MVC2. Я с трудом пытался заставить работать шаблон DropDownList. Самая большая проблема, над которой я работал, - как получить исходные данные для выпадающего списка в шаблон. Я видел много примеров, когда вы можете поместить исходные данные в словарь ViewData (ViewData ["DropDownSourceValuesKey"]), а затем получить их в самом шаблоне (var sourceValues ​​= ViewData ["DropDownSourceValuesKey"];) Это работает, но я сделал не то чтобы иметь глупую нить в качестве рычага для выполнения этой работы.

Ниже приведен подход, который я предложил и хотел бы получить мнения об этом подходе:

вот мои цели дизайна:

  • Модель представления должна содержать исходные данные для выпадающего списка
  • Предел глупых струн
  • Не использовать словарь ViewData
  • Контроллер отвечает за заполнение свойства исходными данными для выпадающего списка

Вот мой вид Модель:

   public class CustomerViewModel
    {
        [ScaffoldColumn(false)]
        public String CustomerCode{ get; set; }

        [UIHint("DropDownList")]
        [DropDownList(DropDownListTargetProperty = "CustomerCode"]
        [DisplayName("Customer Code")]
        public IEnumerable<SelectListItem> CustomerCodeList { get; set; }

        public String FirstName { get; set; }
        public String LastName { get; set; }
        public String PhoneNumber { get; set; }
        public String Address1 { get; set; }
        public String Address2 { get; set; }
        public String City { get; set; }
        public String State { get; set; }
        public String Zip { get; set; }
    }

My View Model имеет свойство CustomerCode, которое является значением, которое пользователь выбирает из списка значений. У меня есть свойство CustomerCodeList, которое представляет собой список возможных значений CustomerCode и является источником раскрывающегося списка. Я создал атрибут DropDownList с DropDownListTargetProperty. DropDownListTargetProperty указывает на свойство, которое будет заполнено на основе выбора пользователя из сгенерированного раскрывающегося списка (в данном случае, свойства CustomerCode).

Обратите внимание , что свойство CustomerCode имеет [ScaffoldColumn (false)], что заставляет генератор пропускать поле в сгенерированном выводе.

Мой файл DropDownList.ascx сгенерирует элемент формы выпадающего списка с исходными данными из свойства CustomerCodeList. Сгенерированный раскрывающийся список будет использовать значение DropDownListTargetProperty из атрибута DropDownList в качестве атрибутов Id и Name элемента формы Select. Таким образом, сгенерированный код будет выглядеть так:

<select id="CustomerCode" name="CustomerCode">
  <option>...
</select>

Это прекрасно работает, потому что при отправке формы MVC заполняет целевое свойство выбранным значением из выпадающего списка, поскольку имя сгенерированного выпадающего списка IS целевого свойства. Я своего рода визуализирую это, поскольку свойство CustomerCodeList является расширением своего рода свойства CustomerCode. Я связал исходные данные со свойством.

Вот мой код для контроллера:

public ActionResult Create()
{
    //retrieve CustomerCodes from a datasource of your choosing
    List<CustomerCode> customerCodeList = modelService.GetCustomerCodeList();

    CustomerViewModel viewModel= new CustomerViewModel();
    viewModel.CustomerCodeList = customerCodeList.Select(s => new SelectListItem() { Text = s.CustomerCode, Value = s.CustomerCode, Selected = (s.CustomerCode == viewModel.CustomerCode) }).AsEnumerable();

    return View(viewModel);
}

Вот мой код для DropDownListAttribute:

namespace AutoForm.Attributes
{
    public class DropDownListAttribute : Attribute
    {
        public String DropDownListTargetProperty { get; set; }
    }
}

Вот мой код для шаблона (DropDownList.ascx):

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<SelectListItem>>" %>
<%@ Import Namespace="AutoForm.Attributes"%>

<script runat="server">
    DropDownListAttribute GetDropDownListAttribute()
    {
        var dropDownListAttribute = new DropDownListAttribute();

        if (ViewData.ModelMetadata.AdditionalValues.ContainsKey("DropDownListAttribute"))
        {
            dropDownListAttribute = (DropDownListAttribute)ViewData.ModelMetadata.AdditionalValues["DropDownListAttribute"];
        }

        return dropDownListAttribute;        
    }
</script>    

<% DropDownListAttribute attribute = GetDropDownListAttribute();%>

<select id="<%= attribute.DropDownListTargetProperty %>" name="<%= attribute.DropDownListTargetProperty %>">
    <% foreach(SelectListItem item in ViewData.Model) 
       {%>
        <% if (item.Selected == true) {%>
            <option value="<%= item.Value %>" selected="true"><%= item.Text %></option>
        <% } %>
        <% else {%>
            <option value="<%= item.Value %>"><%= item.Text %></option>
        <% } %>
    <% } %>
</select>

Я попытался использовать помощник Html.DropDownList, но он не позволил мне изменить атрибуты Id и Name сгенерированного элемента Select.

ПРИМЕЧАНИЕ. Необходимо переопределить метод CreateMetadata объекта DataAnnotationsModelMetadataProvider для DropDownListAttribute. Вот код для этого:

public class MetadataProvider : DataAnnotationsModelMetadataProvider
{
    protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
    {
        var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
        var additionalValues = attributes.OfType<DropDownListAttribute>().FirstOrDefault();

        if (additionalValues != null)
        {
            metadata.AdditionalValues.Add("DropDownListAttribute", additionalValues);
        }

        return metadata;
    }
}

Затем необходимо выполнить вызов нового провайдера метаданных в Application_Start файла Global.asax.cs:

protected void Application_Start()
{
    RegisterRoutes(RouteTable.Routes);

    ModelMetadataProviders.Current = new MetadataProvider();
}

Ну, я надеюсь, что это имеет смысл, и я надеюсь, что этот подход может сэкономить вам время. Я хотел бы получить некоторые отзывы об этом подходе, пожалуйста. Есть ли лучший подход?

Ответы [ 3 ]

2 голосов
/ 25 сентября 2010

Я думаю, что нашел решение, чтобы оно работало при использовании Html.EditorForModel (); При использовании EditorForModel () MVC использует Object.ascx для циклического прохождения всех свойств модели и вызывает соответствующий шаблон для каждого свойства в модели. ASP.Net MVC из коробки имеет Object.ascx в коде, но вы можете создать собственный Object.ascx Просто создайте подпапку EditorTemplates в папке Shared View. Создайте файл Object.ascx там. (прочитайте этот пост для получения дополнительной информации: http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-3-default-templates.html)

Вот мой Object.ascx:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<%@ Import Namespace="WebAppSolutions.Helpers" %>
<% if (ViewData.TemplateInfo.TemplateDepth > 1) { %>
<%= ViewData.ModelMetadata.SimpleDisplayText%>
<% }
else { %>    
<% foreach (var prop in ViewData.ModelMetadata.Properties.Where(pm => pm.ShowForEdit && !ViewData.TemplateInfo.Visited(pm))) { %>

    <% var htmlFieldName = Html.HtmlFieldNameFor(prop.PropertyName);%>

    <% if (prop.HideSurroundingHtml) { %>
        <%= Html.Editor(htmlFieldName)%>
    <% }
       else { %>
        <div id="<%= htmlFieldName %>Container" class="editor-field">
            <% if (!String.IsNullOrEmpty(Html.Label(prop.PropertyName).ToHtmlString())) { %>
                <%= Html.Label(prop.PropertyName, Html.HtmlDisplayName(prop.PropertyName), prop.IsRequired)%>
            <% } %>
            <%= Html.Editor(prop.PropertyName, "", htmlFieldName)%>
            <%= Html.ValidationMessage(prop.PropertyName, "*") %>
        </div>
    <% } %>
<% } %>

<%}%>

В моем WebAppSolutions.Helpers есть несколько привычных кодов для HtmlFieldNameFor и HtmlDisplayName. Эти помощники извлекают данные из атрибутов, примененных к свойствам в модели представления.

    public static String HtmlFieldNameFor<TModel>(this HtmlHelper<TModel> html, String propertyName)
    {
        ModelMetadata modelMetaData = GetModelMetaData(html, propertyName);
        return GetHtmlFieldName(modelMetaData, propertyName);
    }

    public static String HtmlDisplayName<TModel>(this HtmlHelper<TModel> html, String propertyName)
    {
        ModelMetadata modelMetaData = GetModelMetaData(html, propertyName);
        return modelMetaData.DisplayName ?? propertyName;
    }
    private static ModelMetadata GetModelMetaData<TModel>(HtmlHelper<TModel> html, String propertyName)
    {
        ModelMetadata modelMetaData = ModelMetadata.FromStringExpression(propertyName, html.ViewData);
        return modelMetaData;
    }

    private static String GetHtmlFieldName(ModelMetadata modelMetaData, string defaultHtmlFieldName)
    {
        PropertyExtendedMetaDataAttribute propertyExtendedMetaDataAttribute = GetPropertyExtendedMetaDataAttribute(modelMetaData);
        return propertyExtendedMetaDataAttribute.HtmlFieldName ?? defaultHtmlFieldName;
    }

Ключ к тому, чтобы заставить это работать с помощью EditorModelFor (), заключается в следующем (должна быть строка 20 или около того в Object.ascx выше):

<%= Html.Editor(prop.PropertyName, "", htmlFieldName)%>

prop.PropertyName - это свойство в ViewModel, содержащее список данных, которые станут DropDownList. htmlFieldName - это имя скрытого свойства, которое заменяет свойство DropDownList. Имеет смысл?

Надеюсь, это поможет вам.

1 голос
/ 12 мая 2010

Отлично. Это то, что я ищу. Спасибо!

Но ваш пример модели - простая модель. Как насчет сложной модели представления типа

public class MaintainServicePackageViewModel
{
    public IEnumerable<ServicePackageWithOwnerName> ServicePackageWithOwnerName { get; set; }
    public ServicePackageWithOwnerName CurrentServicePackage { get; set; }
    public IEnumerable<ServiceWithPackageName> ServiceInPackage { get; set; }
}

public class ServicePackageWithOwnerName : ServicePackage
{
    [UIHint("DropDownList")]
    [DropDownList(DropDownListTargetProperty = "Owner")]
    [DisplayNameLocalized(typeof(Resources.Globalization), "OwnerName")]
    public IEnumerable<SelectListItem> OwnerName { get; set; }
}

OwnerName установлено в раскрывающемся списке, но это не прямой элемент модели представления, а дочерний элемент ServicePackageWithOwnerName, который является элементом модели представления. В таком состоянии нет способа установить значение OwnerName в контроллере, как это исправить? Цените!

Привет

Jack

0 голосов
/ 04 февраля 2012

Это мой подход из этого поста в Code Project:

Один шаблон редактора для всех DropDownLists в ASP.Net MVC

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