将多个接口与 MVC DataAnnotations 和 MetaDataType 结合使用

发布于 2024-12-10 13:40:23 字数 1709 浏览 0 评论 0原文

我正在使用 DataAnnotations 将验证应用于 MVC ViewModel,它是多个实体框架对象和一些自定义逻辑的组合。接口中的实体对象已经定义了验证,但是如何将此验证应用于 ViewModel?

我最初的想法是将这些接口合并为一个,并将合并后的接口应用到 ViewModel,但这不起作用。下面是一些示例代码,演示了我的意思:

// interfaces containing DataAnnotations implemented by entity framework classes
public interface IPerson
{
    [Required]
    [Display(Name = "First Name")]
    string FirstName { get; set; }

    [Required]
    [Display(Name = "Last Name")]
    string LastName { get; set; }

    [Required]
    int Age { get; set; }
}
public interface IAddress
{
    [Required]
    [Display(Name = "Street")]
    string Street1 { get; set; }

    [Display(Name = "")]
    string Street2 { get; set; }

    [Required]
    string City { get; set; }

    [Required]
    string State { get; set; }

    [Required]
    string Country { get; set; }
}

// partial entity framework classes to specify interfaces
public partial class Person : IPerson {}
public partial class Address : IAddress {}

// combined interface
public interface IPersonViewModel : IPerson, IAddress {}

// ViewModel flattening a Person with Address for use in View
[MetadataType(typeof(IPersonViewModel))] // <--- This does not work. 
public class PersonViewModel : IPersonViewModel
{
    public string FirstName { get; set; }

    public string LastName { get; set; }

    public int Age { get; set; }

    public string Street1 { get; set; }

    public string Street2 { get; set; }

    public string City { get; set; }

    public string State { get; set; }

    public string Country { get; set; }
}

我的实际问题涉及 ViewModel 上的大约 150 个属性,因此它并不像示例那么简单,并且重新输入所有属性似乎严重违反了 DRY。

关于如何实现这一目标有什么想法吗?

I am applying validation using DataAnnotations to an MVC ViewModel which is a composite of several entity framework objects and some custom logic. The validation is already defined for the entity objects in interfaces, but how can I apply this validation to the ViewModel?

My initial idea was to combine the interfaces into one and apply the combined interface to the ViewModel, but this didn't work. Here's some sample code demonstrating what I mean:

// interfaces containing DataAnnotations implemented by entity framework classes
public interface IPerson
{
    [Required]
    [Display(Name = "First Name")]
    string FirstName { get; set; }

    [Required]
    [Display(Name = "Last Name")]
    string LastName { get; set; }

    [Required]
    int Age { get; set; }
}
public interface IAddress
{
    [Required]
    [Display(Name = "Street")]
    string Street1 { get; set; }

    [Display(Name = "")]
    string Street2 { get; set; }

    [Required]
    string City { get; set; }

    [Required]
    string State { get; set; }

    [Required]
    string Country { get; set; }
}

// partial entity framework classes to specify interfaces
public partial class Person : IPerson {}
public partial class Address : IAddress {}

// combined interface
public interface IPersonViewModel : IPerson, IAddress {}

// ViewModel flattening a Person with Address for use in View
[MetadataType(typeof(IPersonViewModel))] // <--- This does not work. 
public class PersonViewModel : IPersonViewModel
{
    public string FirstName { get; set; }

    public string LastName { get; set; }

    public int Age { get; set; }

    public string Street1 { get; set; }

    public string Street2 { get; set; }

    public string City { get; set; }

    public string State { get; set; }

    public string Country { get; set; }
}

My real-world problem involves about 150 properties on the ViewModel, so it's not as trivial as the sample and retyping all the properties seems like a horrible violation of DRY.

Any ideas on how to accomplish this?

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

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

发布评论

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

评论(3

太阳男子 2024-12-17 13:40:23

为了使其工作,您需要手动将接口关联为具体类的元数据。

我希望能够添加多个 MetadataType 属性,但这是不允许的。

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] // Notice AllowMultiple
public sealed class MetadataTypeAttribute : Attribute

因此,这会产生编译错误:

[MetadataType(typeof(IPerson))] 
[MetadataType(typeof(IAddress))] // <--- Duplicate 'MetadataType' attribute 
public class PersonViewModel : IPersonViewModel

但是,如果您只有一个接口,它就可以工作。因此,我的解决方案是简单地使用 AssociatedMetadataTypeTypeDescriptionProvider 关联接口,并将其包装在另一个属性中。

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class MetadataTypeBuddyAttribute : Attribute
{
    public MetadataTypeBuddyAttribute(Type modelType, Type buddyType)
    {
        TypeDescriptor.AddProviderTransparent(
           new AssociatedMetadataTypeTypeDescriptionProvider(
               modelType,
               buddyType
           ),
           modelType);
    }
}

在我的情况(MVC4)中,我的接口上的数据注释属性已经起作用了。这是因为我的模型直接实现接口而不是具有多级继承。但是,在接口级别实现的自定义验证属性不起作用。

