Saturday, January 29, 2011

Grid view in MVC using mvcContrib

Last post I showed a simple way to display images in your html using an MVC controller Displaying images from a database - MVC style.

But then it got me thinking...what if I have a lot of records ? what if I want to allow the users to search for a specific record?
So the answer is of course - adding some sort of page/sort/filter control/s
in ASP.NET we just needed to pick one from millions..what about MVC?

Googling through this topic I found all sort of HTML helpers some free & some commercial, the 1st one to catch my eye and later on proven to be a good catch was mvcContrib.

I will show you a small sample I built based on Raj Kaimal blog using mvcContrib.

Lets start from a few semi-new concepts when developing in MVC:

1. ViewModels:
The architectural hype these days is definitely ORM tools like EntityFramework, NHibernate etc - These frameworks and others were invented to decouple the database structure from the logical entities defined in our application and map between them to allow transferring data back and forward.
The thing is that before these tools came we were working with smaller sets of data, retrieving only the data we needed for display and sometimes modeling it for smoother work in a specific view.
For this type of work we will use ViewModel, view-model is a portion of our model built specific for a single view (or more if same that is display in more than one way), it decouple the view from the model, allows us to work with a smaller set of data in a structure adjusted to the view but still keep it strongly typed.

2. Controllers
Controllers as their name suggests - control, they are the main MVC concept, Controllers are the gateway of the view to the application & data - building a wall between them - a real wall - not an imaginary one as in webform ASP.NET - this "wall" will allow us to serve the same services for different types of views (web applications, webservices, smart phones etc).

Let's see an implementation of these concepts.

In every grid view there are 3 different views:
1. The filter view.
2. The main view.
3. The paging view.



Every view has its own viewModel.
The filter will need a viewModel which describes the filter, a list of items for a combo filter, a datetime for a datepicker etc
The main view will probably need columns from all sorts of entities, some of them could be "translated" using some sort of lookup table, some maybe formatted differently for this view etc.
The paging view will need a viewModel that describes the paging ruler, in the case of mvcContrib - implementation of IPagination.
The main view will also need to implement GridSortOptions to allow sorting.

Sounds kinda complicated, but actually it couldn't be easier...

Filter's view model will contain a list that will be represented as a dropdown in the view:
public class AlbumFilterViewModel
    {
        public AlbumFilterViewModel()
        {
            SelectedArtistId = -1;
        }

        public int SelectedArtistId { get; set; }

        public List Artists { get; set; }
    }

The main view view-model, this will contain the columns we want to display in the gridview, notice that we can change the way the columns are displayed using simple attributes, this could be implemented for a specific view or at the entity level exploiting the fact that EF entities are define as partial classes (using DataAnnotations: [MetadataType(typeof(AlbumMetaData))]):

public class AlbumListViewModel
    {
        [ScaffoldColumn(false)]  //this columns won't be shown in view
        public int ArtistId { get; set; }

        [ScaffoldColumn(false)]
        public int AlbumId { get; set; }

        [DisplayName("Artist Name")] //change the display name of the column
        public string ArtistName { get; set; }

        [DisplayName("Album Name")]
        public string Name { get; set; }

        [DisplayName("Cover")]
        public byte[] Picture { get; set; }

        [DisplayName("Last Updated Date")]
        [DisplayFormat(DataFormatString = "{0:g}")] //format the date
        public DateTime LastUpdatedDate { get; set; }

    }

As I mentioned before the main view implements the acutal grid and also the paging and sorting, the viewModel will contain the previous one plus the paging and sort definition. it will look like this:
public class AlbumListContainerViewModel
    {
        //paging
        public IPagination AlbumPageList { get; set; }
        //main view
        public AlbumFilterViewModel AlbumFilterViewModel { get; set; }
        //sort
        public GridSortOptions GridSortOptions { get; set; }
    }

Now lets go to the view.
We'll have 3 different partial views (ascx):
1. The filter.
2. The main view.
3. The paging ruler that we'll had before and after the main view.

The container will look something like this:
<% Html.RenderPartial("SearchFilter", Model.AlbumFilterViewModel); %>
    <% Html.RenderPartial("Pager", Model.AlbumPageList); %>
    <% Html.RenderPartial("SearchResult", Model); %>
    <% Html.RenderPartial("Pager", Model.AlbumPageList); %>

The filter view html is quite simple, notice that the view is strongly typed:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
.
.
            
<%:Html.DropDownList("ArtistId", Model.Artists, "-- All --", htmlAttributes)%>
. .

