Lakshmi Arun Lakshmi Arun - 22 days ago 8
C# Question

Mvc form with viewmodel properties contain list<object>

I'm a newbie to MVC5. I want to add building details to database. The building details include name, list of room details, etc.. The user can add building details such as name, and list of room details. After adding all details the list of room and building details are saved to database.

Add Building Details Image

I have form with a view-model as below

BuildingViewModel.cs

public class Building
{
public Building()
{
Rooms = new List<Room>();
NewRoom = new Room();
}

[Required]
public string Name { get; set; }

public List<Room> Rooms { get; set; }

public Room NewRoom { get; set; }

public string Button { get; set; }
}

public class Room
{
[Required]
public string RoomName { get; set; }

public string Area { get; set; }
}


This is my view Create.cshtml

@model Building
@{
ViewBag.Title = "Create";
}

@using (Html.BeginForm())
{
<h2>Create</h2>

<h3>Building</h3>
@Html.LabelFor(t => t.Name)
@Html.TextBoxFor(t => t.Name)
@Html.ValidationMessageFor(t => t.Name)

<br />
<br />

<fieldset>
<legend>Room</legend>

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

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

<p>
<input type="submit" name="button" value="Add Room" />
</p>
</fieldset>

<br />
<table border="1" cellpadding="5px" cellspacing="0">
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>Description</th>
</tr>
</thead>
<tbody>
@for (int i = 0; i < Model.Rooms.Count(); i++)
{
var room = Model.Rooms[i];
<tr>
<td>@(i + 1)</td>
<td>@room.RoomName @Html.HiddenFor(t => room.RoomName)</td>
<td>@room.Area @Html.HiddenFor(t => room.Area)</td>
</tr>
}
</tbody>
</table>
<br />

<input type="submit" name="button" value="Create" />
}
<script src="~/Scripts/jquery-1.8.0.min.js"></script>
<script src="~/Scripts/jquery.validate.min.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>


And this is my controller BuildingController.cs

public class BuildingController : Controller
{
[HttpGet]
public ActionResult Create()
{
var building = new Building();
return View(building);
}

[HttpPost]
public ActionResult Create(Building building)
{
switch (building.Button)
{
case "Add Room":
if (ModelState.IsValid)
{
building.Rooms.Add(building.NewRoom);
building.NewRoom = new Room();

return View(building);
}
break;

case "Create":
if (ModelState.IsValid)
{
// TODO: save to db

return View();
}
break;
}

return View(building);
}
}


My problem is that when 'Add Room' button is clicked, it throws a validation error in Name property of building. What i want is, show validation only for Room class properties when 'Add Room' button is clicked, and show validation error for Building property when 'Create' button is clicked.

Add Building Validation Error Image

I'm spending more than 2 week for this problem. Please help me...

Thanks for your valuable time.

Answer

Finally I found the solution using partial view and Ajax URL post.

Here is the solution :-)

BuildingController.cs

public class BuildingController : Controller
{
    [HttpGet]
    public ActionResult Create()
    {
        var building = new Building();
        return View(building);
    }

    [HttpPost]
    public ActionResult Create(Building building)
    {
        switch (building.Button)
        {
            case "Add Room":
                if (ModelState.IsValid)
                {
                    building.Rooms.Add(building.NewRoom);
                    building.NewRoom = new Room();
                    ModelState.Clear();
                    return PartialView("_Room", building);
                }
                break;

            case "Create":
                if (ModelState.IsValid)
                {
                    // TODO: save to db

                    return Json("Building created successfully.", JsonRequestBehavior.AllowGet);
                }
                break;
        }

        return View(building);
    }
}

BuildingViewModel.cs

public class Building
{
    public Building()
    {
        Rooms = new List<Room>();
        NewRoom = new Room();
    }

    public string Button { get; set; }

    //[Required(AllowEmptyStrings = false, ErrorMessage = "The building name is required.")]
    public string Name { get; set; }

    public Room NewRoom { get; set; }
    public List<Room> Rooms { get; set; }
}

