Wednesday, June 23, 2010

Refactoring ASP MVC 2 to use Dependecy Injection (part1)

In my previous post named ASP.NET MVC 2.0 - Master Detail Views I wanted to show how to retreive data from several tables move this into objects and then present this in a view in a master-detail format.

My comment specifically with regards to the controller was "Since this controller was created to illustrate as simple as possible how to build a master-detail view in ASP.NET MVC,
I have not focused on proper architecture using Depedency Injection, Repository Patterns etc... (read more here and here)."

I would now like to go through the steps needed to refactor the solution to take advantage of Dependency Injection. The scenario we are facing is that my fictual boss has requested that we move the implementation so it is running on mono on top of a CentOS/Redhat linux distribution on Amazon Elastic Compute Cloud. We will have to switch the database to MySQL.

Now since I have chosen to NOT use NHibernate or another similar database agnostic ORM (which I could have) I will instead do this manually. The ultimate goal is that we can swap the database from Microsoft SQL Server to MySQL server but we want to allow the client (my fickle fictive boss) to go back to Microsoft SQL Server as his temper changes.
As a matter of fact I would also like to allow implementations of another view front end in Silverlight 4.0 or WPF.

I will in this post not actually implement the MySQL DAL or the Silverlight GUI but make sure that my applications allows it.

First we need to look at if we need to change anything at all?

public ActionResult Details(Guid id)
{
    Guid project_id = id;   // needed due to conflicting variables 
    ReverseAuctionDBDataContext db = new ReverseAuctionDBDataContext(); 

    var projects = from p in db.projects
                   where p.id == project_id
                   join u in db.users on p.owner_user_id equals u.id
                   select
                   new ProjectViewModel
                   {
                       Name = p.name,
                       Description = p.description,
                       BiddingStartDate = p.bidding_start_date,
                       BiddingEndDate = p.bidding_end_date,
                       ProjectLauchDate = p.project_start_date,
                       ProjectDeadline = p.project_deadline,
                       Owner = new SimpleUserViewModel 
                       { 
                           ID = u.id, 
                           Username = u.user_name 
                       }
                   };

    var bids = from b in db.project_bids
               where b.project_id == project_id
               join u in db.users on b.user_id equals u.id
               select new ProjectBidViewModel 
               { 
                   BidAmount = b.bid_amount, 
                   BidDate = b.bid_date, 
                   Bidder = new SimpleUserViewModel 
                   { 
                       ID = u.id, 
                       Username = u.user_name 
                   } 
               };

    ProjectViewModel project = projects.Single<ProjectViewModel>();
    project.ProjectBids = bids.ToList<ProjectBidViewModel>();
    return View(project);
} 

If we have a look at line 04 - line 37 we have intimate knowledge of the database in this code. Basically since we are directly using the generated LINQ classes we might as well hardcode SQL Server in the controller.


Now lets first of all try to move the LINQ classes to a new assembly.


So let us move the LINQ classes to a new project and change the name to reflect that this generated set of classes is for MSSQL.



Updated the dependency / reference
using Appinux.ReverseAuction.Data.Mssql;

