Controls for ASP.NET MVC

 

Custom Controls in ASP.NET MVC can be created in two ways : declarative MVC Controls and HtmlHelper extension methods. A declarative MVC control example is

<mvc:GridView dataSourceController=”CarsController” dataSourceAction=”ListCars” />

whereas HtmlHelper extension methods are like

<%= Html.GridView(gridViewModel) %>

I have implemented an HtmlHelper extension method to write HTML+Javascript for Yahoo UI DataTable.

To start with let’s see the YUI DataTable documentation and samples at http://developer.yahoo.com/yui/examples/datatable/dt_basic.html.

A simple YUI DataTable requires a div tag and following JavaScript on the page:

Code Snippet
  1. <div id=”basic”></div>
  2.    <script type=”text/javascript”>
  3.        YAHOO.example.Data = {
  4.            bookorders: [
  5.         { id: “po-0167”, date: new Date(1980, 2, 24), quantity: 1, amount: 4, title: “A Book About Nothing” },
  6.         { id: “po-0783”, date: new Date(“January 3, 1983”), quantity: null, amount: 12.12345, title: “The Meaning of Life” },
  7.         { id: “po-0297”, date: new Date(1978, 11, 12), quantity: 12, amount: 1.25, title: “This Book Was Meant to Be Read Aloud” },
  8.         { id: “po-1482”, date: new Date(“March 11, 1985”), quantity: 6, amount: 3.5, title: “Read Me Twice” }
  9.     ]
  10.        }
  11.  
  12.        YAHOO.util.Event.addListener(window, “load”, function () {
  13.            YAHOO.example.Basic = function () {
  14.                var myColumnDefs = [
  15.             { key: “id”, sortable: true, resizeable: true },
  16.             { key: “date”, formatter: YAHOO.widget.DataTable.formatDate, sortable: true, sortOptions: { defaultDir: YAHOO.widget.DataTable.CLASS_DESC }, resizeable: true },
  17.             { key: “quantity”, formatter: YAHOO.widget.DataTable.formatNumber, sortable: true, resizeable: true },
  18.             { key: “amount”, formatter: YAHOO.widget.DataTable.formatCurrency, sortable: true, resizeable: true },
  19.             { key: “title”, sortable: true, resizeable: true }
  20.         ];
  21.  
  22.                var myDataSource = new YAHOO.util.DataSource(YAHOO.example.Data.bookorders);
  23.                myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSARRAY;
  24.                myDataSource.responseSchema = {
  25.                    fields: [“id”, “date”, “quantity”, “amount”, “title”]
  26.                };
  27.  
  28.                var myDataTable = new YAHOO.widget.DataTable(“basic”,
  29.                 myColumnDefs, myDataSource, { caption: “DataTable Caption” });
  30.  
  31.                return {
  32.                    oDS: myDataSource,
  33.                    oDT: myDataTable
  34.                };
  35.            } ();
  36.        });
  37.  
  38.    </script>

 

To represent the View Model classes YUIGridViewModel and YUIDataColumnDefinition as \

Code Snippet
  1. public class YUIGridViewModel
  2.     {
  3.         public IEnumerable<YUIDataColumnDefinition> ColumnDefintions { get; set; }
  4.  
  5.         public String DataSourceAction { get; set; }
  6.  
  7.         public String Title { get; set; }
  8.  
  9.         public String Name { get; set; }
  10.  
  11.         public String DivElement { get; set; }               
  12.     }

 