public class Room
{
    //[Required(AllowEmptyStrings = false, ErrorMessage = "The room name is required.")]
    public string RoomName { get; set; }

    public string Area { get; set; }
}

Create.cshtml

@model Building
@{
    ViewBag.Title = "Create";
 }

 @using (Html.BeginForm("Create", "Building", FormMethod.Post, new { @id = "commentForm" }))
{
<h2>Create</h2>

<h3>Building</h3>
@Html.LabelFor(t => t.Name)
@Html.TextBoxFor(t => t.Name, htmlAttributes: new { @class = "create" })
@Html.ValidationMessageFor(t => t.Name)  

<br />
<br />

<div id="rooms">
    @Html.Partial("_Room", Model)
</div>

<br />

<button onclick="create()" >Create</button>
}
<script src="~/Scripts/jquery-1.8.2.min.js"></script>
<script src="~/Scripts/jquery.validate.min.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>

<script>
$(document).ready(function () {

    $(".create").removeAttr('required');
    $(".add-room").removeAttr('required');
});

function addRoom() {
    debugger;
    $(".add-room").removeAttr('required');
    $(".add-room").attr('required', true);

    var $valid = true;
    $('.add-room').each(function (i, obj) {
        var a = $(this).valid();
        if (!a) {
            $valid = a;
        }
    });

    if (!$valid) {
        return false;
    }
    else {

        var data = $("#commentForm").serializeArray();
        data.push({ name: 'Button', value: 'Add Room' });

        $.ajax({
            url: '@Url.Action("Create", "Building")',
            type: 'post',
            data: data,
            success: function (data) {
                debugger;
                $('#rooms').html(data);

                $(".add-room").removeAttr('required');
            },
            error: function (xhr, status, error) {
                debugger;
                alert(error);
            }
        });

    }
}

function create() {

    $(".add-room").removeAttr('required');
    $(".create").removeAttr('required');
    $(".create").attr('required', true);

    var $valid = true;
    $('.create').each(function (i, obj) {
        var a = $(this).valid();
        if (!a) {
            $valid = a;
        }
    });

    if (!$valid) {
        return false;
    }
    else {
        debugger;
        var data = $("#commentForm").serializeArray();
        data.push({ name: 'Button', value: 'Create' });

        $.ajax({
            url: '@Url.Action("Create", "Building")',
            type: 'post',
            data: data,
            success: function (data) {
                debugger;
                alert(data);
            },
            error: function (xhr, status, error) {
                debugger;
                alert(error);
            }
        });

    }
}
</script>

And finally partial view _Room.cshtml

@model Building

<fieldset>
<legend>Room</legend>

<div class="editor-label">
    @Html.LabelFor(model => model.NewRoom.RoomName)
</div>
<div class="editor-field">
    @Html.TextBoxFor(model => model.NewRoom.RoomName, htmlAttributes: new { @class = "add-room" })
    @Html.ValidationMessageFor(model => model.NewRoom.RoomName)
</div>

<div class="editor-label">
    @Html.LabelFor(model => model.NewRoom.Area)
</div>
<div class="editor-field">
    @Html.TextBoxFor(model => model.NewRoom.Area, htmlAttributes: new { @class = "add-room" })
    @Html.ValidationMessageFor(model => model.NewRoom.Area)
</div>

<p>
    <button onclick="addRoom();">Add Room</button>
</p>
</fieldset>

<br />
<table border="1" cellpadding="5px" cellspacing="0">
<thead>
    <tr>
        <th>#</th>
        <th>RoomName</th>
        <th>Area</th>
    </tr>
</thead>
<tbody>
    @for (int i = 0; i < Model.Rooms.Count(); i++)
    {
        <tr>
            <td>@(i + 1)</td>
            <td>@Model.Rooms[i].RoomName @Html.HiddenFor(t => Model.Rooms[i].RoomName)</td>
            <td>@Model.Rooms[i].Area @Html.HiddenFor(t => Model.Rooms[i].Area)</td>
        </tr>
    }
</tbody>
</table>

Thank you all.