The search result view code, you will see the mvcContrib's html helpers does everything, the only two place I wrote my own code was to specify a different behavior for two columns, one is the "More.." column which is linked to a detail view of the chosen row and a the image column which is linked to the controller that retrieve the image from the database:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<%@ Import Namespace="MvcContrib.UI.Grid" %>
<%@ Import Namespace="DiegJukeboxRemote.Models" %>
<%@ Import Namespace="DiegJukeboxRemote.HtmlHelpers" %>
<%= Html.Grid(Model.AlbumPageList).AutoGenerateColumns()
    .Columns(column => {
        column.For(a => Html.ActionLink("More..", "ThumbChooserEdit", new { id = a.AlbumId })).InsertAt(4).Encode(false);
    })
    .Columns(column=> {
        column.For(a => Html.Image(a.AlbumId, "RetrieveImage", "MusicInfo", 
            new Dictionary() { 
                                                { "Height", "100" }, { "Width", "100" },
                                                {"onerror", "onImgErrorSmall(this)"}
            })).InsertAt(3).Encode(false);
    })
    .Sort(Model.GridSortOptions)
    .Attributes(@class => "table-list")
%>

The pager couldn't be simpler:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<%@ Import Namespace="MvcContrib.UI.Pager" %>

<%= Html.Pager(Model) .First("First") .Last("Last") .Next("Next") .Previous("Previous") %>


Finally the controller, using LINQ we retrieve the data we need from artist/album repositories, you will see here two special points:
1. Using System.Web.Mvc.SelectListItem for the dropdownlist.
2. Using MvcContrib.UI.Grid.GridSortOptions & MvcContrib.Pagination.PaginationHelper so mvcContrib helpers can do the rest :-)
public ActionResult ThumbChooser(int? artistId, GridSortOptions gridSortOptions, int? page, bool? MissingCoverOnly)
        {
            IQueryable albumList = null;

            if (!MissingCoverOnly.HasValue || !MissingCoverOnly.Value)
            {
                albumList = _BL.GetAlbumListView();
            }
            else
            {
                albumList = _BL.GetAlbumListViewMissingCover();
            }

            if (string.IsNullOrWhiteSpace(gridSortOptions.Column))
            {
                gridSortOptions.Column = "ArtistId";
            }

            if (artistId.HasValue)
            {
                albumList = albumList.Where(a => a.ArtistId == artistId.Value);
            }

            var albumFilter = new AlbumFilterViewModel();
            albumFilter.SelectedArtistId = artistId ?? -1;

            albumFilter.Artists = _BL.GetArtistList().OrderBy(art=>art.Name)
                                    .Select(a => new { a.ArtistId, a.Name })
                                    .ToList().Select(a =>
                                        new SelectListItem
                                        {
                                            Text = a.Name.Length > 20 ? string.Concat(a.Name.Substring(0,20),"...") : a.Name,
                                            Value = a.ArtistId.ToString(),
                                            Selected = a.ArtistId == albumFilter.SelectedArtistId
                                        }).ToList();

            var albumPageList = albumList
                                .OrderBy(gridSortOptions.Column, gridSortOptions.Direction)
                                .AsPagination(page ?? 1, 10);

            var albumListContainer = new AlbumListContainerViewModel
            {
                AlbumFilterViewModel = albumFilter,
                AlbumPageList = albumPageList,
                GridSortOptions = gridSortOptions
            };

            return View(albumListContainer);
        }

That's it!

Till next time...
Diego

Saturday, January 8, 2011

Building N-Tier Applications with Entity Framework 4

Introduction

This post describes using of EF4 for building N-Tier applications. I've decided to write about this topic, after reading an excellent series of articles about N-Tier applications by Daniel Simmons:

1. N-Tier Application Patterns
2. Anti-Patterns to Avoid in N-Tier Applications
3. Building N-Tier Apps with EF 4

Those articles provide in-depth explanation about different issues and considerations we need to take into account, while building N-Tier applications.

Self-Tracking Entities

Self-tracking entities are smart objects with an ability to keep track on their own changes. The key difference between them and regular datasets is that self-tracking entities are plain-old CLR objects (POCO) and consequently are not tied to any specific persistance technology. They are relatively simple objects that represent the entities and information about changes that they went through.

T4 Templates

T4 templates are text templates that contain text blocks and control logic mixed together and can generate a text file, similar to Velocity Template Engine used in Java.

In our example we will use T4-based code generator to create our self-tracking entities.
You need to download and install it before you start.

Building N-Tier Application

The application we're going to build is based on the example from Daniel Simmons third article and uses Northwind database as a back-end.

