Applying Data Annotations to sub properties of the View Model in MVC?

。_饼干妹妹 提交于 2020-02-01 03:26:17

问题


Putting simple Data Annotations on properties is great,

public class UnicornViewModel
{
   [Required]
   public string Name { get; set; }

But lets say I'm have something like this:

public class SuperPower
{
   public class Name { get; set; }
}

public class UnicornViewModel
{
   [Required]
   public string Name { get; set; }

   public SuperPower PrimarySuperPower { get; set; }

   public SuperPower SecondarySuperPower { get; set; }

How do I apply the Required attribute on PrimarySuperPower.Name while leaving it optional for SecondarySuperPower.Name? Preferably 1. something that ties into client side validation and 2. with out any special handling like checking the value of PrimarySuperPower.Name in the Action/Custom validator and add a ModelState error if it's empty. It would be great if there was something like:

   [Required(p => p.Name)]
   public SuperPower PrimarySuperPower { get; set; }

   public SuperPower SecondarySuperPower { get; set; }

回答1:


Generally this isn't supported: ASP.NET MVC3 Validation of nested view model object fields

But you can implement custom model validation, but doing so for both client and server side gets pretty complicated.

If you have your own template for the SuperPower object, it could look for an attribute of your own making:

   [RequiredSubProperty("Name")]
   public SuperPower PrimarySuperPower { get; set; }

And in the template just past the unobtrusive validation attributes into the htmlAttributes parameter of the TextBoxFor or whatever input helper you use.

If you are not using a template, I would forgo all that and just pass the unobtrusive validation attributes into the htmlAttributes parameter when displaying the first name but not for the second.

Another option is for the UnicornViewModel to be flattened like

public class UnicornViewModel
{
   [Required]
   public string Name { get; set; }

   [Required]
   public string PrimarySuperPowerName { get; set; }

   public string SecondarySuperPowerName { get; set; }

It all depends on how much reuse you might get from more complicated approaches. When I tried to use templating alot, I found that in different contexts certain things about templates didn't make sense, and such I'd need lots of variations on an object template(when a child template is displayed on a parent's page, it doesn't make sense for the child to have a URL linking to the parent's detail, since you're already on that page, but everywhere else the child template is used, it should display that link to parent). Ultimately I stopped using templates, and occasionally use partials where there is a good case for lots of reuse. The UI is where the rubber meets the road and ViewModels won't be structured as nicely as your entity/business models might be.




回答2:


You cannot do this with the standard data attributes. The Required syntax you mention would not be possible either in a custom implementation, as there is no reference to the object you're trying to use the lambda against.

You may be better off using a third party validation library, such as FluentValidation. It gives considerable flexibility to your validation requirements.




回答3:


I, personally, am a fan of using the ModelMetadataClass to directorate my ViewModels. If you are willing to go on step extra and use AutoMapper you could create a viewmodel as follows:

public class SuperPower
{
    public string Name { get; set; }
}

[MetadataType(typeof(UnicornViewModel.UnicornViewModelMetaData))]
public class UnicornViewModel
{
    public string Name { get; set; }

    public RequiredSuperPowerViewModel PrimarySuperPower { get; set; }

    public SuperPower SecondarySuperPower { get; set; }

    public class UnicornViewModelMetaData
    {
        [Required]
        public string Name { get; set; }

    }
}

[MetadataType(typeof(UnicornViewModel.UnicornViewModelMetaData))]
public class RequiredSuperPowerViewModel : SuperPower
{
    public class RequiredSuperPowerModelMetaData
    {
        [Required]
        public string Name { get; set; }

    }
}

This will allow you to pick which fields you would like required for a given model class without impacting your model.

If you are using AutoMapper you can rehydrate the original SuperPower as follows:

SuperPower reqSuperPower = AutoMapper.Mapper.Map<RequiredSuperPowerViewModel, SuperPower>(Data.PrimarySuperPower);



回答4:


This might be a late answer, but I found this question when searching for the same thing. This is how I solved my particular situation:

Before I had this:

public class ProductVm
{
    //+ some other properties        

    public Category Category {get; set;}
    public Category ParentCategory {get; set;}
}

For which I wanted to have something in the likes of:

public class ProductVm
{
    //some other properties        

    [DisplayName("Product Category", e => e.Description)]
    public Category Category {get; set;}
    [DisplayName("Parent Category", e => e.Description)]
    public Category ParentCategory {get; set;}
}

I couldn't enter this in the model itself since both are the same object class.

I solved it like this (since I only needed to read the Description value in this case and not write it):

public class ProductVm
{
    //some other properties        

    public Category Category {get; set;}
    public Category ParentCategory {get; set;}

    [DisplayName("Product Category")]
    public string Category => Category.Description;

    [DisplayName("Main Category")]
    public string ParentCategory => ParentCategory.Description;
}

You could possible just rewrite it a bit more to keep the remaining private backing fields and remove the property encapsulation of the Category objects, but in my case I still needed them to be public for other uses.

Concerning the above question I would do the following:

public class UnicornViewModel
{
    [Required]
    public string Name { get; set; }

    public SuperPower PrimarySuperPower { get; set; }

    public SuperPower SecondarySuperPower { get; set; }

    [Required]
    public string PrimarySuperPowerName 
    {
        get { return PrimarySuperPower.Name; }
        set { PrimarySuperPower.Name = value; }
    }

    public string SecondarySuperPowerName 
    {
        get { return SecondarySuperPower.Name; }
        set { SecondarySuperPower.Name = value; }
    }
}

And then I'd bind my View to the string properties and exclude the SuperPower properties.



来源:https://stackoverflow.com/questions/17153252/applying-data-annotations-to-sub-properties-of-the-view-model-in-mvc

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!