Monday, May 7, 2012

Aspect Oriented Programming (AOP)

There are many posts available to add AOP to the appliaction but as a starter it was very difficult to follow some of them. so just trying to make an attempt to make this simpler. Recently I wanted to add logging ,auditing, exception handling to an existing appliaction and going by regular norms to add try / catch/ finally blocks for this could be cumbersome so I was searching for the better approach and came across this concept of AOP. This is used when we want to implement a ceratin functionality across platforms in an application e.g. logging. so with this, we intercept the method callls wrap it with information we need to add and execute this. The application I built is pretty straight forward calculator app.
Calculator
  1. using System;
  2.  
  3. namespace AOP
  4. {
  5.     public interface ICalculator
  6.     {
  7.         int Add(int i, int j);
  8.         int Subtract(int i, int j);
  9.         int Multiply(int i, int j);
  10.         int Divide(int i, int j);
  11.     }
  12.  
  13.     public class Calculator : ICalculator
  14.     {
  15.         public int Add(int i, int j)
  16.         {
  17.             var result = i + j;
  18.             Console.WriteLine("result:{0}", result);
  19.             return result;
  20.         }
  21.  
  22.         public int Subtract(int i, int j)
  23.         {
  24.             var result = i - j;
  25.             Console.WriteLine("result:{0}", result);
  26.             return result;
  27.         }
  28.  
  29.         public int Multiply(int i, int j)
  30.         {
  31.             var result = i * j;
  32.             Console.WriteLine("result:{0}", result);
  33.             return result;
  34.         }
  35.  
  36.         public int Divide(int i, int j)
  37.         {
  38.             var result = i / j;
  39.             Console.WriteLine("result: {0}", result);
  40.             return result;
  41.         }
  42.     }
  43. }
We need to log information like //Time : Method {MethodName} execution started //Input parameters : {ParamterName} {ParameterValue} // Output value : {result} //Time taken for execution To add this much information to each method, will make the code with difficult to read / manage and violate Single responsibility and DRY pronciples. so to fix all this, we can intercept each method call of interface and let Castle Windsor create a proxy of this class to help with interception. As I prefer StructureMap as IoC container, I am using EnrichWith feature of StrutureMap to link proxy from Castle Windsor to enrich Calculator functions with logging functionality.
Program.cs
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using Castle.DynamicProxy;
  6. using StructureMap;
  7. using log4net;
  8.  
  9. namespace AOP
  10. {
  11.     class Program
  12.     {
  13.         static void Main(string[] args)
  14.         {
  15.             log4net.Config.XmlConfigurator.Configure();
  16.             ObjectFactory.Initialize(x =>
  17.                                          {
  18.                                              x.Scan(y =>
  19.                                                         {
  20.                                                             y.TheCallingAssembly();
  21.                                                             y.WithDefaultConventions();
  22.                                                         });
  23.                                              var proxy = new ProxyGenerator();
  24.                                              x.For<ICalculator>().EnrichAllWith(
  25.                                                  p =>
  26.                                                  proxy.CreateInterfaceProxyWithTarget<ICalculator>(p,
  27.                                                                                                    new LogInterceptor(
  28.                                                                                                        LogManager.GetLogger(typeof(Program))
  29.                                                                                                        )));
  30.                                          });
  31.  
  32.             
  33.  
  34.             ObjectFactory.GetInstance<ICalculator>().Add(2, 3);
  35.  
  36.             ObjectFactory.GetInstance<ICalculator>().Subtract(3,2);
  37.  
  38.             ObjectFactory.GetInstance<ICalculator>().Multiply(2,3);
  39.  
  40.             ObjectFactory.GetInstance<ICalculator>().Divide(4,2);
  41.  
  42.             Console.ReadLine();
  43.         }
  44.     }
  45.  
  46.     
  47.  
  48.     
  49. }