Code Snippet
  1. public class YUIDataColumnDefinition
  2.   {
  3.       public String Key { get; set; }
  4.       public String Label { get; set; }
  5.       public bool Resizable { get; set; }
  6.       public bool Visible { get; set; }
  7.       public bool Sortable { get; set; }
  8.       public FormatType Formatter { get; set; }
  9.  
  10.       public override string ToString()
  11.       {          
  12.           return String.Format(“{{ key : \”{0}\”, sortable : {1}, resizeable : {2}, label : \”{3}\” {4} }}”,
  13.                   Key,GetBoolString(Sortable), GetBoolString(Resizable), GetNonNullString(Label), GetFormatter());
  14.       
  15.       }
  16.  
  17.       private string GetBoolString(bool value)
  18.       {
  19.           return value ? “true” : “false”;
  20.       }
  21.  
  22.       private string GetNonNullString(string Label)
  23.       {
  24.           return String.IsNullOrEmpty(Label) ? String.Empty : Label;
  25.       }
  26.  
  27.       private string GetFormatter()
  28.       {
  29.           var formatString = String.Empty;// “YAHOO.widget.DataTable.formatDefault”;
  30.           switch (this.Formatter)
  31.           {
  32.               case FormatType.Date:
  33.                   formatString = “, formatter:YAHOO.widget.DataTable.formatDate “;
  34.                   break;
  35.               case FormatType.Number:
  36.                   formatString = “, formatter:YAHOO.widget.DataTable.formatNumber”;
  37.                   break;
  38.               case FormatType.Currency:
  39.                   formatString = “, formatter:YAHOO.widget.DataTable.formatCurrency”;
  40.                   break;
  41.           }
  42.           return formatString;
  43.       }
  44.   }
  45.  
  46.   public enum FormatType
  47.   {
  48.       String=0, //this makes it default??
  49.       Date,
  50.       Number,
  51.       Currency
  52.   }

 

Then I created a Repository to return the YUI DataTable View Model. Using Repository helps to abstract the ViewModel creation.

