3

I'm trying to create a RoomType record. Each RoomType have a collection of RoomTypeInfos, which are descriptions for every Language. Then, each RoomTypeInfo is associated to a Language record.

So to create a RoomType, I preload the object with one RoomTypeInfo for each Language.

Get action:

//
// GET: /RoomType/Create/IdHotel
[HttpGet]
public ActionResult Create(int id)
{
    List<Language> _Languages = __roomTypeRepository.GetLanguages().ToList<Language>();
    RoomType roomType = new RoomType { IdHotel = id };
    roomType.RoomTypeInfos = new System.Data.Linq.EntitySet<RoomTypeInfo>();
    foreach (Language Language in _Languages)
    {
        roomType.RoomTypeInfos.Add(new RoomTypeInfo { Language = Language });
    }
    return View(roomType);
}

Post action:

//
// POST: /RoomType/Create/
[HttpPost]
public ActionResult Create(RoomType roomType)
{
    try
    {
        if (ModelState.IsValid)
        {
            __roomTypeRepository.InsertRoomType(roomType);
            return RedirectToAction("Index", new { id = roomType.IdHotel });
        }
        else
        {
            return View(roomType);
        }
    }
    catch
    {
        return View(roomType);
    }
}

So if in the post action the model is not valid or an exception is caught, I get an error in @Model.Language.Name at the view, because Language is now null.

Code for the RoomTypeInfo editor template:

@model DataAccess.RoomTypeInfo
@Html.HiddenFor(model => model.Id)
@Html.HiddenFor(model => model.IdRoomType)
@Html.HiddenFor(model => model.IdLanguage)
<fieldset>
    <legend>@Model.Language.Name</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.Descripcion)
    </div>
    <div class="editor-field">
        @Html.TextAreaFor(model => model.Descripcion, new { Class = "wysiwyg" })
        @Html.ValidationMessageFor(model => model.Descripcion)
    </div>
</fieldset>

And the RoomType editor template:

@model DataAccess.RoomType
@Html.HiddenFor(model => model.Id)
@Html.HiddenFor(model => model.IdHotel)
<h3>
    Información Básica
</h3>
<div class="editor-field">
    @Html.EditorFor(model => model.RoomTypeInfos)
    @Html.ValidationMessageFor(model => model.RoomTypeInfos)
</div>

I fixed it by querying the Languages table and setting it again to each RoomTypeInfo.Language, but if feels like a workaround.

What is the best practice in this case?

2
  • Can you also post the cshtml code for the RoomType view, which renders these different RoomTypeInfo views? Commented Dec 15, 2011 at 17:26
  • Added the RoomType view. Commented Dec 15, 2011 at 19:31

1 Answer 1

2

We use a BeginCollectionItem HTML helper authored by Steve Sanderson for this. In the partial view, we would wrap all of the input fields in @using(Html.BeginCollectionItem("RoomTypeInfos")).

In the background, this changes the input element names and id's so that they are recognized by the default modelbinder and added to the root viewmodel (in your case RoomType).

An example for your code:

@model DataAccess.RoomTypeInfo
@using (Html.BeginCollectionItem("RoomTypeInfos")) 
{
    Html.HiddenFor(model => model.Id)
    Html.HiddenFor(model => model.IdRoomType)
    Html.HiddenFor(model => model.IdLanguage)
    <fieldset>
        <legend>@Model.Language.Name</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.Descripcion)
        </div>
        <div class="editor-field">
            @Html.TextAreaFor(model => model.Descripcion, new { Class = "wysiwyg" })
            @Html.ValidationMessageFor(model => model.Descripcion)
        </div>
    </fieldset>
}

Also, if you want to make sure that Language is not null after a POST, you should add it as a hidden field:

@Html.HiddenFor(m => m.Language.Name)

This way, it is bound to the model by the default modelbinder. If you don't send the data over HTTP POST (in some kind of input field), then your POST action method will have to repopulate it before returning the model & view.

Sign up to request clarification or add additional context in comments.

3 Comments

@Html.HiddenFor(m => m.Language.Name) partially fixes the error, but now it is trying to create a new Language record, so I get a LINQ exception because the Language object is missing required attributes. Also, BeginCollectionItem doesn't seem to be working for me.
Are you using your domain entities directly within your controller? If so, to satisfy the other required attributes, you have to pass them as well with @Html.HiddenFor(m => m.Language.PropName). This is a good reason why you should separate your viewmodel layer from your entities (we use automapper to translate). Keep trying with BeginItemCollection. Remember that the "CollectionName" argument has to match the CollectionName in your root entity type, that always stumped me.
Added the rest and now it creates a new Language record.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.