and finally the LoggingInterceptor which implements IInterceptor from Catsle.DynamicProxy namespace.
LogInterceptor
  1. using System;
  2. using System.Linq;
  3. using Castle.DynamicProxy;
  4. using log4net;
  5.  
  6. namespace AOP
  7. {
  8.     public class LogInterceptor : IInterceptor
  9.     {
  10.         private readonly ILog _logger;
  11.         public LogInterceptor(ILog logger)
  12.         {
  13.             _logger = logger;
  14.         }
  15.  
  16.         public void Intercept(IInvocation invocation)
  17.         {
  18.             try
  19.             {
  20.                 var parameters = invocation.Method.GetParameters().ToList();
  21.                 _logger.InfoFormat("Started Logging method: {0}", invocation.Method.Name);
  22.                 for (int i = 0; i < parameters.Count; i++)
  23.                 {
  24.                     _logger.InfoFormat(" Parameter[{2}] Name: {0} Value: {1}", parameters[i].Name, invocation.Arguments[i], i);
  25.                 }
  26.  
  27.                 invocation.Proceed();
  28.                 _logger.InfoFormat("Logged Successfully return value: {0}", invocation.ReturnValue);
  29.                 _logger.Info("------------------------------------------------------------------------");
  30.                 
  31.             }
  32.             catch (Exception ex)
  33.             {
  34.                _logger.ErrorFormat("Exception occurred: {0}", ex.Message);
  35.             }
  36.             finally
  37.             {
  38.                 _logger.Info("Logging Completed");
  39.             }
  40.  
  41.         }
  42.     }
  43. }
Log4Net Configuration:
  1. <? xml version= "1.0" ?>
  2. < configuration >
  3.   < configSections >
  4.     < section name= "log4net" type= "log4net.Config.Log4NetConfigurationSectionHandler,Log4net" />
  5.   </ configSections >
  6.   < startup >
  7.     < supportedRuntime version= "v4.0" sku= ".NETFramework,Version=v4.0" />
  8.   </ startup >
  9.   < log4net >
  10. < root >
  11. < level value= "DEBUG" />
  12. < appender-ref ref= "LogFileAppender" />
  13. </ root >
  14. < appender name= "LogFileAppender" type= "log4net.Appender.RollingFileAppender" >
  15. < param name= "File" value= "C:\logs\test\log.txt" />
  16. < param name= "AppendToFile" value= "true" />
  17. < maximumFileSize value= "10MB" />
  18. < staticLogFileName value= "true" />
  19. < layout type= "log4net.Layout.PatternLayout" >
  20. < param name= "ConversionPattern" value= "%-5p%d{yyyy-MM-dd hh:mm:ss} – %m%n" />
  21. </ layout >
  22. </ appender >
  23. </ log4net >
  24. </ configuration >

Monday, February 20, 2012

MVC ListBox using EditorTemplates and KnockoutJS

Editor templates are really handy tool when we want to create controls for a specific type. Recently I had a requiremnt to show winform styled ListBox control with lists for Available/Selected and allowing user to move items from one collection to other and move items up/down in selected collection. It was similar to this:
Step 1: Considering this as a pretty common functionality in our application,I created a ListSource class to support this UI.
MvcLists\Models\ListSource.cs
  1. using System.Collections.Generic;
  2.  
  3. namespace MvcLists.Models
  4. {
  5.     public class ListSource
  6.     {
  7.         public List<Item> Available { get; set; }
  8.         public List<Item> Selected { get; set; }
  9.     }
  10.  
  11.     public class Item
  12.     {
  13.         public int Value { get; set; }
  14.         public string Text { get; set; }
  15.     }
  16. }