Code Snippet
  1. public class SearchRepository
  2.     {
  3.         private readonly Dictionary<string, YUIGridViewModel> _searchMetaItems;
  4.  
  5.         public SearchRepository()
  6.         {
  7.             _searchMetaItems = new Dictionary<string, YUIGridViewModel>();
  8.             InitializeYUIGridViewModel();
  9.         }
  10.  
  11.         public YUIGridViewModel GetSearchMeta(String searchName)
  12.         {
  13.             if (_searchMetaItems.Keys.Contains(searchName))
  14.                 return _searchMetaItems[searchName];
  15.             return null;
  16.         }
  17.  
  18.         private void InitializeYUIGridViewModel()
  19.         {
  20.             _searchMetaItems.Add(“ObjectModelSearch”, new YUIGridViewModel()
  21.             {
  22.                 Title = “Search Grid”,
  23.                 Name = “Grid from Object source”,
  24.                 DivElement = “IEGrid”,
  25.                 DataSourceAction = @”/Search/GetData?name=ObjectModelSearch”,
  26.                 ColumnDefintions = new List<YUIDataColumnDefinition>
  27.                 {
  28.                     new YUIDataColumnDefinition(){ Key=“id”, Label=“ID”, Sortable=true, Resizable=true,Visible=false},
  29.                     new YUIDataColumnDefinition{ Key=“date”, Label=“Date”, Formatter= FormatType.Date, Sortable=true, Resizable=true},
  30.                     new YUIDataColumnDefinition{ Key=“quantity”, Label=“Quantity”, Formatter=FormatType.Number, Sortable=true, Resizable=true},
  31.                     new YUIDataColumnDefinition{ Key=“amount”, Label=“Amount”, Formatter=FormatType.Currency, Sortable=true, Resizable=true},
  32.                     new YUIDataColumnDefinition{ Key=“title”, Label=“Title”, Sortable=true, Resizable=true}
  33.                 }
  34.             });
  35.  
  36.             _searchMetaItems.Add(“DataTableSearch”, new YUIGridViewModel()
  37.             {
  38.                 Title = “Search Grid”,
  39.                 Name = “Grid from ADO.NET DataTable source”,
  40.                 DivElement = “NEGrid”,
  41.                 DataSourceAction = @”/Search/GetData?name=DataTableSearch”,
  42.                 ColumnDefintions = new List<YUIDataColumnDefinition>
  43.                 {
  44.                     new YUIDataColumnDefinition(){ Key=“id”, Label=“ID”, Sortable=true, Resizable=true,Visible=false},
  45.                     new YUIDataColumnDefinition{ Key=“title”, Label=“Title”, Sortable=true, Resizable=true},
  46.                     new YUIDataColumnDefinition{ Key=“name”, Label=“Name”, Sortable=true, Resizable=true}
  47.                 }
  48.             });
  49.  
  50.         }
  51.  
  52.  
  53.         public object GetSearchData(string searchname)
  54.         {
  55.             if (“ObjectModelSearch”.Equals(searchname))
  56.             {
  57.                 return GetNEItems();
  58.             }
  59.             DataTable table = new DataTable();
  60.             table.TableName = “NEData”;
  61.             var column = new DataColumn(“id”, typeof(string));
  62.             table.Columns.Add(column);
  63.             column = new DataColumn(“title”, typeof(string));
  64.             table.Columns.Add(column);
  65.             column = new DataColumn(“name”, typeof(string));
  66.             table.Columns.Add(column);
  67.  
  68.             table.Rows.Add(“ct-0123”, “Ms.”, “Cathy Green”);
  69.             table.Rows.Add(“ct-1246”, “Mr.”, “John Meyer”);
  70.  
  71.             var result = GetDataAsDictionary(table);
  72.             return result;
  73.  
  74.         }
  75.  
  76.         private IEnumerable<SearchItemModel> GetNEItems()
  77.         {
  78.             var list = new List<SearchItemModel>(){
  79.             new SearchItemModel{id=“po-0167”, date=new DateTime(1980, 2, 24).ToShortDateString(), quantity=1, amount=4, title=“A Book About Nothing”},
  80.             new SearchItemModel {id=“po-0783”, date=new DateTime(1983, 3, 23).ToShortDateString(), quantity=10, amount=12.12345, title=“The Meaning of Life”},
  81.             new SearchItemModel {id=“po-0297”, date=new DateTime(1978, 11, 12).ToShortDateString(), quantity=12, amount=1.25, title=“This Book Was Meant to Be Read Aloud”},
  82.             new SearchItemModel {id=“po-1482”, date=new DateTime(1985,3,11).ToShortDateString(), quantity=6, amount=3.5, title=“Read Me Twice”}
  83.             };
  84.             return list;
  85.         }
  86.         private object GetDataAsDictionary(DataTable table)
  87.         {
  88.             var mainDictionary = new List<object>();
  89.  
  90.             foreach (DataRow row in table.Rows)
  91.             {
  92.                 var subDicitionary = new Dictionary<string, object>();
  93.                 foreach (DataColumn column in table.Columns)
  94.                 {
  95.                     subDicitionary[column.ColumnName] = row[column];
  96.                 }
  97.                 mainDictionary.Add(subDicitionary);
  98.             }
  99.             return mainDictionary;
  100.         }
  101.     }

 

In it’s constructor I have initialized two Grid View Models for the searches named ObjectModelSearch and DataTableSearch. The data for ObjectModelSearch is represented as a list of objects whereas for DataTableSearch as ADO.NET Datatable.

To display a YUI datatable I have created following exntension method that takes YUIGridViewModel as parameter.

