2010-04-29 2 views
16

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

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

вот мои цели проекта:

  • модель представления должна содержать исходные данные для падения списке
  • Limit Глупые Строки
  • Не использовать словарь 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; } 
    } 

Мой вид Модель имеет свойство 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, но он не позволил бы мне изменить идентификатор и имя атрибуты создаваемого элемента 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; 
    } 
} 

Тогда вы должны сделать вызов на новый MetadataProvider в Application_Start из Global.asax.cs:

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

    ModelMetadataProviders.Current = new MetadataProvider(); 
} 

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

ответ

1

Perfect. Это то, что я ищу. Благодаря!

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

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

С уважением

Джек

+0

Первое, что приходит на ум, чтобы создать пользовательский шаблон редактор для ServicePackageWithOwnerName. Пользовательский шаблон ServicePackageWithOwnerName выдаст выпадающий список для свойства OwnerName. В MaintainServicePackageViewModel вы должны украсить CurrentServicePackage с помощью UIHint ("[ServicePackageWithOwnerName Tempalte Name]"). Я планирую изучить эту концепцию, но еще не успел сделать это. – tschreck

+0

Jack- Проверьте эту ссылку, это может быть что вам нужно: http://dotnetslackers.com/articles/aspnet/asp-net-mvc-2-0-templating.aspx – tschreck

+1

Также проверьте эту ссылку. Брэд Уилсон создал очень хорошую серию для создания шаблонов. Эта ссылка касается вложенных сложных типов. http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-4-custom-object-templates.html – tschreck

2

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

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

+0

У вас есть ссылка на 'PropertyExtendedMetaDataAttribute propertyExtendedMetaDataAttribute = GetPropertyExtendedMetaDataAttribute (modelMetaData); ', но я не смог найти этот тип в любом месте. Это что-то еще, что вы создали или это в какой-то сторонней библиотеке, например MvcContrib? – TLS

+0

Если у вас возникли трудности с принятием вашего ответа, отметьте это сообщение для внимания модератора. –

Смежные вопросы