I'm a beginner to web programming and just started a MVC project from scratch. Because this will become a large project eventually, I would like to make sure that I'm doing things kind of right from the beginning.
The architecture is the following: ASP.NET 4.6, MVC 5, EF 6, Identity 2.0. I'm using EF Database First approach and Bootstrap 3.3.5. The solution is divided into 3 projects: Data (where I keep my .edmx and model classes), Resources (where I keep strings for localization purposes -and eventually images-), and Web (with my controllers, views, etc).
I'm going to point out a couple of examples in my code where I'm not sure about my approach. I have a navigation bar with an "Administration" link and two submenu links, "Users" and "Roles".
Users
When a user clicks on "Users", I'd like to show a table with four columns:
- Username
- Roles (string with the names of all roles assigned)
- Assign Role (button that will take you to another form)
- Remove Role (button that will take you to another form)
This is what the UserIndex.cshtml
view looks like:
@model List<MySolution.Data.DAL.ApplicationUser>
@{
ViewBag.Title = Resources.Users;
}
<h2>@Resources.Users</h2>
<hr />
<table class="table table-striped table-hover ">
<thead>
<tr>
<th>@Resources.User</th>
<th>@Resources.Roles</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var user in Model)
{
<tr>
<td>
@user.UserName
</td>
<td>
@user.DisplayRoles()
</td>
<td>
@using (Html.BeginForm("UserAssignRole", "Admin", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Get, new { @class = "form-horizontal", role = "form" }))
{
@Html.AntiForgeryToken()
@Html.HiddenFor(m => m.Where(u => u.Id.Equals(user.Id)).FirstOrDefault().UserName)
<input type="submit" value="@Resources.AssignRole" class="btn btn-default btn-sm" />
}
</td>
<td>
@using (Html.BeginForm("UserRemoveRole", "Admin", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Get, new { @class = "form-horizontal", role = "form" }))
{
@Html.AntiForgeryToken()
@Html.HiddenFor(m => m.Where(u => u.Id.Equals(user.Id)).FirstOrDefault().UserName)
<input type="submit" value="@Resources.RemoveRole" class="btn btn-default btn-sm" />
}
</td>
</tr>
}
</tbody>
</table>
I added a DisplayRoles()
method to my ApplicationUser
class that returns a string with the list of assigned roles separated by commas, so that I can plug it directly into the user table in my view. I'm not sure at all about this approach; it does work, but putting logic like that in my model just seems kind of weird. I just haven't figured a better way to do this.
Then, on my controller, I have the following:
//
// GET: /Admin/UserIndex
[Authorize(Roles = "Admin")]
public ActionResult UserIndex()
{
var users = context.Users.ToList();
return View(users);
}
//
// GET: /Admin/UserAssignRole
[HttpGet]
//[ValidateAntiForgeryToken]
public ActionResult UserAssignRole(UserAssignRoleViewModel vm)
{
ViewBag.Username = vm.Username;
ViewBag.Roles = context.Roles.OrderBy(r => r.Name).ToList().Select(rr => new SelectListItem { Value = rr.Name.ToString(), Text = rr.Name }).ToList();
return View("UserAssignRole");
}
//
// POST: /Admin/UserAssignRole
[HttpPost]
//[ValidateAntiForgeryToken]
[ActionName("UserAssignRole")]
public ActionResult UserAssignRolePost(UserAssignRoleViewModel vm)
{
ApplicationUser user = context.Users.Where(u => u.UserName.Equals(vm.Username, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();
this.UserManager.AddToRole(user.Id, vm.Role);
return RedirectToAction("UserIndex");
}
With my UserAssignRoleViewModel
looking like this:
/// <summary>
/// Views\Admin\UserAssignRole.cshtml
/// </summary>
public class UserAssignRoleViewModel
{
[Display(Name = "Username", ResourceType = typeof(Resources))]
public string Username { get; set; }
[Display(Name = "Role", ResourceType = typeof(Resources))]
public string Role { get; set; }
}
And the UserAssignRole
view being this:
@model UserAssignRoleViewModel
@{
ViewBag.Title = Resources.AssignRole;
}
<h2>@Resources.AssignRole</h2>
<hr />
<div class="row">
<div class="col-md-8">
<section id="assignRoleForm">
@using (Html.BeginForm("UserAssignRole", "Admin", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
@Html.AntiForgeryToken()
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
<div class="form-group">
@Html.LabelFor(m => m.Username, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.TextBoxFor(m => m.Username, new { @class = "form-control" , @readonly = "readonly" })
@Html.ValidationMessageFor(m => m.Username, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(m => m.Role, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.DropDownListFor(m => m.Role, (IEnumerable<SelectListItem>)ViewBag.Roles, Resources.DropdownSelect, new { @class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="@Resources.Assign" class="btn btn-default" />
</div>
</div>
}
</section>
</div>
</div>
I especially am not sure about the way that I use my controller actions, and how I'm calling them from my forms. And does it make sense to have a Get and Post method for the same action, or should I be doing something else?
Roles
The "Roles" section is very similar, with a table with three columns:
- Name
- Edit (button that will take you to another form to rename the role)
- Delete button (button that will show a modal asking for verification)
On top of the table, there's a separate button allowing the user to add a new role.
Here's my RoleIndex.cshtml
view.
@model IEnumerable<Microsoft.AspNet.Identity.EntityFramework.IdentityRole>
@{
ViewBag.Title = Resources.Roles;
}
<h2>@Resources.Roles</h2>
<hr />
@using (Html.BeginForm("RoleCreate", "Admin", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Get, new { @class = "form-horizontal", role = "form" }))
{
<input type="submit" value="@Resources.CreateRole" class="btn btn-default btn-sm" />
}
<hr />
<table class="table table-striped table-hover ">
<thead>
<tr>
<th>@Resources.Role</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var role in Model)
{
<tr>
<td>
@role.Name
</td>
<td>
@using (Html.BeginForm("RoleEdit", "Admin", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Get, new { @class = "form-horizontal", role = "form" }))
{
@Html.AntiForgeryToken()
@Html.HiddenFor(m => m.Where(r => r.Id.Equals(role.Id)).FirstOrDefault().Name)
<input type="submit" value="@Resources.Edit" class="btn btn-default btn-sm" />
}
</td>
<td>
<input type="submit" value="@Resources.Delete" class="btn btn-default btn-sm" data-toggle="modal" data-target="#confirm-delete"/>
<div class="modal fade" id="confirm-delete" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
@Resources.DeleteRole
</div>
<div class="modal-body">
@Resources.AreYouSureYouWantToDelete
</div>
<div class="modal-footer">
@using (Html.BeginForm("RoleDelete", "Admin", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
@Html.AntiForgeryToken()
@Html.HiddenFor(m => m.Where(r => r.Id.Equals(role.Id)).FirstOrDefault().Name)
<button type="button" class="btn btn-default" data-dismiss="modal">@Resources.Cancel</button>
<input type="submit" value="@Resources.Delete" class="btn btn-danger btn-ok" />
}
</div>
</div>
</div>
</div>
</td>
</tr>
}
</tbody>
</table>
Here's my RoleCreateViewModel
/// <summary>
/// Views\Admin\RoleCreate.cshtml
/// </summary>
public class RoleCreateViewModel
{
[Required]
[Display(Name = "Name", ResourceType = typeof(Resources))]
public string Name { get; set; }
}
and RoleCreate
actions
//
// GET: /Admin/RoleCreate
[HttpGet]
[Authorize(Roles = "Admin")]
public ActionResult RoleCreate()
{
return View();
}
//
// POST: /Admin/RoleCreate
[HttpPost]
[Authorize(Roles = "Admin")]
public ActionResult RoleCreate(RoleCreateViewModel vm)
{
context.Roles.Add(new IdentityRole()
{
Name = vm.Name
});
context.SaveChanges();
ViewBag.ResultMessage = Resources.RoleCreatedSuccessfully;
return RedirectToAction("RoleIndex");
}
and RoleCreate.cshtml
view
@model RoleCreateViewModel
@{
ViewBag.Title = Resources.CreateRole;
}
<h2>@Resources.CreateRole</h2>
<hr />
<div class="row">
<div class="col-md-8">
<section id="createRoleForm">
@using (Html.BeginForm("RoleCreate", "Admin", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
@Html.AntiForgeryToken()
@Html.ValidationSummary(true)
<div class="form-group">
@Html.LabelFor(m => m.Name, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.TextBoxFor(m => m.Name, new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.Name, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="@Resources.Save" class="btn btn-default" />
</div>
</div>
}
</section>
</div>
</div>
Please do critique.