Updated the class we work with
public ActionResult Details(Guid id)
        {
            Guid project_id = id;   // needed due to conflicting variables 
            ReverseAuctionDBMssqlDataContext db = new ReverseAuctionDBMssqlDataContext(); 

            var projects = from p in db.projects
...

Has this changed anything at all for the better? No, it has not since we are still depending on the concrete classes in ReverseAuctionDBMssql.dbml which now are just located in another assembly.

I would like to split the refactoring into 3 different pieces

  1. Seperating the DataLayer (LINQ classes) from the controller and views by introducing a domain layer
  2. Refactoring to adhere to Single Responsibility Principle
  3. Abstracting away the actual Respositories and Operations from the controller and domain setting the stage for DI
  4. Introducing the Composition Root and Windsor Lightweight Container

UPDATE

I have added a series of postings that talk about ASP.NET MVC ,Dependency Injection and Inversion of control.

They are available here:

Refactoring ASP MVC 2 to use Dependecy Injection (part1) (this posting)
Refactoring ASP MVC 2 to use Dependecy Injection (part2)

Monday, June 14, 2010

ASP MVC 2.0 - Master Detail Views


I recently had a person asking me how to make master-detail views in ASP.NET MVC 2.0.
As I often do when I think this is a common problem I introduce them to Google search.
The person in question came back and said that he could not find any good posts
(I am sure they are out there) but it prompted me to write this post.

What is this post trying to achieve?



This is a walk through how to create a master-detail page using ASP.NET MVC 2.0.
Below we see the rendered output I am aiming for (a CSS or two might make wonders).


"Master" and "Details" in the image above does not require a lot of explaining but the "Details-Details" might need a few words.
Details-Details is basically a third tables in the database holding user names and user information.





The database structure that we have looks like this



Project(s) have project_bids which in turn have a bid creator (user).



The first step is to create LINQ to SQL Classes for the database



Now pull the needed tables onto the LINQ design surface to generate the classes
(this is standard Visual Studio operation so I will not elaborate on this here).





Since our final rendered view consists of data from 3 seperate ViewModels/objects we need to create views for each of those models.
We need one full view, 2 partial views as well as the corresponding 3 ViewModel classes.
You can see the class diagram for the 3 ViewModels and how they interact below.





You can see how the solution looks and where the different files are placed below. (please ignore the HomeController it is not relevant to this context).





The first partial view is for the user name link that is rendered on the project_bid row ("Details-Details").
The user table contains far more columns than we need for this user link,
so we will create a new class/ViewModel (SimpleUserViewModel) which only contains the needed data to create a user link.




public class SimpleUserViewModel
{
    public string Username { get; set; }
    public Guid ID { get; set; }
}



The view that renders this class is located at Views/Shared/UserLink.ascx and looks like this (since the user link will be useful everywhere in the solution,
we will place the view in the Shared folder).


<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Appinux.ReverseAuction.Models.SimpleUserViewModel>" %>

<%: Html.ActionLink(Model.Username, "Details","User", new { id = Model.ID }, null) %>

The next higher level in the class hierarchy is ProjectBidsViewModel.

(note the Bidder which type is SimpleUserViewModel)
public class ProjectBidViewModel 
{
    public SimpleUserViewModel Bidder { get; set; }
    public Double BidAmount { get; set; }
    public DateTime BidDate { get; set; }
}

Before we look at the code for the View specific to the ProjectBidViewModel we need to have a look at the ProjectViewModel.
The definition of the ProjectViewModel will affect our implementation of the view specific to the ProjectBidViewModel.




public class ProjectViewModel
{
    public string Name { get; set; }
    public string Description { get; set; }
    public SimpleUserViewModel Owner { get; set; }
    public DateTime BiddingStartDate { get; set; }
    public DateTime BiddingEndDate { get; set; }
    public DateTime ProjectLauchDate { get; set; }
    public DateTime ProjectDeadline { get; set; }
    public float MaxAllowedBid { get; set; }
    public string Status { get; set; }
    public List<projectbidviewmodel> ProjectBids { get; set; }
}

If you closely at the class definition of ProjectViewModel you will note that ProjectBids is a List<ProjectBidViewModel>.
We need to make sure that the strongly typed view of the ProjectBid is of the same type.
(The view is located at Views/Project/ProjectBids.ascx).



<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<List<Appinux.ReverseAuction.Models.ProjectBidViewModel>>" %>
    <table>
        <tr>
            <th>
                BidDate
            </th>
            <th>
                BidAmount
            </th>
        </tr>

    <% foreach (var item in Model) { %>
    
        <tr>
            <td>
                <%: String.Format("{0:g}", item.BidDate) %>
            </td>
            <td>
                <%: item.BidAmount %>
            </td>
            <td>
                <% Html.RenderPartial("UserLink", item.Bidder); %>
            </td>
        </tr>
    
    <% } %>

    </table>

    <p>
        <%: Html.ActionLink("Create New", "Create") %>
    </p>

Also worth noting is the call to Html.RenderPartial("UserLink",item.Bidder); which calls the first partial view we created - UserLink.ascx.





Finally the full view of Project which is located at Views/Project/Details.aspx.

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<Appinux.ReverseAuction.Models.ProjectViewModel>" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
 <head runat="server">
     <title>Details</title>
 </head>
 <body>
  <table border=1>
   <tr>
    <td>Name</td>
    <td><%: Model.Name %></td>
   </tr>
   <tr>
    <td>Description</td>
    <td> <%: Model.Description %></td>
   </tr>
   <tr>
    <td>BiddingStartDate</td>
    <td><%: String.Format("{0:g}", Model.BiddingStartDate) %></td>
   </tr>
   <tr>
    <td>BiddingEndDate</td>
    <td><%: String.Format("{0:g}", Model.BiddingEndDate) %></td>
   </tr>
   <tr>
    <td>ProjectLauchDate</td>
    <td><%: String.Format("{0:g}", Model.ProjectLauchDate) %></td>
   </tr>
   <tr>
    <td>ProjectDeadline</td>
    <td><%: String.Format("{0:g}", Model.ProjectDeadline) %></td>
   </tr>
   <tr>
    <td>MaxAllowedBid</td>
    <td><%: Model.MaxAllowedBid %></td>
   </tr>
   <tr>
    <td>Status</td>
    <td><%: Model.Status %></td>
   </tr>
  </table>  
  <br />
  Project Bids:
  <% Html.RenderPartial("ProjectBids", Model.ProjectBids); %>
  <p>
   <%: Html.ActionLink("Edit", "Edit", new { /* id=Model.PrimaryKey */ }) %> |
   <%: Html.ActionLink("Back to List", "Index") %>
  </p>
 </body>
</html>


Also here note the Html.RenderPartial call.



Finally a little bit about the controller implementation.
Since this controller was created to illustrate as simple as possible how to build a master-detail view in ASP.NET MVC,
I have not focused on proper architecture using Depedency Injection, Repository Patterns etc...
(read more here
and here).



Without further ado I present the controller.




public ActionResult Details(Guid id)
{
    Guid project_id = id;   // needed due to conflicting variables 
    ReverseAuctionDBDataContext db = new ReverseAuctionDBDataContext(); 

    var projects = from p in db.projects
                   where p.id == project_id
                   join u in db.users on p.owner_user_id equals u.id
                   select
                   new ProjectViewModel
                   {
                       Name = p.name,
                       Description = p.description,
                       BiddingStartDate = p.bidding_start_date,
                       BiddingEndDate = p.bidding_end_date,
                       ProjectLauchDate = p.project_start_date,
                       ProjectDeadline = p.project_deadline,
                       Owner = new SimpleUserViewModel 
                       { 
                           ID = u.id, 
                           Username = u.user_name 
                       }
                   };

    var bids = from b in db.project_bids
               where b.project_id == project_id
               join u in db.users on b.user_id equals u.id
               select new ProjectBidViewModel 
               { 
                   BidAmount = b.bid_amount, 
                   BidDate = b.bid_date, 
                   Bidder = new SimpleUserViewModel 
                   { 
                       ID = u.id, 
                       Username = u.user_name 
                   } 
               };

    ProjectViewModel project = projects.Single&lt;ProjectViewModel&gt;();
    project.ProjectBids = bids.ToList&lt;ProjectBidViewModel&gt;();
    return View(project);
}

First we get project then the bids for that project and create the full object.
The only other thing that is changed from default code generated controller is the type of the id parameter which here is a Guid rather than an int.

UPDATE

I have added a series of postings that talk about ASP.NET MVC ,Dependency Injection and Inversion of control.

They are available here:

Refactoring ASP MVC 2 to use Dependecy Injection (part1)
Refactoring ASP MVC 2 to use Dependecy Injection (part2)