ModelMetaData、自定义类属性和一个难以描述的问题

发布于 2024-11-26 11:19:58 字数 947 浏览 5 评论 0原文

我想做的事情看起来很简单。

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

因此,用户将在每个页面的顶部看到第1步:输入用户信息


我有一个名为的ViewModel WizardViewModel。此 ViewModel 有一个属性 IList步骤

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

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

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

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

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

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

但我有几个问题。

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

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

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

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

一曲琵琶半遮面シ 2024-12-03 11:19:58

这是可以做到的,但既不容易,也不漂亮。

首先,我建议向您的 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,以获取您的 WizardStepAttribute 类的值自定义属性并将它们插入到 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>

瞧。

辅导员本

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

纵山崖 2024-12-03 11:19:58

在我们的例子中,我们只需要一个实现 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);
        }
    }
}

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);
        }
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文