ModelMetaData, пользовательские атрибуты класса и неописуемый вопрос - PullRequest
6 голосов
/ 26 июля 2011

То, что я хочу сделать, кажется таким простым.

В моем index.cshtml я хочу отобразить WizardStepAttribute Значение

Итак, пользователь будет видеть вверху каждой страницы, Step 1: Enter User Information


У меня есть ViewModel с именем WizardViewModel.Этот ViewModel имеет свойство IList<IStepViewModel> Steps

, каждый «шаг» реализует интерфейс IStepViewModel, который является пустым интерфейсом.

У меня есть представление с именем Index.cshtml.В этом представлении отображается EditorFor() текущий шаг.

У меня есть пользовательский ModelBinder, который связывает представление с новым экземпляром конкретного класса, реализующего IStepViewModel на основе свойства WizardViewModel.CurrentStepIndex

Я создал собственный атрибут WizardStepAttribute.

Каждый из моих классов шагов определен следующим образом.

[WizardStepAttribute(Name="Enter User Information")] 
[Serializable]
public class Step1 : IStepViewModel
....

У меня есть несколько проблем.

My Viewстрого набирается на WizardViewModel не каждый шаг.Я не хочу создавать представление для каждой конкретной реализации IStepViewModel

. Я думал, что мог бы добавить свойство к интерфейсу, но затем я должен явно реализовать его в каждом классе.(Так что это не лучше)

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

Ответы [ 2 ]

11 голосов
/ 28 июля 2011

Это можно сделать, но это не просто и не красиво.

Во-первых, я бы предложил добавить второе строковое свойство в ваш класс WizardStepAttribute, StepNumber, чтобы ваш класс WizardStepAttribute выглядел следующим образом:

[AttributeUsage(AttributeTargets.All, AllowMultiple = false)]
public class WizardStepAttribute : Attribute
{
    public string StepNumber { get; set; }
    public string Name { get; set; }
}

Затем каждый класс должен быть оформлен:

[WizardAttribute(Name = "Enter User Information", StepNumber = "1")]
public class Step1 : IStepViewModel
{
    ...
}

Далее необходимо создать пользовательский DataAnnotationsModelMetadataProvider, чтобы взять значения вашего пользовательского атрибута и вставить их в метаданные модели Step1.:

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

        if (additionalValues != null)
        {
            modelMetadata.AdditionalValues.Add("Name", additionalValues.Name);
            modelMetadata.AdditionalValues.Add("StepNumber", additionalValues.StepNumber);
        }
        return modelMetadata;
    }
}

Затем, чтобы представить ваши собственные метаданные, я предлагаю создать собственный HtmlHelper для создания вашей метки для каждого представления:

    [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
    public static MvcHtmlString WizardStepLabelFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
    {
        return WizardStepLabelFor(htmlHelper, expression, null /* htmlAttributes */);
    }

    [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
    public static MvcHtmlString WizardStepLabelFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
    {
        return WizardStepLabelFor(htmlHelper, expression, new RouteValueDictionary(htmlAttributes));
    }

    [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Users cannot use anonymous methods with the LambdaExpression type")]
    [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
    public static MvcHtmlString WizardStepLabelFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IDictionary<string, object> htmlAttributes)
    {
        if (expression == null)
        {
            throw new ArgumentNullException("expression");
        }
        ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
        var values = metadata.AdditionalValues;

        // build wizard step label
        StringBuilder labelSb = new StringBuilder();
        TagBuilder label = new TagBuilder("h3");
        label.MergeAttributes(htmlAttributes);
        label.InnerHtml = "Step " + values["StepNumber"] + ": " + values["Name"]; 
        labelSb.Append(label.ToString(TagRenderMode.Normal));

        return new MvcHtmlString(labelSb.ToString() + "\r");
    }

Как видите, пользовательский помощник создаеттег h3 с вашими пользовательскими метаданными.

Затем, наконец, на ваш взгляд, добавьте следующее:

@Html.WizardStepLabelFor(model => model)

Две заметки: сначала в файле Global.asax.cs,добавьте следующее в Application_Start ():

        ModelMetadataProviders.Current = new MyModelMetadataProvider();

Во-вторых, в файле web.config в папке Views убедитесь, что вы добавили пространство имен для своего пользовательского класса HtmlHelper:

<system.web.webPages.razor>
  <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
  <pages pageBaseType="System.Web.Mvc.WebViewPage">
    <namespaces>
      <add namespace="System.Web.Mvc" />
      <add namespace="System.Web.Mvc.Ajax" />
      <add namespace="System.Web.Mvc.Html" />
      <add namespace="System.Web.Routing" />
      <add namespace="YOUR NAMESPACE HERE"/>
    </namespaces>
  </pages>
</system.web.webPages.razor>

Вуаля.

counsellorben

3 голосов
/ 08 марта 2018

В нашем случае нам просто нужен атрибут, который реализует интерфейс IMetadataAware:

https://msdn.microsoft.com/en-us/library/system.web.mvc.imetadataaware(v=vs.118).aspx

В вашем случае это может быть:

public class WizardStepAttribute : Attribute, IMetadataAware
{
    public string Name;

    public void OnMetadataCreated(ModelMetadata metadata)
    {
        if (!metadata.AdditionalValues.ContainsKey("WizardStep"))
        {
            metadata.AdditionalValues.Add("WizardStep", Name);
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...