Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using an interface as the model type of a partial view + data annotations

I have a case where a complex partial view needs different validation of fields depending on where the partial view is used.

I thought I could get around this by making the partial view take an interface as the model type and implementing two different ViewModels based on the interface. The data annotations in the two ViewModels would be different. I would then supply an instance of the correct ViewModel to the partial view.

But what I'm finding is that the only annotations that are recognized are those on the interface itself. DAs on the interface-implementing ViewModel classes are ignored, even though those are the objects that are being passed as models. So my plan isn't working.

Is there a way around this? A better approach? I'd prefer not to split the partial view into separate views if I can avoid it.

ETA: This is an abbreviated version of the partial view, as requested:

@model IPerson
@Html.ValidationSummary(false)
<fieldset>
    <table class="editForm">
        <tr>
            <td class="editor-label">
                @Html.LabelFor(model => model.FirstName)
            </td>
            <td class="editor-field">
                @Html.EditorFor(model => model.FirstName)
                @Html.ValidationMessageFor(model => model.FirstName)
            </td>
            <td class="editor-label">
                @Html.LabelFor(model => model.LastName)
            </td>
            <td class="editor-field">
                @Html.EditorFor(model => model.LastName)
                @Html.ValidationMessageFor(model => model.LastName)
            </td>
        </tr>
     </table>
  <fieldset>

The real partial view is quite long and has a lot of @if statements managing the rendering (or not) of optional sections, but it doesn't do anything tricky.

like image 315
Ann L. Avatar asked Mar 27 '12 20:03

Ann L.


3 Answers

My idea isn't going to work: this thread reminded me that classes don't inherit attributes from their interfaces. (As the answer points out, what would happen if two interfaces specified the same property with different attributes, and both were implemented by one class?)

It might work with a common base class. I will try that tomorrow.

Thanks, everybody.

like image 77
Ann L. Avatar answered Nov 15 '22 16:11

Ann L.


Ann, you're right. I've deleted my comment. You can't post an interface back through your view. However, I don't know what exactly your trying to do since I can't see your code. Maybe something like this? I'm passing an interface to the view, but passing it back as the class I'm expecting. Again, I'm not sure the application is here.

Let's say you have classes like this:

[MetadataType(typeof(PersonMetaData))]
public class Customer : IPerson {
    public int ID { get; set; }
    public string Name { get; set; }
     [Display(Name = "Customer Name")]
    public string CustomerName { get; set; }
}

public class Agent : IPerson {
    public int ID { get; set; }
    public string Name { get; set; }
}

public partial class PersonMetaData : IPerson {
    [Required]
    public int ID { get; set; }

    [Required]
    [Display(Name="Full Name")]
    public string Name { get; set; }
}

public interface IPerson {
    int ID { get; set; }
    string Name { get; set; }
}

public interface IAgent {
    int AgentType { get; set; }
}

public interface ICustomer {
    int CustomerType { get; set; }
}

Your Controller looks like:

    public ActionResult InterfaceView() {
        IPerson person = new Customer {
            ID = 1
        };
        return View(person);
    }

    [HttpPost]
    public ActionResult InterfaceView(Customer person) {
        if (ModelState.IsValid) {
            TempData["message"] = string.Format("You posted back Customer Name {0} with an ID of {1} for the name: {2}", person.CustomerName, person.ID, person.Name);
        }
        return View();
    }

And your View Looks like this:

@model DataTablesExample.Controllers.Customer

<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>

@if (@TempData["message"] != null) {
    <p>@TempData["message"]</p>
}

@using (Html.BeginForm()) {
    @Html.ValidationSummary(true)
    <fieldset>
        <legend>IPerson</legend>

        @Html.HiddenFor(model => model.ID)

        <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.CustomerName)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.CustomerName)
            @Html.ValidationMessageFor(model => model.CustomerName)
        </div>

        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>
}
like image 25
anAgent Avatar answered Nov 15 '22 16:11

anAgent


Well, actually you have a very reasonable idea! and can be archived is you use the non generic version of the HtmlHelper methods (ex. "@Html.Editor" instead of "@Html.EditorFor"), because the generic versions recreate the ModelMetadata (i don't know why!) based on the generic parameter type and don't use the ModelMetadata of the view. Freaking awful, isn't it?

Hope this help.

like image 32
Rafael Peña García Avatar answered Nov 15 '22 16:11

Rafael Peña García