仅当手动关联接口时,所有自定义验证才会相应地起作用。如果我正确理解你的情况,这也是你问题的解决方案。

[MetadataTypeBuddy(typeof(PersonViewModel), typeof(IPerson))] 
[MetadataTypeBuddy(typeof(PersonViewModel), typeof(IAddress))]
public class PersonViewModel : IPersonViewModel

In order for this to work you will need to manually associate the interfaces as metadata for your concrete classes.

I expected to be able to add multiple MetadataType attributes but that is not permitted.

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] // Notice AllowMultiple
public sealed class MetadataTypeAttribute : Attribute

Therefore, this gives a compilation error:

[MetadataType(typeof(IPerson))] 
[MetadataType(typeof(IAddress))] // <--- Duplicate 'MetadataType' attribute 
public class PersonViewModel : IPersonViewModel

However, it works if you only have one interface. So my solution to this was to simply associate the interfaces using a AssociatedMetadataTypeTypeDescriptionProvider and wrap that in another attribute.

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class MetadataTypeBuddyAttribute : Attribute
{
    public MetadataTypeBuddyAttribute(Type modelType, Type buddyType)
    {
        TypeDescriptor.AddProviderTransparent(
           new AssociatedMetadataTypeTypeDescriptionProvider(
               modelType,
               buddyType
           ),
           modelType);
    }
}

In my situation (MVC4) the data annotation attributes on my interfaces already worked. This is because my models directly implement the interfaces instead of having multi-level inheritance. However custom validation attributes implemented at the interface level do not work.

Only when manually associating the interfaces all the custom validations work accordingly. If I understand your case correctly this is also a solution for your problem.

[MetadataTypeBuddy(typeof(PersonViewModel), typeof(IPerson))] 
[MetadataTypeBuddy(typeof(PersonViewModel), typeof(IAddress))]
public class PersonViewModel : IPersonViewModel
不离久伴 2024-12-17 13:40:23

根据此处的答案,我无法以某种方式使 MetadataTypeBuddy 属性起作用。我确信我们必须在某个地方设置 MVC 应该调用该属性。当我像这样在 Application_Start() 中手动运行该属性时,我设法让它工作

new MetadataTypeBuddyAttribute(typeof(PersonViewModel), typeof(IPerson));
new MetadataTypeBuddyAttribute(typeof(PersonViewModel), typeof(IAddress));

based on answer here, I couldn't somehow make that MetadataTypeBuddy attribute works. I'm sure that we must set somewhere that MVC should be calling that attribute. I managed to get it work when I run that attribute manually in Application_Start() like this

new MetadataTypeBuddyAttribute(typeof(PersonViewModel), typeof(IPerson));
new MetadataTypeBuddyAttribute(typeof(PersonViewModel), typeof(IAddress));
玩套路吗 2024-12-17 13:40:23

MetadataTypeBuddy 属性对我不起作用。
但是在“启动”中添加“新”MetadataTypeBuddyAttribute 确实有效但是它可能会导致复杂的代码,开发人员不知道在任何新类的“启动”中添加它。

注意:您只需在每个类的应用程序启动时调用一次 AddProviderTransparent 即可。

这是为类添加多个元数据类型的线程安全方法。

    [AttributeUsage(AttributeTargets.Class)]
    public class MetadataTypeMultiAttribute : Attribute
    {
        private static bool _added = false;
        private static readonly object padlock = new object();

        public MetadataTypeMultiAttribute(Type modelType, params Type[] metaDataTypes)
        {
            lock (padlock)
            {
                if (_added == false)
                {
                    foreach (Type metaDataType in metaDataTypes)
                    {
                        System.ComponentModel.TypeDescriptor.AddProviderTransparent(
                            new AssociatedMetadataTypeTypeDescriptionProvider(
                                modelType,
                                metaDataType
                            ),
                            modelType);
                    }

                    _added = true;
                }
            }
        }
    }

The MetadataTypeBuddy attribute did not work for me.
BUT adding "new" MetadataTypeBuddyAttribute in the "Startup" did work BUT it can lead to complex code where the developer is not aware to add this in the "Startup" for any new classes.

NOTE: You only need to call the AddProviderTransparent once at the startup of the app per class.

Here is a thread safe way of adding multiple Metadata types for a class.

    [AttributeUsage(AttributeTargets.Class)]
    public class MetadataTypeMultiAttribute : Attribute
    {
        private static bool _added = false;
        private static readonly object padlock = new object();

        public MetadataTypeMultiAttribute(Type modelType, params Type[] metaDataTypes)
        {
            lock (padlock)
            {
                if (_added == false)
                {
                    foreach (Type metaDataType in metaDataTypes)
                    {
                        System.ComponentModel.TypeDescriptor.AddProviderTransparent(
                            new AssociatedMetadataTypeTypeDescriptionProvider(
                                modelType,
                                metaDataType
                            ),
                            modelType);
                    }

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