Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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; }
like image 963
Levitikon Avatar asked Jun 17 '13 17:06

Levitikon


2 Answers

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.

like image 125
AaronLS Avatar answered Oct 21 '22 18:10

AaronLS


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.

like image 40
BVer Avatar answered Oct 21 '22 17:10

BVer