Step 2: Add a property of type ListSource to Model.
MvcLists\Models\Person.cs
  1. public class Person
  2.     {
  3.         [Tooltip("Enter First Name")]
  4.         public string FirstName { get; set; }
  5.         [Tooltip("Enter Last Name")]
  6.         public string LastName { get; set; }
  7.         [Tooltip("Enter SSN")]
  8.         [Mask("999-99-9999")]
  9.         public string SSN { get; set; }
  10.         [Tooltip("Enter Age")]
  11.         [Mask("99")]
  12.         public string Age { get; set; }
  13.         [Mask("(999)-999-9999")]
  14.         [Tooltip("Enter Phone")]
  15.         public string Phone { get; set; }
  16.         [Mask("99999?-9999")]
  17.         [Tooltip("Enter Zip Code")]
  18.         public string ZipCode { get; set; }
  19.         [Mask("9999-9999-9999-9999")]
  20.         [Tooltip("Enter Credit Card")]
  21.         public string CreaditCard { get; set; }
  22.         [AutoComplete("Person", "GetStates", "state")]
  23.         public string State { get; set; }
  24.         [AutoComplete("Person", "GetStates", "state")]
  25.         public string USState { get; set; }
  26.         public ListSource Country { get; set; }
  27.     }
Step 3: Created an EditorTemplate for this type and added knockoutJS functions for move items left/right/up/down.
MvcLists\Views\Shared\EditorTemplates\ListSource.cshtml
  1. @model MvcLists.Models.ListSource
  2. <style type="text/css">
  3.     .divItem
  4.     {
  5.         float: none;
  6.         border-bottom-width: .2em;
  7.         border-bottom-style: solid;
  8.         border-bottom-color: #000000;
  9.         padding: 5px;
  10.         cursor: pointer;
  11.     }
  12.     .rightBoxItem
  13.     {
  14.         padding-left: 10px;
  15.     }
  16.     .indexCell
  17.     {
  18.         /*border-right-width: .2em;
  19.             border-right-style: solid;
  20.             border-right-color: #900;*/
  21.         padding: 5px;
  22.         padding-right: 10px;
  23.     }
  24.     .selectedItem
  25.     {
  26.         background: gold;
  27.     }
  28.     .header
  29.     {
  30.         font-family: arial;
  31.         color: #003469;
  32.         background-color: #dcdcdc;
  33.         margin: 0 0 10px 0;
  34.         padding: 2px 5px 2px 5px;
  35.     }
  36. </style>
  37. <script src="../../../Scripts/jquery-1.5.1.min.js" type="text/javascript"></script>
  38. <script src="../../../Scripts/knockout-2.0.0.js" type="text/javascript"></script>
  39. <script>
  40.         
  41.         function ListViewModel() {
  42.             var listItem = function(text,id,itemIndex) {
  43.                 this.Text = text;
  44.                 this.Id = id;
  45.             };
  46.             var self = this;
  47.             var listA = @Html.Raw(Json.Encode(Model.Available.ToList()));
  48.             var listB = @Html.Raw(Json.Encode(Model.Selected.ToList()));
  49.             self.listAArray= ko.observableArray(ko.utils.arrayMap(listA,function(list) {
  50.                 return new listItem(list.Text, list.Value,index);
  51.             }));
  52.             var index = 0;
  53.             self.listBArray= ko.observableArray(ko.utils.arrayMap(listB,function(list) {
  54.                 index++;
  55.                 return new listItem(list.Text, list.Value,index);
  56.             }));
  57.             self.changeClass = function(item) {
  58.                 $(item).closest("divItem").toggleClass("selectedItem");
  59.             };
  60.             self.moveRight = function() {
  61.                 $(".leftBox .selectedItem").each(function() {
  62.                     var item=ko.dataFor(this);
  63.                     self.listBArray.push(item);
  64.                     self.listAArray.remove(item);
  65.                 });
  66.             };
  67.             self.moveLeft = function() {
  68.                 $(".rightBox .selectedItem").each(function() {
  69.                     var item = ko.dataFor(this);
  70.                     self.listAArray.push(item);
  71.                     self.listBArray.remove(item);
  72.                     
  73.                 });
  74.             };
  75.             self.moveUp = function() {
  76.                 $(".rightBox .selectedItem").each(function() {
  77.                     var item = ko.dataFor(this);
  78.                     var index = self.listBArray.indexOf(item);
  79.                     if(index>0) {
  80.                         self.listBArray.remove(item);
  81.                         self.listBArray.splice(index - 1, 0, item);
  82.                         $(".rightBox .divItem:eq(" + (index -1) + ")").toggleClass("selectedItem");
  83.                     } else {
  84.                         return false;  
  85.                     }
  86.  
  87.                 });
  88.             };
  89.             self.moveDown = function() {
  90.                 $(".rightBox .selectedItem").each(function() {
  91.                     var item = ko.dataFor(this);
  92.                     var index = self.listBArray.indexOf(item);
  93.                     if(index<self.listBArray.length-1) {
  94.                         self.listBArray.remove(item);
  95.                         self.listBArray.splice(index + 1, 0, item);
  96.                         $(".rightBox .divItem:eq(" + (index +1) + ")").toggleClass("selectedItem");
  97.                     } else {
  98.                         return false;  
  99.                     }
  100.  
  101.                 });
  102.             };
  103.             
  104.             self.afterRender = function() {
  105.                 var i = 0;
  106.                 $(".rightBox .divItem").each(function() {
  107.                     i++;
  108.                     $(this).find(".indexCell")[0].text(i);
  109.                 });
  110.             };
  111.             ko.bindingHandlers.Index = {
  112.                 update:function (element,valueAccessor) {
  113.                     var i = 0;
  114.                     $(".rightBox .indexCell").each(function() {
  115.                         i++;
  116.                         $(this).text(i);
  117.                     });
  118.                     $(".divItem").unbind("click").bind("click", function() {
  119.                         $(this).toggleClass("selectedItem");
  120.                     });
  121.                 }
  122.             };
  123.         }
  124.  
  125.         $(document).ready(function () {
  126.             ko.applyBindings(new ListViewModel());
  127.         });
  128. </script>
  129. @Html.LabelForModel()
  130. <div style="margin-left: 5px; width: 100%;">
  131.     <div style="margin: auto; width: 90%;">
  132.         <div>
  133.             <div style="float: left">
  134.                 <div>
  135.                     <span>Available</span>
  136.                 </div>
  137.                 <div data-bind="foreach:listAArray" style="color: black; background-color: #dcdcdc;
  138.                     display: inline-block; border-style: solid; border-width: 2px; border-bottom-width: 0px;
  139.                     border-color: #000000" class="leftBox">
  140.                     <div class="divItem">
  141.                         <span data-bind="text:Text"></span>
  142.                     </div>
  143.                 </div>
  144.             </div>
  145.             <div style="float: left; vertical-align: middle; padding: 5px; display: inline-block;
  146.                 margin-top: 50px; width: 75px;">
  147.                 <div>
  148.                     <div>
  149.                         <input type="button" data-bind="click:moveRight" value=">" /></div>
  150.                     <div>
  151.                         <input type="button" data-bind="click:moveLeft" value="<" /></div>
  152.                 </div>
  153.             </div>
  154.             <div style="float: left">
  155.                 <div>
  156.                     <span>Selected</span>
  157.                 </div>
  158.                 <div class="rightBox" data-bind="foreach:listBArray,Index:listBArray" style="
  159.                     background-color: #FFFFAA; display: inline-block; border-style: solid; border-width: 2px;
  160.                     border-color: #000000; border-bottom-width: 0px">
  161.                     <span class="indexCell" style="float: left;"></span>
  162.                     <div class="divItem rightBoxItem">
  163.                         <span data-bind="text:Text"></span>
  164.                     </div>
  165.                 </div>
  166.             </div>
  167.             <div style="float: left; vertical-align: middle; padding: 5px; display: inline-block;
  168.                 margin-top: 50px; width: 75px;">
  169.                 <div style="background-color: bisque;">
  170.                     <div>
  171.                         <input type="button" data-bind="click:moveUp" value="Move Up" /></div>
  172.                     <div>
  173.                         <input type="button" data-bind="click:moveDown" value="Move Down" /></div>
  174.                 </div>
  175.             </div>
  176.             <div style="clear: both">
  177.             </div>
  178.         </div>
  179.     </div>
  180. </div>
