Maciej
Maciej

Reputation: 93

MVC - Nested models with interfaces and annotations

I have basic class:

public abstract class AbstractBaseModel : IModel
{
    [Display(Name = "Some Name")]
    [DisplayFormat(DataFormatString = "{0:d0}")]
    [RegularExpression(@"[0-9]{1,10}", ErrorMessage = "error")]
    public virtual string SomeName{ get; set; }
}

The IModel interface is just simple declaration of properties:

public interface IModel
{
    string SomeName{ get; set; }
}

From the base model I have 2 derived models

public class ClientModel : AbstractBaseModel
{
    [Required(ErrorMessage = "Some error message for customer only")]
    public override string SomeName{ get; set; }
}

public class PowerUserModel : AbstractBaseModel
{
    [Required(ErrorMessage = "Different message for the admin")]
    public override string SomeName{ get; set; }
}

This model, or rather interface is part of another model that combines multiple models:

public class ComboEndModel
{
     public IModel Model { get; set; }
     public IDifferentModel DifferentModel { get; set; }
}

Depending on the View/Controler that is currently used, I pass new ClientModel or PowerUserModel as a Model in ComboEndModel

When the view is rendered, I'm only getting the annotations from the base, abstract model, not the ones added in the derivative type. I suspect that's because I'm using interface as a nested model property instead of type.

Whats the correct way of implementing this relation, or working around the issue with incorrect annotations? Should I try with custom binding?

Upvotes: 2

Views: 299

Answers (2)

JamieD77
JamieD77

Reputation: 13949

One thing that might work for you would be to create editor templates for each derived model.

If you define your ComboEndModel like this

var model = new ComboEndModel() { Model = new PowerUserModel() };
return View(model);

and in your view you use EditorFor

@model WebApplication.Models.ComboEndModel
@Html.EditorFor(m => m.Model)

depending on what Type Model is, it will pick the associated editor template and apply the annotations for that derived type. In this case you'd need a partial view in your EditorTemplates folder named PowerUserModel.cshtml with a model type of PowerUserModel

Upvotes: 3

Jonny Piazzi
Jonny Piazzi

Reputation: 3784

Ok I have some problems with that too, and what I understood so far is (someone correct me if I'm wrong):

Why this problem occurs?

Because the DataAnnotation that is retrieved are retrived from the type of the model. Let me show you some samples:

If you have in your .cshtml file:

Sample 1:

@model AbstractBaseModel
// The type that will be retrived is `AbstractBaseModel`.

Sample 2:

@model IModel
// The type that will be retrived is `IModel`.

Or in other case if you have actions:

public ActionResult SomeAction(AbstractBaseModel model) { /* ... */ }

Sample 2:

public ActionResult SomeAction(IModel model) { /* ... */ }

So what matters is what type you specified in @model or in the action parameter. Doesn't matter if is interface, abstract class, base class or other type of class, matter only the type you described in your use.

In your example specifically you wrote:

public class ComboEndModel
{
    public IModel Model { get; set; }
    // ...
}

So the DataAnnotation will be retrive from IModel, but IModel don't have a RequiredAttribute defined so you get one step further in the chain of inheritance/implementation and get AbstractBaseModel. (Try this to confirm what I'm writing: put a required in IModel.SomeName with a different message and see what message will get).

Solution/Suggestion

If I'm right, there is no solution for your problem without change the strategy. So here some suggestions about what you can do:

  • Remove Required attribute and do yourself the validation (in action or javascript, depending of how type of validation you're using).
  • Replace the error message after validation occurs, (I did this in my case).
  • Give up of this inheritance and make independent models.
  • Upvotes: 2

    Related Questions