Code Snippet
  1. public static class RenderGridExtensions
  2.     {
  3.         public static string RenderGrid(this HtmlHelper helper, YUIGridViewModel viewModel)
  4.         {
  5.             StringBuilder builder = new StringBuilder();
  6.             builder.Append(“<div id=\”” + viewModel.DivElement + “\”></div> “);
  7.             builder.Append(“<script type=\”text/javascript\”>  \n”);
  8.             builder.Append(” YAHOO.util.Event.addListener(window, \”load\”, function () {\n”);
  9.             builder.Append(” YAHOO.example.Basic = function () { \n”);
  10.             //Column Definitions
  11.                 builder.Append(”   var myColumnDefs = [\n”);
  12.                 builder.Append(String.Join(“,”, from p in viewModel.ColumnDefintions select p.ToString() ));
  13.                 builder.Append(”        ];\n”);
  14.             //***Column Definitions
  15.              builder.Append(“var myDataSource = new YAHOO.util.DataSource(\””+viewModel.DataSourceAction + “\”);\n”);
  16.              builder.Append(” myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSARRAY;\n”);
  17.                  builder.Append(” myDataSource.responseSchema = { fields : [\n”);
  18.                  builder.Append(string.Join(“,”, (from colDef in viewModel.ColumnDefintions select (“\””+ colDef.Key + “\””)).ToArray()));                   
  19.                  builder.Append(”    ]};\n”);
  20.             builder.Append(” var myDataTable = new YAHOO.widget.DataTable(\”” + viewModel.DivElement +“\”, myColumnDefs, myDataSource, { caption: \”” + viewModel.Name + “\” });\n”);
  21.            
  22.             builder.Append(@”return {
  23.                    oDS: myDataSource,
  24.                    oDT: myDataTable
  25.                };”);
  26.             builder.Append(”    } ();\n”);
  27.             builder.Append(” });\n”);//end of addListerner
  28.             builder.Append(“</script>\n”);
  29.             var returnValue = builder.ToString();
  30.             return returnValue;
  31.         }
  32.     }

 

The Controller and View pages are :

Code Snippet
  1. public class SearchController : Controller
  2.     {
  3.  
  4.         public ActionResult Index(string name)
  5.         {
  6.             var srviewModel = new SearchRepository().GetSearchMeta(name);
  7.             return View(srviewModel);
  8.         }
  9.  
  10.         public JsonResult GetData(string name)
  11.         {            
  12.             if (String.IsNullOrEmpty(name))
  13.                 return null;
  14.  
  15.             var data = new SearchRepository().GetSearchData(name);
  16.             return Json(data,   JsonRequestBehavior.AllowGet);
  17.         }
  18.  
  19.     }

 

Code Snippet
  1. <%@ Page Title=”” Language=”C#” MasterPageFile=”~/Views/Shared/Site.Master” Inherits=”System.Web.Mvc.ViewPage<MvcApplicationWithYUI.Models.YUIGridViewModel>” %>
  2. <%@ Import Namespace=”MvcApplicationWithYUI.UIHelpers” %>
  3.  
  4. <asp:Content ID=”Content1″ ContentPlaceHolderID=”TitleContent” runat=”server”>
  5.     Index
  6. </asp:Content>
  7.  
  8. <asp:Content ID=”Content2″ ContentPlaceHolderID=”MainContent” runat=”server”>
  9.  
  10.    
  11.    <%= Html.RenderGrid(Model) %>
  12.   
  13. </asp:Content>

To get the data, YUI datatable’s source to the controller’s GetData action like http://localhost:49417/Search/GetData?name=ObjectModelSearch. The controller uses Search Reposiotory to get the data and sends back it in JSON format.

Sample project source code is attached ASPNETMVCControlForYUIDataTable

Advertisements

MEF Tip : Importing multiple instances of a Contract

In the last post, I have used following Contract

Code Snippet
  1. public interface ILogger
  2.     {
  3.         void Debug(string message);
  4.         void Error(string message);
  5.         void Warn(string message);
  6.     }

 

To import a collection of ILogger the ImportManyAttribute  can be used like:

 

Code Snippet
  1. [ImportMany(typeof(ILogger))]
  2. public IEnumerable<ILogger> Loggers { get; set; }

Composition using MEF

 

With .NET 4.0 Microsoft ships a new composition container named Managed Extensibility Framework (MEF). MEF already have two feathers in the cap: Visual Studio 2010 editor extensions and Intellipad, the DSL editor of OSLO SQL Server Modeling.

