ModelMetaData, Custom Class Attributes and an indescribable question

前端 未结 2 883
长情又很酷
长情又很酷 2021-02-09 20:13

What I want to do seems so simple.

In my index.cshtml I want to display the WizardStepAttribute Value

So, a user will see at the top of each page,

相关标签:
2条回答
  • 2021-02-09 20:42

    In our case we just needed an attribute that implements the IMetadataAware interface:

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

    In your case, this could be:

    public class WizardStepAttribute : Attribute, IMetadataAware
    {
        public string Name;
    
        public void OnMetadataCreated(ModelMetadata metadata)
        {
            if (!metadata.AdditionalValues.ContainsKey("WizardStep"))
            {
                metadata.AdditionalValues.Add("WizardStep", Name);
            }
        }
    }
    
    0 讨论(0)
  • 2021-02-09 20:43

    It can be done, but it is neither easy nor pretty.

    First, I would suggest adding a second string property to your WizardStepAttribute class, StepNumber, so that your WizardStepAttribute class looks like this:

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

    Then, each class must be decorated:

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

    Next, you need to create a custom DataAnnotationsModelMetadataProvider, to take the values of your custom attribute and insert them into the Step1 model's metadata:

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

    Then, to present your custom metadata, I suggest creating a custom HtmlHelper to create your label for each view:

        [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");
        }
    

    As you can see, the custom helper creates an h3 tag with your custom metadata.

    Then, finally, in your view, put in the following:

    @Html.WizardStepLabelFor(model => model)
    

    Two notes: first, in your Global.asax.cs file, add the following to Application_Start():

            ModelMetadataProviders.Current = new MyModelMetadataProvider();
    

    Second, in the web.config in the Views folder, make sure to add the namespace for your custom HtmlHelper class:

    <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>
    

    Voila.

    counsellorben

    0 讨论(0)
提交回复
热议问题