Developing web apps using ASP.NET MVC 3, Razor and EF Code First - Part 2
In my previous post Developing web apps using ASP.NET MVC 3, Razor and EF Code First - Part 1, we have discussed on how to work with ASP.NET MVC 3 and EF Code First for developing web apps. We have created generic repository and unit of work with EF Code First for our ASP.NET MVC 3 application and did basic CRUD operations against a simple domain entity. In this post, I will demonstrate on working with domain entity with deep object graph, Service Layer and View Models and will also complete the rest of the demo application. In the previous post, we have done CRUD operations against Category entity and this post will be focus on Expense entity those have an association with Category entity. You can download the source code from http://efmvc.codeplex.com .
The following frameworks will be used for this step by step tutorial.
1. ASP.NET MVC 3 RTM
2. EF Code First CTP 5
3. Unity 2.0
Domain Model
Category Entity
- public class Category
- {
- public int CategoryId { get; set; }
- [Required(ErrorMessage = "Name Required")]
- [StringLength(25, ErrorMessage = "Must be less than 25 characters")]
- public string Name { get; set;}
- public string Description { get; set; }
- public virtual ICollection<Expense> Expenses { get; set; }
- }
Expense Entity
- public class Expense
- {
- public int ExpenseId { get; set; }
- public string Transaction { get; set; }
- public DateTime Date { get; set; }
- public double Amount { get; set; }
- public int CategoryId { get; set; }
- public virtual Category Category { get; set; }
- }
We have two domain entities - Category and Expense. A single category contains a list of expense transactions and every expense transaction should have a Category.
Repository class for Expense Transaction
Let’s create repository class for handling CRUD operations for Expense entity
- public class ExpenseRepository : RepositoryBase<Expense>, IExpenseRepository
- {
- public ExpenseRepository(IDatabaseFactory databaseFactory)
- : base(databaseFactory)
- {
- }
- }
- public interface IExpenseRepository : IRepository<Expense>
- {
- }
Service Layer
If you are new to Service Layer, checkout Martin Fowler's article Service Layer . According to Martin Fowler, Service Layer defines an application's boundary and its set of available operations from the perspective of interfacing client layers. It encapsulates the application's business logic, controlling transactions and coordinating responses in the implementation of its operations. Controller classes should be lightweight and do not put much of business logic onto it. We can use the service layer as the business logic layer and can encapsulate the rules of the application.
Let’s create a Service class for coordinates the transaction for Expense
- public interface IExpenseService
- {
- IEnumerable<Expense> GetExpenses(DateTime startDate, DateTime ednDate);
- Expense GetExpense(int id);
- void CreateExpense(Expense expense);
- void DeleteExpense(int id);
- void SaveExpense();
- }
- public class ExpenseService : IExpenseService
- {
- private readonly IExpenseRepository expenseRepository;
- private readonly IUnitOfWork unitOfWork;
- public ExpenseService(IExpenseRepository expenseRepository, IUnitOfWork unitOfWork)
- {
- this.expenseRepository = expenseRepository;
- this.unitOfWork = unitOfWork;
- }
- public IEnumerable<Expense> GetExpenses(DateTime startDate, DateTime endDate)
- {
- var expenses = expenseRepository.GetMany(exp => exp.Date >= startDate && exp.Date <= endDate);
- return expenses;
- }
- public void CreateExpense(Expense expense)
- {
- expenseRepository.Add(expense);
- unitOfWork.Commit();
- }
- public Expense GetExpense(int id)
- {
- var expense = expenseRepository.GetById(id);
- return expense;
- }
- public void DeleteExpense(int id)
- {
- var expense = expenseRepository.GetById(id);
- expenseRepository.Delete(expense);
- unitOfWork.Commit();
- }
- public void SaveExpense()
- {
- unitOfWork.Commit();
- }
- }
View Model for Expense Transactions
In real world ASP.NET MVC applications, we need to design model objects especially for our views. Our domain objects are mainly designed for the needs for domain model and it is representing the domain of our applications. On the other hand, View Model objects are designed for our needs for views. We have an Expense domain entity that has an association with Category. While we are creating a new Expense, we have to specify that in which Category belongs with the new Expense transaction. The user interface for Expense transaction will have form fields for representing the Expense entity and a CategoryId for representing the Category. So let's create view model for representing the need for Expense transactions.
- public class ExpenseViewModel
- {
- public int ExpenseId { get; set; }
- [Required(ErrorMessage = "Category Required")]
- public int CategoryId { get; set; }
- [Required(ErrorMessage = "Transaction Required")]
- public string Transaction { get; set; }
- [Required(ErrorMessage = "Date Required")]
- public DateTime Date { get; set; }
- [Required(ErrorMessage = "Amount Required")]
- public double Amount { get; set; }
- public IEnumerable<SelectListItem> Category { get; set; }
- }
The ExpenseViewModel is designed for the purpose of View template and contains the all validation rules. It has properties for mapping values to Expense entity and a property Category for binding values to a drop-down for list values of Category.
Create Expense transaction
Let’s create action methods in the ExpenseController for creating expense transactions
- public ActionResult Create()
- {
- var expenseModel = new ExpenseViewModel();
- var categories = categoryService.GetCategories();
- expenseModel.Category = categories.ToSelectListItems(-1);
- expenseModel.Date = DateTime.Today;
- return View(expenseModel);
- }
- [HttpPost]
- public ActionResult Create(ExpenseViewModel expenseViewModel)
- {
- if (!ModelState.IsValid)
- {
- var categories = categoryService.GetCategories();
- expenseViewModel.Category = categories.ToSelectListItems(expenseViewModel.CategoryId);
- return View("Save", expenseViewModel);
- }
- Expense expense=new Expense();
- ModelCopier.CopyModel(expenseViewModel,expense);
- expenseService.CreateExpense(expense);
- return RedirectToAction("Index");
- }
In the Create action method for HttpGet request, we have created an instance of our View Model ExpenseViewModel with Category information for the drop-down list and passing the Model object to View template. The extension method ToSelectListItems is shown below
- public static IEnumerable<SelectListItem> ToSelectListItems(
- this IEnumerable<Category> categories, int selectedId)
- {
- return
- categories.OrderBy(category => category.Name)
- .Select(category =>
- new SelectListItem
- {
- Selected = (category.CategoryId == selectedId),
- Text = category.Name,
- Value = category.CategoryId.ToString()
- });
- }
In the Create action method for HttpPost, our view model object ExpenseViewModel will map with posted form input values. We need to create an instance of Expense for the persistence purpose. So we need to copy values from ExpenseViewModel object to Expense object. ASP.NET MVC futures assembly provides a static class ModelCopier that can use for copying values between Model objects. ModelCopier class has two static methods - CopyCollection and CopyModel.CopyCollection method will copy values between two collection objects and CopyModel will copy values between two model objects. We have used CopyModel method of ModelCopier class for copying values from expenseViewModel object to expense object. Finally we did a call to CreateExpense method of ExpenseService class for persisting new expense transaction.
List Expense Transactions
We want to list expense transactions based on a date range. So let’s create action method for filtering expense transactions with a specified date range.
- public ActionResult Index(DateTime? startDate, DateTime? endDate)
- {
- //If date is not passed, take current month's first and last dte
- DateTime dtNow;
- dtNow = DateTime.Today;
- if (!startDate.HasValue)
- {
- startDate = new DateTime(dtNow.Year, dtNow.Month, 1);
- endDate = startDate.Value.AddMonths(1).AddDays(-1);
- }
- //take last date of start date's month, if end date is not passed
- if (startDate.HasValue && !endDate.HasValue)
- {
- endDate = (new DateTime(startDate.Value.Year, startDate.Value.Month, 1)).AddMonths(1).AddDays(-1);
- }
- var expenses = expenseService.GetExpenses(startDate.Value ,endDate.Value);
- //if request is Ajax will return partial view
- if (Request.IsAjaxRequest())
- {
- return PartialView("ExpenseList", expenses);
- }
- //set start date and end date to ViewBag dictionary
- ViewBag.StartDate = startDate.Value.ToShortDateString();
- ViewBag.EndDate = endDate.Value.ToShortDateString();
- //if request is not ajax
- return View(expenses);
- }
We are using the above Index Action method for both Ajax requests and normal requests. If there is a request for Ajax, we will call the PartialView ExpenseList.
Razor Views for listing Expense information
Let’s create view templates in Razor for showing list of Expense information
ExpenseList.cshtml
- @model IEnumerable<MyFinance.Domain.Expense>
- <table>
- <tr>
- <th>Actions</th>
- <th>Category</th>
- <th>
- Transaction
- </th>
- <th>
- Date
- </th>
- <th>
- Amount
- </th>
- </tr>
- @foreach (var item in Model) {
- <tr>
- <td>
- @Html.ActionLink("Edit", "Edit",new { id = item.ExpenseId })
- @Ajax.ActionLink("Delete", "Delete", new { id = item.ExpenseId }, new AjaxOptions { Confirm = "Delete Expense?", HttpMethod = "Post", UpdateTargetId = "divExpenseList" })
- </td>
- <td>
- @item.Category.Name
- </td>
- <td>
- @item.Transaction
- </td>
- <td>
- @String.Format("{0:d}", item.Date)
- </td>
- <td>
- @String.Format("{0:F}", item.Amount)
- </td>
- </tr>
- }
- </table>
- <p>
- @Html.ActionLink("Create New Expense", "Create") |
- @Html.ActionLink("Create New Category", "Create","Category")
- </p>
Index.cshtml
- @using MyFinance.Helpers;
- @model IEnumerable<MyFinance.Domain.Expense>
- @{
- ViewBag.Title = "Index";
- }
- <h2>Expense List</h2>
- <script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
- <script src="@Url.Content("~/Scripts/jquery-ui.js")" type="text/javascript"></script>
- <script src="@Url.Content("~/Scripts/jquery.ui.datepicker.js")" type="text/javascript"></script>
- <link href="@Url.Content("~/Content/jquery-ui-1.8.6.custom.css")" rel="stylesheet" type="text/css" />
- @using (Ajax.BeginForm(new AjaxOptions{ UpdateTargetId="divExpenseList", HttpMethod="Get"})) {
- <table>
- <tr>
- <td>
- <div>
- Start Date: @Html.TextBox("StartDate", Html.Encode(String.Format("{0:mm/dd/yyyy}", ViewData["StartDate"].ToString())), new { @class = "ui-datepicker" })
- </div>
- </td>
- <td><div>
- End Date: @Html.TextBox("EndDate", Html.Encode(String.Format("{0:mm/dd/yyyy}", ViewData["EndDate"].ToString())), new { @class = "ui-datepicker" })
- </div></td>
- <td> <input type="submit" value="Search By TransactionDate" /></td>
- </tr>
- </table>
- }
- <div id="divExpenseList">
- @Html.Partial("ExpenseList", Model)
- </div>
- <script type="text/javascript">
- $().ready(function () {
- $('.ui-datepicker').datepicker({
- dateFormat: 'mm/dd/yy',
- buttonImage: '@Url.Content("~/Content/calendar.gif")',
- buttonImageOnly: true,
- showOn: "button"
- });
- });
- </script>
Ajax search functionality using Ajax.BeginForm
The search functionality of Index view is providing Ajax functionality using Ajax.BeginForm. The Ajax.BeginForm() method writes an opening <form> tag to the response. You can use this method in a using block. In that case, the method renders the closing </form> tag at the end of the using block and the form is submitted asynchronously by using JavaScript. The search functionality will call the Index Action method and this will return partial view ExpenseList for updating the search result. We want to update the response UI for the Ajax request onto divExpenseList element. So we have specified the UpdateTargetId as "divExpenseList" in the Ajax.BeginForm method.
Add jQuery DatePicker
Our search functionality is using a date range so we are providing two date pickers using jQuery datepicker. You need to add reference to the following JavaScript files to working with jQuery datepicker.
- jquery-ui.js
- jquery.ui.datepicker.js
For theme support for datepicker, we can use a customized CSS class. In our example we have used a CSS file “jquery-ui-1.8.6.custom.css”. For more details about the datepicker component, visit jquery UI website at http://jqueryui.com/demos/datepicker . In the jQuery ready event, we have used following JavaScript function to initialize the UI element to show date picker.
- <script type="text/javascript">
- $().ready(function () {
- $('.ui-datepicker').datepicker({
- dateFormat: 'mm/dd/yy',
- buttonImage: '@Url.Content("~/Content/calendar.gif")',
- buttonImageOnly: true,
- showOn: "button"
- });
- });
- </script>
Source Code
You can download the source code from http://efmvc.codeplex.com/ .
Summary
In this two-part series, we have created a simple web application using ASP.NET MVC 3 RTM, Razor and EF Code First CTP 5. I have demonstrated patterns and practices such as Dependency Injection, Repository pattern, Unit of Work, ViewModel and Service Layer. My primary objective was to demonstrate different practices and options for developing web apps using ASP.NET MVC 3 and EF Code First. You can implement these approaches in your own way for building web apps using ASP.NET MVC 3. I will refactor this demo app on later time.