Step 4: In controller/viewModel set DataSource for list box.
MvcLists\Controllers\PersonController.cs
  1. public ListSource Countries
  2.         {
  3.             get
  4.             {
  5.                 return new ListSource
  6.                            {
  7.                                Available = new List<Item>
  8.                                                {
  9.                                                    new Item {Text = "US", Value = 1},
  10.                                                    new Item {Text = "UK", Value = 2},
  11.                                                    new Item {Text = "Canada", Value = 3},
  12.                                                    new Item {Text = "France", Value = 4}
  13.                                                },
  14.                                Selected = new List<Item> {new Item {Text = "India", Value = 5}}
  15.                            };
  16.             }
  17.         }
Step 5: Render this listbox(Country)
MvcLists\Views\Person\Index.cshtml
  1. @using MvcLists.Common.HtmlHelpers
  2. @model MvcLists.Models.Person
  3. @{
  4.     ViewBag.Title = "Index";
  5.     Layout = "~/Views/Shared/_Layout.cshtml";
  6. }
  7. <h2>
  8.     Index</h2>
  9. <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
  10. <script src="../../Scripts/jquery.maskedinput.js" type="text/javascript"></script>
  11. @using (Html.BeginForm("Index","Person"))
  12. {
  13.     <fieldset>
  14.         <legend>Person</legend>
  15.         <div class="editor-label">
  16.             @Html.LabelFor(model => model.FirstName)
  17.         </div>
  18.         <div class="editor-field">
  19.             @Html.TextBoxFor(model => model.FirstName,new{title=@Html.TooltipFor(x=>x.FirstName)})
  20.         </div>
  21.         <div class="editor-label">
  22.             @Html.LabelFor(model => model.LastName)
  23.         </div>
  24.         <div class="editor-field">
  25.             @Html.TextBoxFor(model => model.LastName,new{title=Html.TooltipFor(x=>x.LastName)})
  26.         </div>
  27.         @Html.EditorFor(x => x.SSN)
  28.         @Html.EditorFor(x => x.Age)
  29.         @Html.EditorFor(x => x.Phone)
  30.         @Html.EditorFor(x => x.CreaditCard)
  31.         @Html.EditorFor(x => x.ZipCode)
  32.         @Html.EditorFor(x=>x.State)
  33.         @Html.EditorFor(x=>x.USState)
  34.         @Html.EditorFor(x=>x.Country)
  35.  
  36.         <p>
  37.             <input type="submit" value="Create" />
  38.         </p>
  39.     </fieldset>
  40. }
  41. <div>
  42.     @Html.ActionLink("Back to List", "Index")
  43. </div>

