0

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.

  • You taking the wrong approach and none of this code really makes any sense (and the code in your `@for (int i = 0; i < Model.Rooms.Count(); i++)` loop is generating `name` attributes that have no relationship to your model and wont bind to anything. You either handle this by editing `Building` and its collection of `Room` in one action (refer [this answer](http://stackoverflow.com/questions/28019793/submit-same-partial-view-called-multiple-times-data-to-controller/28081308#28081308)) which allows you to dynamically add new collection items. –  Oct 13 '16 at 23:47
  • Or you create a building in one form, and once created, you redirect to a view to create its room(s). But why your creating `Room` objects that have no relationship to the associated buildings is unclear. You `Room` class needs a navigation property to `Building` otherwise the data is meaningless –  Oct 13 '16 at 23:49

3 Answers3

1

A validation error is thrown because of your property Building.Name which is [Required].

So you simply need to remove [Require] attribute, and @Html.ValidationMessageFor(t => t.Name).

Yurii N.
  • 5,455
  • 12
  • 42
  • 66
  • 1
    OP wants to show validation when 'Create' button is clicked.. if you take off the `[Required]` attribute, then the OP will not get the validation that they are looking for. – Grizzly Oct 12 '16 at 15:07
  • @BviLLe_Kid _My problem is that when 'Add Room' button is clicked, it throws a validation error in Name property of building._ doesn't it mean that he prefer not to validate `Building.Name`? – Yurii N. Oct 12 '16 at 15:11
  • what @BviLLe_Kid is right...i want Building.Name validation error when 'Create' button is clicked. – Lakshmi Arun Oct 13 '16 at 09:02
0

You are doing to much in your controller method. I would split your page into 2 partial views which have their own view models. Since they are 2 distinct functions that have different data requirements.

Each partial view should contain a separate form for each action. and then map the each form's submit button to separate actions on the controller.

Fran
  • 6,440
  • 1
  • 23
  • 35
0

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.