As part of this post I will first create a simple MVC 4 standalone website from scratch. To add a bit of extra jazz, I am going to build it using Entity Framework Code First, using a SQL 2012 backend. I will then migrate and host both the site and database in Azure.
To start off, let us describe a very simple idea for a website. Assume we wish to create a simple site that will allow us to create and maintain a TODO list of items, basically something we can use to create, timestamp and manage things to do throughout our day. I must stress, its going to be a very simple solution for demonstration purposes. To start off with, within VS2010, create a simple MVC 4 solution, labelled ‘MyToDoApp’, using the Basic project template.
Creating the Entity Model
Once the solution is created, note that the project template should have already enabled Entity Framework for the solution. Verify this via the package manager console via the following commandThe next step, I am going to go ahead and create my entity model. To keep things simple, we only need 1 entity in our model, a Task entity to capture all the information we need. We create the entity class like so (note the namespace):
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Web;
5:
6: namespace MyToDoApp.Models
7: {
8: public class Task
9: {
10: public int Id { get; set; }
11:
12: public string Description { get; set; }
13:
14: public DateTime? DueBy { get; set; }
15:
16: public bool Completed { get; set; }
17: }
18: }
I purposely chose the class name ‘ToDoTask’ over the simple and obvious ‘Task’ mainly to avoid any conflicts with the .NET TPL library. Also I have left made the DueBy property nullable as we may have a task that does not need to be completed by some time framed (like mowing the lawn). My next step is to create a an EF context. We can do this like so:
1: using System;
2: using System.Collections.Generic;
3: using System.Data.Entity;
4: using System.Linq;
5: using System.Web;
6:
7: namespace MyToDoApp.Models
8: {
9: public class TaskListContext: DbContext
10: {
11: public DbSet<ToDoTask> Tasks { get; set; }
12: }
13: }
This is all the code we need to define out entity model (yes it is that awesome).
Using Entity Framework Code-First to push the entity schema to an external database
To mimic a real world scenario, I want Entity Framework to translate and host the entity model on my local SQL 2012 DB. I proceed by creating a simple database which we can label ‘ToDoListDb’. Once this is done, we need to instruct entity framework where it needs to point and create the DB schema to. Navigate to the Web.config in your solution, find the element labelled ‘connectionStrings’ & add a new connection entry, similarly labelled ‘ToDoListDb’, to our newly created DB as shown. You may comment out/delete the existing connection string entry labelled “DefaultConnection” (created by default by the project template)
<connectionStrings> <add name="ToDoListDb" providerName="System.Data.SqlClient" connectionString="Data Source=.;Initial Catalog=ToDoListDb;Integrated Security=True;Connect Timeout=15;Encrypt=False;TrustServerCertificate=False" /> </connectionStrings>
We may now proceed to translate our entity model to the database. Navigate to the Nuget Package manager console and run the following commands (in the same sequence). First we will instruct EF to enable code first migrations in our project, telling it that we would like to use the DB in the connection string we added previously to the web.config (Note: make sure you specify this parameter using double quotes)
PM> Enable-Migrations -ConnectionStringName "ToDoListDb"
Next we instruct EF code first to add a base migration to our project to reflect the current state of the entity model, again passing in the connection string as a parameter
PM> Add-Migration Base -ConnectionStringName "ToDoListDb"
Finally we ask it to update the database
PM> Update-Database -ConnectionStringName "ToDoListDb"
If all goes as planned, we should see a schema applied to our database like so
Creating the MVC Web UX
Now that we have our entity model created and deployed to our SQL db, we now need to instruct the TaskListContext initially created to connect to and utilize the ‘ToDoListDb’ created. We can do this via a constructor for the TaskListContext class, which can accept a connection string as a parameter and pass this onto its superclass DbContext like so:1: namespace MyToDoApp.Models
2: {
3: public class TaskListContext: DbContext
4: {
5: public DbSet<ToDoTask> Tasks { get; set; }
6:
7: public TaskListContext(string connectionString)
8: :base(connectionString)
9: { }
10: }
11: }
To simplify creating an instance of the TaskListContext class, we may use a Factory to create and serve new instances to any MVC controller that wishes to utilize it:
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Web;
5: using System.Configuration;
6:
7: namespace MyToDoApp.Models
8: {
9: public static class TaskListContextFactory
10: {
11: public static TaskListContext GetNewContext()
12: {
13: //get the connection string entry "ToDoListDb"
14: var connectionString = ConfigurationManager.ConnectionStrings["ToDoListDb"].ConnectionString;
15: return new TaskListContext(connectionString);
16: }
17: }
18: }
The TaskListContextFactory class simply uses the System.Configuration.ConfigurationManager class to retrieve the connection string from our web.config and pass that as an argument to our TaskListContext, ensuring any instance of the context is always communicating with the correct DB. Being a static class adds to the convenience of just being able to invoke any of its methods statically to get a new context. Our next step is to create an MVC controller & view to display all the tasks. The view will also allow the user to add a new task, edit & remove existing tasks. We start with specifying the controller TaskController like so:
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Web;
5: using System.Web.Mvc;
6: using MyToDoApp.Models;
7:
8: namespace MyToDoApp.Controllers
9: {
10: public class TaskController : Controller
11: {
12: TaskListContext taskContext;
13:
14: public TaskController()
15: {
16: taskContext = TaskListContextFactory.GetNewContext();
17: }
18:
19:
20: //display all tasks
21: public ActionResult Index()
22: {
23: return View(taskContext.Tasks);
24: }
25:
26: }
27: }
The Index mvc action method simply selects all the tasks and passes them as a parameter to the view. Next we create the strongly typed Index.cshtml MVC View to simply list all the tasks:
@model IEnumerable<MyToDoApp.Models.ToDoTask> @{ ViewBag.Title = "My Task List"; } <h2>My Task List</h2> <table> <thead> <tr> <th>Description</th> <th>DueBy</th> <th>Completed</th> </tr> </thead> <tbody> @foreach (var task in Model) { <tr> <td>@Html.DisplayFor(model => task.Description)</td> <td>@Html.DisplayFor(model => task.DueBy)</td> <td>@Html.DisplayFor(model => task.Completed)</td> </tr> } </tbody> </table>
Ensure you also have the following rout configured for the application in your RouteConfig.cs
1: public static void RegisterRoutes(RouteCollection routes)
2: {
3: routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
4:
5: routes.MapRoute(
6: name: "Default",
7: url: "{controller}/{action}/{id}",
8: defaults: new { controller = "Task", action = "Index", id = UrlParameter.Optional }
9: );
10: }
If we create and run the application we now have the *currently empty’ list of tasks
Next we want to be able to allow adding a new ToDoTask. We start by providing an Action-link in our current MVC view like so:
@model IEnumerable<MyToDoApp.Models.ToDoTask> @{ ViewBag.Title = "My Task List"; } <h2>My Task List</h2> @Html.ActionLink("Add Task", "add") <table> <thead> <tr> <th>Description</th> <th>DueBy</th> <th>Completed</th> </tr> </thead> <tbody> @foreach (var task in Model) { <tr> <td>@Html.DisplayFor(model => task.Description)</td> <td>@Html.DisplayFor(model => task.DueBy)</td> <td>@Html.DisplayFor(model => task.Completed)</td> </tr> } </tbody> </table>
This is followed by adding the Add action method to our TaskController class. Notice that I am explicitly stating the name of the MVC view to use, reason being I intend to reuse the same view for the edit template. Also, as per best practise in MVC, I pass a new ToDoTask instance to the view, defaulting the Completed flag to false:
1: public ActionResult Add()
2: {
3: return View("TaskEdit", new ToDoTask() { Completed=false});
4: }
The TaskEdit.cshtml view contains the following html
@model MyToDoApp.Models.ToDoTask @{ ViewBag.Title = "Edit Task"; } <script type="text/javascript"> //use JQuery to add a datepicker $(function () { $("#@ViewData.TemplateInfo.GetFullHtmlFieldName("DueBy")") .datepicker({ dateFormat: 'dd/mm/yy' }); }); </script> <h2>Edit Task</h2> @using (Html.BeginForm()) { @Html.EditorForModel() <input type="submit" name="submit" value="Save" /> }
Debugging the site in its current state should render the following
This outlines a tiny issue, the Id member of a ToDoTask should not be editable as it is the primary and hence automatically incremented using an Identity in TSQL. We can fix this by editing the class ToDoTask to add a HiddenInput data annotation attribute to the Id member field like so (line 7-8):
1: using System.Web.Mvc;
2:
3: namespace MyToDoApp.Models
4: {
5: public class ToDoTask
6: {
7: [HiddenInput(DisplayValue=false)]
8: public int Id { get; set; }
9:
10: public string Description { get; set; }
11:
12: public DateTime? DueBy { get; set; }
13:
14: public bool Completed { get; set; }
15: }
16: }
Finally, we handle the POST back when the user clicks save to persist the task. We add the following MVC action method to the TaskController.cs:
1: [HttpPost]
2: public ActionResult Add(ToDoTask task)
3: {
4: if(ModelState.IsValid
5: && task !=null)
6: {
7: taskContext.Tasks.Add(task);
8: taskContext.SaveChanges();
9: return RedirectToAction("Index");
10: }
11: return View();
12: }
The final step is to add some actions to allow editing and deleting values from the list. For the sake of brevity I will now simply list the additional actions I will add to support deleting and editing items in TaskController.cs:
1: public ActionResult Edit(int? Id)
2: {
3: if(Id == null)
4: throw new ArgumentNullException("Id");
5:
6: if (taskContext.Tasks.Any(tsk => tsk.Id == Id))
7: {
8: var task = taskContext.Tasks.FirstOrDefault(tsk => tsk.Id == Id);
9: //we are reusing the TaskEdit.cshtml view that was added earlier
10: return View("TaskEdit",task);
11: }
12:
13: return RedirectToAction("Index");
14: }
15:
16: [HttpPost]
17: public ActionResult Edit(ToDoTask task)
18: {
19: if (task != null)
20: {
21: taskContext.Tasks.Attach(task);
22: //signal that the entity has been modified
23: taskContext.Entry(task).State = System.Data.EntityState.Modified;
24: taskContext.SaveChanges();
25: }
26: return RedirectToAction("Index");
27: }
28:
29: public ActionResult Delete(int? Id)
30: {
31: if(Id == null)
32: throw new ArgumentNullException("Id");
33:
34: if (taskContext.Tasks.Any(tsk => tsk.Id == Id))
35: {
36: var task = taskContext.Tasks.FirstOrDefault(tsk => tsk.Id == Id);
37: taskContext.Tasks.Remove(task);
38: taskContext.SaveChanges();
39: }
40:
41: return RedirectToAction("Index");
42: }
And finally modify the Index.cshtml view to be able to invoke these extra actions:
@model IEnumerable<MyToDoApp.Models.ToDoTask> @{ ViewBag.Title = "My Task List"; } <h2>My Task List</h2> @Html.ActionLink("Add Task", "add") <table> <thead> <tr> <th>Description</th> <th>DueBy</th> <th>Completed</th> </tr> </thead> <tbody> @foreach (var task in Model) { <tr> <td>@Html.DisplayFor(model => task.Description)</td> <td>@Html.DisplayFor(model => task.DueBy)</td> <td>@Html.DisplayFor(model => task.Completed)</td> <td>@Html.ActionLink("Edit", "edit", new { Id = task.Id})</td> <td>@Html.ActionLink("Delete", "delete", new { Id = task.Id})</td> </tr> } </tbody> </table>This concludes our simple MVC4 application. We now have our application in place, communicating with a backend SQL 2012 database.
This mimics a common situation we may all face, whereby we have a standalone web application communicating with an existing backend SQL DB. At this point, there is no suggestion that any of this code should be hosted on Azure anywhere in the solution. In my next post, I will outline how easy it is to host and run this MVC application from Azure
No comments:
Post a Comment
Feel free to provide any feedback