Let's start with building our data access layer project:

New Project--->Class Library Project, name it Northwind.DAL
Add--->New Item--->ADO.NET Entity Data Model



We will generate our model from the database:



Our model will look like this:



Right-click on the model designer--->Add Code Generation Item--->ADO Self-Tracking Entity Generator



This action will add T4 template to our project. You can also notice that it will disable the default code generation for our EF model



Now we're going to move our newly generated self-tracking entities to separate project in order to decouple the DAL project from the entities.

Right-click on the solution item--->Add New Project--->Class Library project, name it Northwind.Entities.
Afterwards, you simply cut the Northwind.tt item and paste it into Northwind.Entities project.
Don't forget to reference System.Runtime.Serialization assembly in the Northwind.Entities project and add a reference to Northwind.Entities project in Northwind.DAL project.



We're ready to add WCF service layer to our application.
Right-click on the solution item--->Add New Project--->WCF Service Application project, name it Northwind.Service.
Rename the created service definition interface to INorthwindService and move it to the entities project.

Add following methods to the INorthwindService:
[ServiceContract]
public interface INorthwindService
{
[OperationContract]
IEnumerable<products> GetProducts();

[OperationContract]
Customers GetCustomer(string id);

[OperationContract]
bool SubmitOrder(Orders order);    
}


I know that in a real application we would add additional layers like Business Logic layer and Service Interface layer, but for the sake of simplicity we will leave it out.

Let's add an implementation to our service:
public class NorthwindService : INorthwindService
{
public IEnumerable<products> GetProducts()
{
using (var ctx = new NorthwindEntities())
{
return ctx.Products.ToList();
}
}

public Customers GetCustomer(string id)
{
using (var ctx = new NorthwindEntities())
{
return ctx.Customers.Include("Orders")
.Where(c => c.CustomerID == id)
.SingleOrDefault();
}
}

public bool SubmitOrder(Orders newOrder)
{
using (var ctx = new NorthwindEntities())
{
ctx.Orders.ApplyChanges(newOrder);
return ctx.SaveChanges() > 0;
}
}
}


Please note the Include method used in GetCustomer method. This method allows so-called eager loading of Orders table to reduce the number of round-trips to the database.
The detailed information about different loading data options in EF may be found here.

Instead of building some "state of art" client to test our service, let's add a real test project to our solution:

Right-click on the solution item--->Add New Project--->Test Project, name it Northwind.Test



You have to add a reference to Northwind.Service and Northwind.Entities. Don't forget to clean up all the proxies automatically generated for the entities.

Add following test methods to the test class:
[TestClass]
public class NorthwindTest
{
public NorthwindTest()
{
}

private TestContext testContextInstance;

/// 
///Gets or sets the test context which provides
///information about and functionality for the current test run.
///
public TestContext TestContext
{
get
{
return testContextInstance;
}
set
{
testContextInstance = value;
}
}



[TestMethod]
public void TestGetProducts()
{
using (NorthwindServiceClient client = new NorthwindServiceClient())
{
List<Products> products = client.GetProducts();

Assert.IsTrue(products.Count > 0);
}
}

[TestMethod]
public void TestGetCustomer()
{
using (NorthwindServiceClient client = new NorthwindServiceClient())
{
Customers customer = client.GetCustomer("ALFKI");
Assert.IsNotNull(customer);
}
}

[TestMethod]
public void TestSubmitOrder()
{
using (NorthwindServiceClient client = new NorthwindServiceClient())
{
var products = new List<Products>(client.GetProducts());
Assert.IsTrue(products.Count > 0);

var customer = client.GetCustomer("ALFKI");
Assert.IsNotNull(customer);               

try
{
// add a new order
var newOrder = new Orders();
newOrder.OrderDate = DateTime.Now;
newOrder.RequiredDate = DateTime.Now;

var orderedProduct = 
products.Where(p => p.ProductName ==  "Chang")
.Single();
Order_Details orderDetails = new Order_Details()
{
ProductID = orderedProduct.ProductID,
Quantity = 1

};
newOrder.Order_Details.Add(orderDetails);                                       
customer.Orders.Add(newOrder);

var submitSuccess = client.SubmitOrder(newOrder);
Assert.IsTrue(submitSuccess);
}
catch (Exception ex)
{
TestContext.WriteLine(ex.StackTrace);
Assert.Fail();
}
}
}
}


I think we're done.

To see your application in action just run all test methods from the "Test" menu.

The complete code could be downloaded here.

This is it,

Mark.