Search code examples
asp.net-mvcpolymorphismviewmodelstrongly-typed-view

Polymorphism and Strongly-typed Views in ASP.NET MVC


I have a problem where I have two forms that are identical except that the required fields are different. For example, let's say the forms have the same fields: X, Y, and Z. In Form #1, X is required, but in Form #2, Y is required.

So I created two view models, Form1 and Form2, with the same properties but with the Required attributes on different properties. I then created an interface, let's call it IForm, that both models implement and built a View that is strongly typed on IForm.

The problem with that solution is that ASP.NET MVC 3 reads the attributes on IForm instead of the dynamic type of the object being passed to the view, that is Form1 or Form2, so I don't get the client side JavaScript field validation that I want.

I'm wondering if there's a solution other than creating a strongly-typed View for each view model.


Solution

  • I have put together a sample with what you described (I think) and I'm able to get it to work:

    public class TestController : Controller
    {
        public ActionResult Foo()
        {
            return View("IFoo");
        }
    
        [HttpPost]
        public ActionResult Foo(Foo foo)
        {
            if (!ModelState.IsValid)
                return View("IFoo", foo);
    
            return RedirectToAction("Foo");
        }
    
        public ActionResult Bar()
        {
            return View("IFoo");
        }
    
        [HttpPost]
        public ActionResult Bar(Bar bar)
        {
            if (!ModelState.IsValid)
                return View("IFoo", bar);
    
            return RedirectToAction("Bar");
        }
    }
    
    // The Interface - the Required attributes are not 
    // on the interface, just the concrete classes
    public interface IFoo
    {
        string Name { get; set; }
        string Description { get; set; }
    }
    
    // Concrete Class 1 - Name is required
    public class Foo : IFoo
    {
        [Required(ErrorMessage="Name is required.")]
        public string Name { get; set; }
    
        public string Description { get; set; }
    }
    
    // Concrete Class 2 - Description is required
    public class Bar : IFoo
    {
        public string Name { get; set; }
    
        [Required(ErrorMessage = "Description is required.")]
        public string Description { get; set; }
    }
    

    I then defined a strongly typed view:

    @model Test.Controllers.IFoo
    
    <h2>Test</h2>
    
    <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
    
    @using (Html.BeginForm()) {
        @Html.ValidationSummary(true)
        <fieldset>
            <legend>IFoo</legend>
    
            <div class="editor-label">
                @Html.LabelFor(model => model.Name)
            </div>
            <div class="editor-field">
                @Html.EditorFor(model => model.Name)
                @Html.ValidationMessageFor(model => model.Name)
            </div>
    
            <div class="editor-label">
                @Html.LabelFor(model => model.Description)
            </div>
            <div class="editor-field">
                @Html.EditorFor(model => model.Description)
                @Html.ValidationMessageFor(model => model.Description)
            </div>
    
            <p>
                <input type="submit" value="Save" />
            </p>
        </fieldset>
    }
    

    When I browse to /test/foo and hit the Save, I get a validation error on Name.

    When I browse to /test/bar and hit the Save, I get a validation error on Description.