Model inheritance possible when using strongly-typed view in MVC3?

后端 未结 3 1520
有刺的猬
有刺的猬 2020-12-10 05:46

I have the following setup in my model:

namespace QuickTest.Models
{
    public class Person
    {
        [Required]
        [Display(Name = \"Full name\")]         


        
3条回答
  •  一向
    一向 (楼主)
    2020-12-10 06:42

    You can customize the validators and the metadata to come from your concrete class, but the solution has several moving parts, including two custom metadata providers.

    First, create a custom Attribute to decorate each property of the base class. This is necessary as a flag for our custom providers, to indicate when further analysis is needed. This is the attribute:

    [AttributeUsage(AttributeTargets.All, Inherited = true, AllowMultiple = true)]
    public class BaseTypeAttribute : Attribute { }
    

    Next, create a custom ModelMetadataProvider inheriting from DataAnnotationsModelMetadataProvider:

    public class MyModelMetadataProvider : DataAnnotationsModelMetadataProvider
    {
        protected override ModelMetadata CreateMetadata(
            IEnumerable attributes,
            Type containerType,
            Func modelAccessor,
            Type modelType,
            string propertyName)
        {
            var attribute = attributes.FirstOrDefault(a => a.GetType().Equals(typeof(BaseTypeAttribute))) as BaseTypeAttribute;
            if (attribute != null && modelAccessor != null)
            {
                var target = modelAccessor.Target;
                var containerField = target.GetType().GetField("container");
                if (containerField == null)
                {
                    var vdi = target.GetType().GetField("vdi").GetValue(target) as ViewDataInfo;
                    var concreteType = vdi.Container.GetType();
                    return base.CreateMetadata(attributes, concreteType, modelAccessor, modelType, propertyName);
                }
                else
                {
                    var container = containerField.GetValue(target);
                    var concreteType = container.GetType();
                    var propertyField = target.GetType().GetField("property");
                    if (propertyField == null)
                    {
                        concreteType = base.GetMetadataForProperties(container, containerType)
                            .FirstOrDefault(p => p.PropertyName == "ConcreteType").Model as System.Type;
                        if (concreteType != null)
                            return base.GetMetadataForProperties(container, concreteType)
                                .FirstOrDefault(pr => pr.PropertyName == propertyName);
                    }
                    return base.CreateMetadata(attributes, concreteType, modelAccessor, modelType, propertyName);
                }
            }
            return base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
        }
    }
    
    
    

    Then, create a custom ModelValidatorProvider inheriting from DataAnnotationsModelValidatorProvider:

    public class MyModelMetadataValidatorProvider : DataAnnotationsModelValidatorProvider
    {
        protected override IEnumerable GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable attributes)
        {
            List vals = base.GetValidators(metadata, context, attributes).ToList();
    
            var baseTypeAttribute = attributes.FirstOrDefault(a => a.GetType().Equals(typeof(BaseTypeAttribute))) 
                as BaseTypeAttribute;
    
            if (baseTypeAttribute != null)
            {
                // get our parent model
                var parentMetaData = ModelMetadataProviders.Current.GetMetadataForProperties(context.Controller.ViewData.Model,
                    metadata.ContainerType);
    
                // get the concrete type
                var concreteType = parentMetaData.FirstOrDefault(p => p.PropertyName == "ConcreteType").Model;
                if (concreteType != null)
                {
                    var concreteMetadata = ModelMetadataProviders.Current.GetMetadataForProperties(context.Controller.ViewData.Model,
                        Type.GetType(concreteType.ToString()));
    
                    var concretePropertyMetadata = concreteMetadata.FirstOrDefault(p => p.PropertyName == metadata.PropertyName);
    
                    vals = base.GetValidators(concretePropertyMetadata, context, attributes).ToList();
                }
            }
            return vals.AsEnumerable();
        }
    }
    

    After that, register both custom providers in Application_Start in Global.asax.cs:

    ModelValidatorProviders.Providers.Clear();
    ModelValidatorProviders.Providers.Add(new MvcApplication8.Controllers.MyModelMetadataValidatorProvider());
    ModelMetadataProviders.Current = new MvcApplication8.Controllers.MyModelMetadataProvider();
    

    Now, change your models like so:

    public class Person
    {
        public Type ConcreteType { get; set; }
    
        [Required]
        [Display(Name = "Full name")]
        [BaseType]
        public string FullName { get; set; }
    
        [Display(Name = "Address Line 1")]
        [BaseType]
        public virtual string Address1 { get; set; }
    }
    
    public class Sender : Person
    {
        public Sender()
        {
            this.ConcreteType = typeof(Sender);
        }
    
        [Required]
        [Display(Name = "Address Line One")]
        public override string Address1 { get; set; }
    }
    
    public class Receiver : Person
    {
    }
    

    Note that the base class has a new property, ConcreteType. This will be used to indicate which inheriting class has instantiated this base class. Whenever an inheriting class has metadata which overrides the metadata in the base class, the inheriting class' constructor should set the base class ConcreteType property.

    Now, even though your view uses the base class, the attributes specific to any concrete inheriting class will appear in your view, and will affect the validation of the model.

    In addition, you should be able to turn the View into a template for the Person type, and use the template for any instance using the base class or inheriting from it.

    提交回复
    热议问题