Previous Post: MVC and Azure for the MVVM/WPF Dev, Part 1 – Getting Started
Up next in this series is covering the model, view and controller objects that were created in the tutorial, Deploy a Secure ASP.NET MVC 5 app with Membership, OAuth, and SQL Database to an Azure Web Site.
Getting There
How does all the MVC views and view controller work? Well, let’s start with getting to the index view from the main page. The tutorial originally told you to replace the home with a call to the Cm controller it had you create. As I mentioned previously it seemed odd to call it Cm and so I called mine ContactsController. And rather then replacing the main link to the home page I added a new link. My “/Views/Shared/_Layout.cshtml” ends up looking like:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@ViewBag.Title - My ASP.NET Application</title>
@Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/modernizr")
</head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
@Html.ActionLink("ExperimentalProducts", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li>@Html.ActionLink("About", "About", "Home")</li>
<li>@Html.ActionLink("Contact", "Contact", "Home")</li>
<li>@Html.ActionLink("Contact Manager", "Index", "Contacts")</li>
</ul>
@Html.Partial("_LoginPartial")
</div>
</div>
</div>
<div class="container body-content">
@RenderBody()
<hr />
<footer>
<p>© @DateTime.Now.Year - My ASP.NET Application</p>
</footer>
</div>
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/bootstrap")
@RenderSection("scripts", required: false)
</body>
</html>
This makes it so I now have a “Contact Manager” link in my nav bar. We need to understand what is going on here. The @Html object is an MVC helper object to set up links and make your life easier when working with html. We’re creating a link that we want to perform some sort of action. In our case we want to go to the “ContactsController” and tell it we want it to perform an Index action. If you look at the third line in the navbar you can see that we have added a link that looks like:
<li>@Html.ActionLink("Contact Manager", "Index", "Contacts")</li>
The first parameter is the string to display for the link. The second parameter is the action we want to perform, in our case we want to display the index. The third parameter is the controller we want to call. Note that part of the name, Controller, is missing. I don’t know what would happen if you created a name without the Controller part appended.
MVC then takes care of wiring everything up so that when you click on the link we end up at “/Views/Contacts/Index.cshtml”.
The Model
The Contact model is straightforward and working with models is the same as if you were using models in MVVM. We’re using Entity Framework on the back-end and it all works almost the same as when I’ve used it in desktop applications. You’ll find the DbContext in “/Models/IdentityModels.cs”.
There are really only two differences from how I use the DbContext with desktop applications. The first is that when working with with desktop applications I tend to extend the DbContext to take a connection string. The auto-generated constructor is empty but will call base with connection string name “DefaultConnection”. I often like to expose this so I can allow my users to dynamically change the connection string dependent on the needs of the application. There may very well be the same need here but I don’t see the need in the near future. The second difference is that we’re obviously using a web.config instead of an app.config. I’m going to assume that you understand the differences between the web.config in the root of the project and an app.config. I will be covering “Views/Web.config” separately in a different post.
The Controller
Before I go into the views we need to take a look at the controller. But before we even do that we need to understand HTTP request methods. This is how the browser makes a request to the server. We’re primarily concerned with two methods, GET and POST. Generally GET is performed by clicking a link and POST is performed by submitting a form via a button at the bottom of the page. You don’t have to perform the request methods in this manner but it’s behavior users will expect.
It is important to take to heart, as a matter of convention, the purpose of GET and POST. GET should only be for getting data. POST, conversely, should be for making changes to data. This is incredibly important and stems from lessons learned in my java servlet/asp.net days. Like most conventions in software development, they are needed in order to make maintenance and the continued operation of an application (whether desktop or web) easier and with a minimal amount of defects.
[Authorize]
public class ContactsController : Controller
{
private ApplicationDbContext db = new ApplicationDbContext();
// GET: Contacts
public ActionResult Index()
{
return View(db.Contacts.ToList());
}
// GET: Contacts/Details/5
public ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Contact contact = db.Contacts.Find(id);
if (contact == null)
{
return HttpNotFound();
}
return View(contact);
}
// GET: Contacts/Create
public ActionResult Create()
{
return View();
}
// POST: Contacts/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ContactId,Name,Address,City,State,Zip,Email")] Contact contact)
{
if (ModelState.IsValid)
{
db.Contacts.Add(contact);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(contact);
}
// GET: Contacts/Edit/5
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Contact contact = db.Contacts.Find(id);
if (contact == null)
{
return HttpNotFound();
}
return View(contact);
}
// POST: Contacts/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "ContactId,Name,Address,City,State,Zip,Email")] Contact contact)
{
if (ModelState.IsValid)
{
db.Entry(contact).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(contact);
}
// GET: Contacts/Delete/5
public ActionResult Delete(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Contact contact = db.Contacts.Find(id);
if (contact == null)
{
return HttpNotFound();
}
return View(contact);
}
// POST: Contacts/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
Contact contact = db.Contacts.Find(id);
db.Contacts.Remove(contact);
db.SaveChanges();
return RedirectToAction("Index");
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
}
If you’re comparing along with your own code from the tutorial you’ll see that there is one difference. I added a “[Authorize]” tag at the top of my class. This makes it so that the only way to enter this controller is if the user is authorized. You can take this attribute a step further and specify specific roles that may access the controller. There are a few things to take note of:
- Each method ties to an action, which in turn ties to a view
- Each method returns an ActionResult
- Each method defaults to a get request
- You must explicitly identify post requests with the attribute, “HttpPost”
- All post requests have the attribute, “ValidateAntiForgeryToken”
The first point, “Each method ties to an action” is incredibly important. Remember a view ties to an action in the controller. What started this was we were trying to get to the Index action for the link we added in “/Views/Shared/_Layout.cshtml”.
The Views
When doing MVVM development there tends to be a one to one relationship between the view model and the view. This isn’t neccasarily true of models as they tend to get used as needed but of view models and views it is.
Take a look at “/Views/Contacts/” and you’ll see that there are 5 views. Each view relates to an action that can be performed on the model. Index lists all instances of the model, and the rest are obvious. This is in stark contrast to how I tend to do MVVM dev. Here is how I would do this in MVVM:
ContactsListView that contains a data context of a ContactsListViewModel. The view model would have a list of contacts I would bind to the list in the view. There would be an empty panel in the view for another view that would be for editing of contacts. At the top of the grid would be a “Create New” button. Selecting a row in the list would set a “SelectedContact” in the view model that would cause the empty panel to populate with a ContactView.
ContactView that contains a data context of a ContactViewModel that would contain the Contact model. It would contain all details on the Contact and would allow you to create, delete and edit the contact.
In MVVM for this operation you end up with two views and two view models. There are a few more “fancy” ways to do this but the above implementation I could probably do in a trivial amount of time.
But here in MVC world we have 5 views and only one controller. Could we do it the same way as I would do it with MVVM? I don’t know, probably something to look at.
So in our ContactsController we have “public ActionResult Index()” and in our views we have Index.cshtml.
@model IEnumerable<ExperimentalProducts.Models.Contact>
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
@Html.ActionLink("Create New", "Create")
</p>
<table class="table">
<tr>
<th>
@Html.DisplayNameFor(model => model.Name)
</th>
<th>
@Html.DisplayNameFor(model => model.Address)
</th>
<th>
@Html.DisplayNameFor(model => model.City)
</th>
<th>
@Html.DisplayNameFor(model => model.State)
</th>
<th>
@Html.DisplayNameFor(model => model.Zip)
</th>
<th>
@Html.DisplayNameFor(model => model.Email)
</th>
<th></th>
</tr>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Address)
</td>
<td>
@Html.DisplayFor(modelItem => item.City)
</td>
<td>
@Html.DisplayFor(modelItem => item.State)
</td>
<td>
@Html.DisplayFor(modelItem => item.Zip)
</td>
<td>
@Html.DisplayFor(modelItem => item.Email)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.ContactId }) |
@Html.ActionLink("Details", "Details", new { id=item.ContactId }) |
@Html.ActionLink("Delete", "Delete", new { id=item.ContactId })
</td>
</tr>
}
</table>
I glossed over “@” before but I need to address it here. In the cshtml @ indicates a start of a code block. This is very similar to using <?php and ?> in php and <% and %> in asp.net. For more information on the syntax read Introduction to ASP.NET Web Programming Using the Razor Syntax (C#).
So let’s walk through this code.
- Line 01: Declare the type for the WebViewPage<TModel> (which is what this view page inherits) is of type IEnumerable<ExperimentalProducts.Models.Contact>.
- This is the type that will be used for “Model” below.
- Line 04: Set the ViewBag.Title to “Index”.
- Lines 13 – 33: Display the columns for each of the properties on the model.
- Line 15: Use the Html helper object to display the name for the Name property of our model.
- These are lambda expressions and could just as easily be “x => x.Name”.
- Try it yourself. Change all of your “model => model.Property” to “x => x.Property” and you’ll see that the page still loads/works just fine.
- By default it will use the property name but you can specify your own name in the model by adding the attribute, “[Display(Name = “First/Last Name”)]”. This would therefore display “First/Last Name” instead of just “Name”.
- Lines 35 – 61: Iterate over each item in Model displaying the values.
- Model gets set with the results from the Index method in the controller and is a property of WebViewPage<TModel>. Rather like data binding to a data context.
- Type of Model is determined by the “@model” type set at the top of the page.
- Where does “modelItem” come from? This is a lambda express we’re passing into the “DisplayFor” method so what it’s name is irrelevant. It’s just like using “x => item.Name”.
- Line 56: Finally build the links passing in an anonymous object with the property “id”.
- The property name is important as it must match the parameter on the action method that is being called in the controller.
And that’s it. Just like working in MVVM there is quite a bit you just have to assume is going to get wired up correctly. The first time I looked over this cshtml I was utterly confused. Why have @model at the top and then use model in the code? But once you understand that these are just lambdas being passed into a method everything gets a bit less murky.
Up next I’ll cover the edit and create actions of the controller and the views so we’ll have a better understanding of what is happening in the back-end when everything gets wired up.
Thanks for reading,
Brian