问题
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, Step 1: Enter User Information
I have a ViewModel called WizardViewModel
. This ViewModel has a property that is IList<IStepViewModel> Steps
each "step" implements the Interface IStepViewModel, which is an empty interface.
I have a view called Index.cshtml. This view displays EditorFor()
the current step.
I have a custom ModelBinder, that binds the View to an new instance of the concrete class implementing IStepViewModel
based on the WizardViewModel.CurrentStepIndex
property
I have created a custom attribute WizardStepAttribute
.
Each of my Steps classes are defined like this.
[WizardStepAttribute(Name="Enter User Information")]
[Serializable]
public class Step1 : IStepViewModel
....
I have several problems though.
My View is strongly typed to WizardViewModel
not each step. I don't want to have to create a view for each concrete implementation of IStepViewModel
I thought I could add a property to the interface, but then I have to explicitly implement it in each class. (So this isn't any better)
I'm thinking I could implement it using reflection in the interface but, you can't refer to instances in methods in an interface.
回答1:
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
回答2:
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);
}
}
}
来源:https://stackoverflow.com/questions/6834814/modelmetadata-custom-class-attributes-and-an-indescribable-question