Sunday, February 19, 2012

MVC Mask Attribute for Inputs

I created a mask input in previous posts where I declared Jquery as a constant and added it to Scripts collection. Alternate approach can be generate an Id for each mask input and use scripts in EditorTemplate.
MaskAttribute
  1. public void OnMetadataCreated(ModelMetadata metadata)
  2.         {
  3.             metadata.TemplateHint = "_maskInput";
  4.             metadata.AdditionalValues["id"] = Guid.NewGuid();
  5.             metadata.AdditionalValues["mask"] = Mask;
  6.         }
Register mask for input in document ready event.
Views\Shared\EditorTemplates\_maskInput.cshtml
  1. @using MvcLists.Common.CustomAttributes
  2. @model System.String
  3. @{
  4.     var additionalValues = ViewData.ModelMetadata.AdditionalValues;
  5.     var mask = additionalValues.SingleOrDefault(x=>x.Key=="mask").Value;
  6.     var id = additionalValues.SingleOrDefault(x => x.Key == "id").Value;
  7. }
  8. @{ var maskedInput = ViewData.GetModelAttribute<MaskAttribute>();
  9.    if (maskedInput != null)
  10.    {
  11.         <div class="editor-label">
  12.             @Html.LabelForModel()
  13.         </div>
  14.         <div class="editor-field">
  15.             @Html.TextBoxFor(m => m, new { id = @id })
  16.         </div>
  17.    }
  18. }
  19. <script type="text/javascript">
  20.     $(document).ready(function () {
  21.         $("#@id").mask('@mask');
  22.     });
  23. </script>

