How to reuse model and view for Add and Edit in the administration MVC 4 app

3.3k views Asked by At

I am building MVC 4 application with C# and i have been doing a lot of add and edit views which are quite similar to each other. My question is how to reuse the same view with the model for the object returned by filling the form for add and edit of the items. Here is and example which i do in my application :

@model namespace.Register
@{
    ViewBag.Title = "Manage User";
}

<div class="col1 manage-user">
<div>   
    @using (Html.BeginForm())
    {
        <h1>Add User</h1>
        <div id="error" class="clear">@Html.ValidationSummary(true)</div>
        <ul class="clear">
            <li><label for="UserName">Username:</label>
                @Html.EditorFor(x => x.UserName)
            </li>
            <li><label for="Password">Password:</label>
                @Html.EditorFor(x => x.Password)
            </li>
            <li><label for="ConfirmPassword">Confirm password:</label>
                @Html.EditorFor(x => x.ConfirmPassword)
            </li>
            <li>
                <label for="level">Level:</label>
                @Html.DropDownList("levels", Model.Elements.Levels)
            </li>
        </ul>

        <div class="buttons">
            <input type="submit" class="button create" value="Save" name="Save"/>
            <input type="submit" class="button back" value="Back" name="Back" formnovalidate/>
        </div>
    }
</div>

and my Add Method :

public ActionResult AddUser()
    {
        Levels levels = new Levels(MyRepository.GetLevelsForUser(User.Identity.Name));
        Register model = new Register(levels);
        return View(register);
    }

    [HttpPost]
    public ActionResult AddUser(Register model, FormCollection form)
    {
        if(form["Back"] != null)
        {
            return RedirectToAction("UserList");
        }

        if (ModelState.IsValid)
        {
        ... add user logic
        }    

        // If we got this far, something failed, redisplay form
        ModelState.AddModelError("", "Please enter valid username and password.");
        return View(model);
    }

When i edit i need to open the same view as AddUser but filled with the proper properties in the model and post to save the changes in the model. Currently i use ViewData["Username"] , ||-|| "Password", and so to do this is there a better way to reuse this AddUser view ?

The register model is as the following

public class Register
{       
    [Required]
    [Display(Name = "User name")]
    public string UserName { get; set; }

    [Required]
    [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
    [DataType(DataType.Password)]
    [Display(Name = "Password")]
    public string Password { get; set; }

    [DataType(DataType.Password)]
    [Display(Name = "Confirm password")]
    [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
    public string ConfirmPassword { get; set; }

    public Levels Levels { get; set; }

    public Register()
    {
        UserName = null;
        Password = null;
        ConfirmPassword = null;
        Levels = null;
    }

    public Register(Levels levels)
    {
        UserName = null;
        Password = null;
        ConfirmPassword = null;
        Levels = levels;
    }   
}
1

There are 1 answers

1
Alex On BEST ANSWER

I usually leverage the EditorTemplate functionality for this purpose, decoupling the common parts in a dedicated Partial View.

Follows an example based on your code (notice the BeginForm tags which differ in each one, while the form "meat" is digged up from the partial):

Note: this is just a sample, it probably can be stripped out more but it's more of a proof of concept.

Add view cshtml

@model namespace.Register
@{
    ViewBag.Title = "Manage User";
}

<div class="col1 manage-user">
<div>   
    @using (Html.BeginForm("AddUser", "YourController", FormMethod.Post))
    {
        <h1>Add User</h1>

        @Html.EditorFor(model => Model)

        <div class="buttons">
            <input type="submit" class="button create" value="Create user" name="Save"/>
            <input type="submit" class="button back" value="Back" name="Back" formnovalidate/>
        </div>
    }
</div>

Edit view cshtml

@model namespace.Register
@{
    ViewBag.Title = "Manage User";
}

<div class="col1 manage-user">
<div>   
    @using (Html.BeginForm("UpdateUser", "YourController", FormMethod.Post)
    {
        <h1>Edit User</h1>

        @Html.EditorFor(model => Model)

        <div class="buttons">
            <input type="submit" class="button create" value="Update user" name="Save"/>
            <input type="submit" class="button back" value="Back" name="Back" formnovalidate/>
        </div>
    }
</div>

Both views above would leverage this file (the name is not made up, it's dictated by MVC naming convention).

\Views\Shared\EditorTemplates\Register.cshtml

@model namespace.Register
<div id="error" class="clear">@Html.ValidationSummary(true)</div>
<ul class="clear">
    <li><label for="UserName">Username:</label>
        @Html.EditorFor(x => Model.UserName)
    </li>
    <li><label for="Password">Password:</label>
        @Html.EditorFor(x => Model.Password)
    </li>
    <li><label for="ConfirmPassword">Confirm password:</label>
        @Html.EditorFor(x => Model.ConfirmPassword)
    </li>
    <li>
        <label for="level">Level:</label>
        @Html.DropDownList("levels", Model.Elements.Levels)
    </li>
</ul>

This also works for displaying data, the exact same principle applies but the partial goes in \Views\Shared\DisplayTemplates and is used through DisplayFor().

You can give the Templates any name you want, but if it doesn't match the type you'll have to use the overloads of EditorFor / DisplayFor which take the view name as parameter.