6

我想做的事情似乎很简单。

在我的 index.cshtml 中,我想显示WizardStepAttribute

因此,用户将在每个页面的顶部看到,Step 1: Enter User Information


我有一个名为WizardViewModel. 这个 ViewModel 有一个属性是IList<IStepViewModel> Steps

每个“步骤”都实现了接口 IStepViewModel,它是一个空接口。

我有一个名为 Index.cshtml 的视图。此视图显示EditorFor()当前步骤。

我有一个自定义 ModelBinder,它将视图绑定到IStepViewModel基于WizardViewModel.CurrentStepIndex属性实现的具体类的新实例

我创建了一个自定义属性WizardStepAttribute

我的每个 Steps 类都是这样定义的。

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

我有几个问题。

我的视图是强类型的,WizardViewModel不是每个步骤。我不想为每个具体实现创建一个视图IStepViewModel

我以为我可以向接口添加一个属性,但是我必须在每个类中显式地实现它。(所以这并没有更好)

我想我可以在接口中使用反射来实现它,但是你不能在接口的方法中引用实例。

4

2 回答 2

11

可以做到,但既不简单也不漂亮。

首先,我建议将第二个字符串属性添加到您的 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();

其次,在 Views 文件夹的 web.config 中,确保为您的自定义 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>

瞧。

辅导员本

于 2011-07-27T23:38:47.043 回答
4

在我们的例子中,我们只需要一个实现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);
        }
    }
}
于 2018-03-08T14:30:13.210 回答