MEF can be used to achieve the object-oriented programming  principle “Open-Closed Principle (OCP)”  software entities (classes, modules, functions, etc.) should be open for extension, but closed for modifications. What that means to me is that it should be possible to extend the application without modifying its case. For example, if an application was programmed to log to an text file and a later point of time if the requirement is to write to a console, then it should be possible to do so without modifying the source code. To achieve OCP we need to provide extension points. We can found a lot of such examples in the addin model of the applications like Visual Studio text editor, Outlook, WinAmp etc.

 

The fundamental concepts in MEF are : Import, Export, Composable Part, Contracts, Composition. The following diagram shows how they are related :

image_4_129702C1

 

A Composable Part should have at least one or more Export or Import. Imports and Exports are defined using Contracts.  A catalog of parts is used by container to compose the parts. Lets see a simple example of this.

 

Let’s define a contract first for the Imports and Exports. I have a ILogger interface as contract.

Code Snippet
  1. public interface ILogger
  2.     {
  3.         void Debug(string message);
  4.         void Error(string message);
  5.         void Warn(string message);
  6.     }

 

A ConsoleLogger class which implements ILogger and writes to the Console.

Code Snippet
  1. using System;
  2. using System.ComponentModel.Composition;
  3.  
  4.  
  5. namespace CdcSoftware.Loggers
  6. {
  7.     [Export(typeof(ILogger))]
  8.     public class ConsoleLogger : ILogger
  9.     {
  10.         #region ILogger Members
  11.  
  12.         public void Debug(string message)
  13.         {
  14.             Console.WriteLine("[DEBUG] " +  message);
  15.         }
  16.  
  17.         public void Error(string message)
  18.         {
  19.             Console.WriteLine("[ERROR] " + message);
  20.         }
  21.  
  22.         public void Warn(string message)
  23.         {
  24.             Console.WriteLine("[WARN] " + message);
  25.         }
  26.  
  27.         #endregion
  28.     }
  29. }

 

At line no. 2 the we import System.ComponentModel.Composition namespace from the references assembly System.ComponentModel.Composition one of the MEF assembly. This is required for the ExportAttribute used at line no. 7. By using Export attribute we have exported the ConsoleLogger as a composable part .

Now we have a contract and an export. Lets see the code to import. To import we need to use the ImportAttribute like this. 

Code Snippet
  1. [Import(typeof(ILogger))]
  2. public ILogger Logger { get; set; }

Now lets compose it.

Code Snippet
  1. using System.ComponentModel.Composition;
  2. using System.ComponentModel.Composition.Hosting;
  3. using System.Reflection;
  4.  
  5. namespace CdcSoftware.Application
  6. {
  7.     public class ComposedApplication
  8.     {
  9.         [Import(typeof(ILogger))]
  10.         public ILogger Logger { get; set; }
  11.  
  12.         public static void Main(string[] args)
  13.         {
  14.             ComposedApplication app = new ComposedApplication();
  15.             app.Run();
  16.         }
  17.  
  18.         private void Run()
  19.         {
  20.             Compose();
  21.             //now the application dependency is composed
  22.             Logger.Debug("This is a debug message");
  23.         }
  24.  
  25.         private void Compose()
  26.         {
  27.             AssemblyCatalog catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
  28.             CompositionContainer mefContainer = new CompositionContainer(catalog);
  29.             mefContainer.ComposeParts(this);
  30.         }
  31.  
  32.     }
  33. }

 

On start of the ComposedApplication, the first thing we do is to compose it as in line 20. For this application since all the required parts viz. Imports and Exports, are in the same assembly I am using AssemblyCatalog at line no 27 and initializing with the executing assembly. Then the CompositionContainer is instantiated with this catalog as constructor parameter. To get the dependency fulfilled or the part composed, we need to call the ComposeParts method on the container as in line 29.  After the composition succeeds, all the imports the current class will be fulfilled like the Logger property.

 

References :

Where is Python absolutely necessary in Intellipad? http://social.msdn.microsoft.com/Forums/en-US/oslo/thread/7a440138-1417-4414-89c3-2e6ecd2ab430

Managed Extensibility Framework in the Editor http://msdn.microsoft.com/en-us/library/dd885013.aspx