Подтвердить что ты не робот

MVC2 EditorTemplate для DropDownList

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

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

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

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

Здесь моя модель просмотра:

   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 имеет свойство CustomerCode, которое является значением, которое пользователь выбирает из списка значений. У меня есть свойство CustomerCodeList, которое является списком возможных значений CustomerCode и является источником для выпадающего списка. Я создал атрибут DropDownList с DropDownListTargetProperty. DropDownListTargetProperty указывает на свойство, которое будет заполнено на основе пользовательского выбора из сгенерированного раскрывающегося списка (в данном случае это свойство CustomerCode).

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

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

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

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

4b9b3361

Ответ 1

Я думаю, что нашел решение, чтобы оно работало при использовании 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>
    <% } %>
<% } %>

<%}% >

У меня есть код custome в моем 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 заменяет. Есть смысл?

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

Ответ 2

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

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

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 установлен выпадающий список, но он не является прямым элементом viewmodel, а не дочерним элементом ServicePackageWithOwnerName, который является элементом viewmodel. В таком состоянии нет способа установить значение OwnerName в контроллере, как это исправить? Цените!

Привет

Джек