Saturday, February 18, 2012

MVC strip mask characters

In Previous post,I created masked inputs but on form post,it is posting mask characters as well. We can strip these characters using ModelBinders and thus can post numeric data only.
Common\ModelBinders\StripMaskCharacters.cs
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text.RegularExpressions;
  5. using System.Web;
  6. using System.Web.Mvc;
  7. using MvcLists.Common.CustomAttributes;
  8.  
  9. namespace MvcLists.Common.ModelBinders
  10. {
  11.     public class StripMaskCharacters : DefaultModelBinder
  12.     {
  13.         protected override void SetProperty(ControllerContext controllerContext,
  14.                                             ModelBindingContext bindingContext,
  15.                                             System.ComponentModel.PropertyDescriptor propertyDescriptor,
  16.                                             object value)
  17.         {
  18.            if(value!=null && propertyDescriptor.PropertyType==(typeof(string)))
  19.            {
  20.                value = ((string) value).Trim();
  21.                if ((string)value == string.Empty)
  22.                {
  23.                    value = null;
  24.                }
  25.                else if(propertyDescriptor.Attributes[typeof(MaskAttribute)]!=null
  26.                    && bindingContext.ValueProvider.GetValue(propertyDescriptor.Name)!=null
  27.                    && bindingContext.ValueProvider.GetValue(propertyDescriptor.Name).AttemptedValue!=null)
  28.                {
  29.                    value = Regex.Replace(bindingContext.ValueProvider.GetValue(propertyDescriptor.Name).AttemptedValue,
  30.                                          "[^0-9]", string.Empty);
  31.                }
  32.            }
  33.             base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value);
  34.         }
  35.     }
  36. }
Then register this model binder in Application_Start of global.asax
Global.asax
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Web;
  5. using System.Web.Mvc;
  6. using System.Web.Routing;
  7. using FluentValidation.Mvc;
  8. using MvcLists.Common.DataAnnotations;
  9. using MvcLists.Common.ModelBinders;
  10.  
  11. namespace MvcLists
  12. {
  13.     // Note: For instructions on enabling IIS6 or IIS7 classic mode,
  14.     // visit http://go.microsoft.com/?LinkId=9394801
  15.  
  16.     public class MvcApplication : System.Web.HttpApplication
  17.     {
  18.         public static void RegisterGlobalFilters(GlobalFilterCollection filters)
  19.         {
  20.             filters.Add(new HandleErrorAttribute());
  21.         }
  22.  
  23.         public static void RegisterRoutes(RouteCollection routes)
  24.         {
  25.             routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
  26.  
  27.             routes.MapRoute(
  28.                 "Default", // Route name
  29.                 "{controller}/{action}/{id}", // URL with parameters
  30.                 new { controller = "Person", action = "Index", id = UrlParameter.Optional } // Parameter defaults
  31.             );
  32.  
  33.         }
  34.  
  35.         protected void Application_Start()
  36.         {
  37.             AreaRegistration.RegisterAllAreas();
  38.  
  39.             RegisterGlobalFilters(GlobalFilters.Filters);
  40.             RegisterRoutes(RouteTable.Routes);
  41.             ModelMetadataProviders.Current=new MyModelMetaDataProvider();
  42.             ModelBinders.Binders.DefaultBinder = new StripMaskCharacters();
  43.             FluentValidationModelValidatorProvider.Configure();
  44.         }
  45.         protected void Application_BeginRequest()
  46.         {
  47.             HttpContext.Current.Response.Cache.SetExpires(DateTime.UtcNow.AddDays(-1));
  48.             HttpContext.Current.Response.Cache.SetValidUntilExpires(false);
  49.             HttpContext.Current.Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
  50.             HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.NoCache);
  51.             HttpContext.Current.Response.Cache.SetNoStore();
  52.  
  53.         }
  54.     }
  55. }