1

I have an Asp.Net MVC 3 application with a database "Consultants", accessed by EF. Now, the Consultant table in the db has a one-to-many relationship to several other tables for CV type information (work experience, etc). So a user should be able to fill in their name etc once, but should be able to add a number of "work experiences", and so on.

But these foreign key tables are complex objects in the model, and when creating the Create View I only get the simple properties as editor fields. How do I go about designing the View or Views so that the complex objects can be filled in as well? I picture a View in my mind where the simple properties are simple fields, and then some sort of control where you can click "add work experience", and as many as needed would be added. But how would I do that and still utilize the model binding? In fact, I don't know how to go about it at all. (BTW, Program and Language stand for things like software experience in general, and natural language competence, not programming languages, in case you're wondering about the relationships there).

Any ideas greatly appreciated!

Here's the Create View created by the add View command by default:

@{
    ViewBag.Title = "Create";
}

<h2>Create</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>Consultant</legend>

        <div class="editor-label">
            @Html.LabelFor(model => model.FirstName)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.FirstName)
            @Html.ValidationMessageFor(model => model.FirstName)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.LastName)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.LastName)
            @Html.ValidationMessageFor(model => model.LastName)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.UserName)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.UserName)
            @Html.ValidationMessageFor(model => model.UserName)
        </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="Create" />
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

And here's the EF database diagram:

Consultants EF diagram

Update:

According to suggestion, I tried Steven Sanderson's blog solution, but I can't get it to work properly. I added this in the main view:

    <div id="editorRows">
        @foreach (var item in Model.Programs)
        {
            Html.RenderPartial("ProgramEditorRow", item);
        }
    </div>
<input type="button" value="Add program" id="addItem" />

I also added the javascript:

<script type="text/javascript">
    var url = '@Url.Action("BlankEditorRow", "Consultant")';
    $(document).ready(function () {
        $("#addItem").click(function () {
            $.ajax({

                url: url,
                cache: false,
                success: function (html) {
                    alert(html);
                 $("#editorRows").append(html); }
            });
            return false;
        });
    });
</script>

A partial view:

@model Consultants.Models.Program
@using Consultants.Helpers

<div class="editorRow">
@*@using (Html.BeginCollectionItem("programs"))
{*@
    @Html.TextBoxFor(model => model.Name)    
@*}*@
</div>

And action methods in the controller:

    public ActionResult Create()
    {
        Consultant consultant = new Consultant();

        return View(consultant);
    }


    public ActionResult BlankEditorRow()
    {
        return PartialView("ProgramEditorRow", new Program());
    }

Note that I commented out the Html.BeginCollectionItem part in the partial view, because I can't get that to work. It only gives me the hidden field Steven Sanderson talks about, but not the actual textbox. So I tried commenting that part out and just had a textbox. Well, that gets me the textbox, but I can't get to that info in the post method. I use the Consultant object as return parameter, but the Programs property contains no Program. Perhaps this has to do with the fact that I cannot get the BeginCollectionItem helper to work, but in any case I don't understand how to do that, or how to get to the Program supposedly added in the view. With a simple object I would add the new object in the post method by something like _repository.AddToConsultants(consultant), and when I save to EF it gets its id. But how do I do the same thing with the program object and save it to the database via EF?

Anders
  • 12,556
  • 24
  • 104
  • 151

2 Answers2

1

Phil Haack has written a great blog post which explains how to model bind to a list. It's specific to MVC2, but I'm not sure if version 3 has improved on this.

Model Binding To A List.

In a scenario where the user is allowed to add an arbitrary number of items, you'll probably want to create new input fields using JavaScript. Steven Sanderson shows here how to achieve that:

Editing a variable length list, ASP.NET MVC 2-style.

Those resources should get you all the way there.

jevakallio
  • 35,324
  • 3
  • 105
  • 112
0

You may take a look at the following blog post about writing custom object templates. Also don't use EF models in your views. Design view models that are classes specifically tailored to the needs of a given view and have your controller map between the EF models and the view models that should be passed to the view. AutoMapper is a good tool that could simplify this mapping.

Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • 1
    Thanks for the suggestion. I get that one should use ViewModels in many cases, but when I want to create a new Consultant, would I gain anything by creating a whole new ViewModel containing basically the same properties? Or how do you mean that ViewModel should look? – Anders Feb 16 '11 at 13:01