Sankarsan Bose

.NET Diaries

  Home  |   Contact  |   Syndication    |   Login
  7 Posts | 0 Stories | 2 Comments | 0 Trackbacks

News

Twitter












Archives

Post Categories

Friday, April 03, 2009 #

My journey with jQuery has actually began last month when along with ASP.NET MVC I started exploring the different features provided by jQuery and related plugins.I am simple amazed at the overall design of jQuery core and also by functionalities provided by the various plugins built around jQuery.I am planing to write a series of posts with my observations on jQuery and this happens to be the first in that series.

In this post I will discuss about the magical ready() function in jQuery.I always used to put all the scripts that needs to be executed when the page is loaded in the onload event of the <BODY> tag or set it using window.onload as shown below

<script type="text/javascript">
     function showAlert() {
         alert('Hi');
     }
     window.onload = showAlert;
</script>

This script will execute when the entire page has finished loading including all the images.jQuery exposes a more sophisticated ready event to do the same job as shown below:

<script type="text/javascript" src="./jquery-1.3.2.js"></script>
<script type="text/javascript">
    $(document).ready(function() {
            alert('Hi');
    });
</script>

This ready checks whether DOM has loaded completely or not.If it is loaded the function is executed else it is pushed to the wait queue as shown below:

ready: function(fn) {
    // Attach the listeners
    bindReady();

    // If the DOM is already ready
    if ( jQuery.isReady )
        // Execute the function immediately
        fn.call( document, jQuery );

    // Otherwise, remember the function for later
    else
        // Add the function to the wait list
        jQuery.readyList.push( fn );

    return this;
},

For Internet Explorer it checks the loading status of the DOM using the onreadystatechange event and readyStatus property of the document object as shown below:

document.attachEvent("onreadystatechange", function(){
    if ( document.readyState === "complete" ) {
        document.detachEvent( "onreadystatechange", arguments.callee );
        jQuery.ready();

So this is in short that's all I had to say about ready event in jQuery.This is the most important function of jQuery and we will use in the samples of the next posts to come.


Saturday, January 24, 2009 #

Normally we use the term postback when an ASP.NET page submits it's content back to that page itself.But there can be situation when a page needs to submit it's content to a different target page.This is known as cross page postback.In this post we will discuss about how to handle and implement cross page postback scenario.

Now to make a page postback to another page we have set the PostBackUrl property as shown below:

<asp:Button ID="Button2" runat="server" Text="CrossPagePostback" PostBackUrl="~/TargetForm.aspx" />

After the content is submitted to the target url we need to retrieve the control values and properties of the page.To do this we have use the PreviousPage property of the Page class as shown below.The PreviousPage maintains a reference to the instance of the source page.

Label1.Text = "Crosspage Postback with value :" + (Page.PreviousPage.FindControl("TextBox1") as TextBox).Text;

Suppose we have a public property defined in the source page as shown below:

public string ScreenID { get; set; }

If we want to access this property in the target page then we need to specify the type of source page in the @PreviousPageType directive of the target page:

<%@ PreviousPageType VirtualPath="~/TargetForm.aspx"  %>

Then we can access the property in target page as follows:

Label1.Text = PreviousPage.ScreenID;

Like the IsPostBack property used to identify as post back we can identify a cross page postback using the IsCrossPagePostBack as shown below:

if(Page.PreviousPage.IsCrossPagePostBack)

{

}

Now let us examine the HTML source of the page to understand how the cross page postback is initiated from the browser.From the click event of the button a JavaScript function called WebForm_DoPostBackWithOptions is invoked as shown below:

<input type="submit" name="Button2" value="CrossPagePostback" onclick="javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(&quot;Button2&quot;, &quot;&quot;, false, &quot;&quot;, &quot;TargetForm.aspx&quot;, false, false))" id="Button2" />

This function accepts a WebForm_PostBackOptions function as parameter.The WebForm_PostBackOption accepts the following values as argument

  • "Button2"  - Id of the control trigerring the event
  • ""
  • false
  • ""
  • "TargetForm.aspx" - Target page url.
  • false
  • false

But where is the code for this function.Now we look at the following extra lines of code emitted:

<script src="/WebResource.axd?d=ZhZ_K2lwE0H8HnJR9LeB3Q2&amp;t=633394762369896337" type="text/javascript"></script>

I typed the url of the script using it's src attribute in address bar and downloaded the axd file.On opening the file in notepad I found the definitions of the two functions mentioned above.

//This function creates the post back options object

function WebForm_PostBackOptions(eventTarget, eventArgument, validation, validationGroup, actionUrl, trackFocus, clientSubmit) {
    this.eventTarget = eventTarget; //id of the control trigerring postback
    this.eventArgument = eventArgument; 
    this.validation = validation;
    this.validationGroup = validationGroup;
    this.actionUrl = actionUrl; //URL of the target page
    this.trackFocus = trackFocus;
    this.clientSubmit = clientSubmit;
}

//This function sets the target url
function WebForm_DoPostBackWithOptions(options) {
    var validationResult = true;
    if (options.validation) {
        if (typeof(Page_ClientValidate) == 'function') {
            validationResult = Page_ClientValidate(options.validationGroup);
        }
    }
    if (validationResult) {
        if ((typeof(options.actionUrl) != "undefined") && (options.actionUrl != null) && (options.actionUrl.length > 0)) {
            theForm.action = options.actionUrl; // form action is set to target page url.
        }
        if (options.trackFocus) {
            var lastFocus = theForm.elements["__LASTFOCUS"];
            if ((typeof(lastFocus) != "undefined") && (lastFocus != null)) {
                if (typeof(document.activeElement) == "undefined") {
                    lastFocus.value = options.eventTarget;
                }
                else {
                    var active = document.activeElement;
                    if ((typeof(active) != "undefined") && (active != null)) {
                        if ((typeof(active.id) != "undefined") && (active.id != null) && (active.id.length > 0)) {
                            lastFocus.value = active.id;
                        }
                        else if (typeof(active.name) != "undefined") {
                            lastFocus.value = active.name;
                        }
                    }
                }
            }
        }
    }
    if (options.clientSubmit) {
        __doPostBack(options.eventTarget, options.eventArgument);
    }
}


Sunday, January 18, 2009 #

In my last post I had discussed about the key classes/interfaces involved in the ASP.NET MVC routing process.In this post we will develop sample route handler.To implement a route handler we need to implement the System.Web.Routing.IRouteHandler interface.The GetHttpHandler method of this interface should create the instance of IHttpHandler which in turn has to create the controller instance.Let us concentrate on this IHttpHandler implementation first.Take a close look at the code below:

public class CustomMVCHandler:IHttpHandler
  {
      #region

      public RequestContext RequestContext { get; set; }

      public CustomMVCHandler(RequestContext requestContext)
      {
          this.RequestContext = requestContext;
      }

      #endregion

      #region IHttpHandler Members

   }

So this class implements IHttpHandler and you need pass the System.Web.Routing.RequestContext to instantiate it.The ProcessRequest methods does the following:

  1. Get the controller identifier from RouteData stored in the RequestContext.
  2. Invokes the CreateController method of ControllerFactory to create an controller instance.
  3. Invokes the execute method of the controller. The code sample is shown below:

void IHttpHandler.ProcessRequest(HttpContext context)
   {
       string controllerId = RequestContext.RouteData.GetRequiredString("controllerId");
       IController controller = null;
       IControllerFactory factory = null;
       try
       {
           factory = ControllerBuilder.Current.GetControllerFactory();
           controller = factory.CreateController(RequestContext, controllerId);
           if (controller != null)
           {
               controller.Execute(RequestContext);
           }
       }
       finally
       {
           factory.ReleaseController(controller);
       }
   }

Now the RouteHandler just instantiates this MVCHandler by passing the RequestContext as shown below:

public class CustomRouteHandler:IRouteHandler
   {
       #region IRouteHandler Members

       public System.Web.IHttpHandler GetHttpHandler(RequestContext requestContext)
       {
          return  new CustomMVCHandler(requestContext);
       }

       #endregion
   }

Now we have plug in this route handler into the ASP.NET MVC application by creating a new route object in Application_Start event of global.asax.cs as shown below:

public static void RegisterRoutes(RouteCollection routes)
  {
      routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
      RouteValueDictionary defaults = new RouteValueDictionary();
      defaults.Add("controllerId", "Home");
      defaults.Add("action","Index");
      defaults.Add("id","");
     Route customRoute = new Route("{controllerId}/{action}/{id}",defaults, new SB.Web.Mvc.CustomRouteHandler());
      routes.Add(customRoute);
  }

So our sample route handler is now up and running.


Saturday, January 10, 2009 #

While requesting some resource from an web server we normally type an URL like www.xyz.com/default.aspx?ops=1  in the browser address bar.In this case the URL maps directly to a file residing in the server.Now say we have an URL like www.xyz.com/default/1 .In this case the web application has to be intelligent enough to parse the URL and find out which resource or request handler it will map to.This technique of parsing URL patterns and deciding on the requested resource is known as URL Routing.

Along with ASP.NET MVC framework we have a framework for URL routing as well.The functionality of routing is provided by classes in the assembly System.Web.Routing.dll.In this post I will discuss about the important classes and interfaces of the routing framework and steps in the process of creating an instance of the right controller from the URL pattern.

We have the System.Web.Routing.IRouteHandler interface (which inherits from IHttpHandler interface) defining the method System.Web.IHttpHandler GetHttpHandler(System.Web.Routing.RequestContext requestContext).The default route handler provided by the ASP.NET MVC framework which implements this interface is System.Web.Mvc.MVCRouteHandler.This class instantiates the System.Web.Mvc.MVCHandler which in turn creates the controller instance via controller factory. The key entity class is System.Web.Routing.Route which inherits from System.Web.Routing.RouteBase class.This class is an abstraction of URL route.It stores the url pattern string,routing constraints and the reference to the route handler.There is another class System.Web.Routing.RouteData which stores the values obtained after parsing the url at runtime.This entire routing system is plugged into the ASP.NET pipeline via the System.Web.Routing.UrlRoutingModule which implements IHttpModule.

The basic steps in the routing process are as follows:

  • System.Web.Routing.RouteTable maintains a static reference to collection of Route objects.This is initialised in the Application_Start event.That's why in an ASP.NET MVC application project we can see the following lines of code by default added in global.asax.cs Application_Start event.

         RegisterRoutes(RouteTable.Routes);

         public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                  "Default",                                              // Route name
                  "{controller}/{action}/{id}",                           // URL with parameters
                 new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
            );

       }

  • The UrlRouting module intercepts request.This is the also added by default in web.config of ASP.NET MVC application project as shown below:

         <add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing,Version=3.5.0.0 ,Culture=neutral,   PublicKeyToken=31BF3856AD364E35"/>

  • The UrlRouting module
    • Creates the RouteData object by parsing the url.
    • The System.Web.Routing.RequestContext is created.This object maintains reference to the HttpContext object ,RouteData and Route.
    • Invokes the GetHttpHandler method of the associated MVCRouteHandler instance and passes the RequestContext object as parameter.
  • MVCRouteHandler creates an instance of MVCHandler.
  • MVCHandler
    • Invokes the CreateController method of System.Web.Mvc.DefaultControllerFactory.This method creates the instance of System.Web.Mvc.IController type.
    • Invokes the Execute method of IController instance.
    • Invokes the ReleaseController method of System.Web.Mvc.DefaultControllerFactory.This method disposes the instance of the controller.

This in brief what happens as part of the URL routing process.In my next post I will discuss about developing a custom route handler.


Thursday, December 25, 2008 #

In an ASP.NET MVC application the request URI is of the following format {controller}/{action}/{id}.Based on the controller name in the URI the ControllerFactory instantiates the appropriate Controller class and then based on the action in the URI the corresponding action method in the Controller is executed.Action method then dispatches the right view and by default the location of the views are in the path ~/Views/<Controller>/<Action>.aspx.But how can we change this default behavior and make the location of the view configurable based on Web.config settings.

This can be done very easily by overriding the FindView method of System.Web.Mvc.VirtualPathProviderViewEngine.In my previous post I have discussed in details about the Classes and Interfaces related to View Engines and how views are located.So we have create a class inheriting System.Web.Mvc.WebFormViewEngine and then override the FindView method as shown below:

public  class CustomViewEngine:WebFormViewEngine
{
     public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName)
     {
         string viewPath = string.Empty;
         string masterPath = string.Empty;
         ViewEngineResult result = null;

         if (controllerContext == null) throw new ArgumentNullException("ControllerContext cannot be null");
         if (viewName == null || viewName.Length == 0) throw new ArgumentNullException("ViewName cannot be null");

        //Read View Path from Web.config

         viewPath = ConfigurationManager.AppSettings[viewName];
         if (viewPath == null || viewPath.Length == 0) throw new ConfigurationErrorsException("View Path cannot be null or empty.Please check web.config");
         masterPath = ConfigurationManager.AppSettings[masterName];

       //Create an instance of IView and wrap the results in an ViewEngineResult instance

         result = new ViewEngineResult(CreateView(controllerContext, viewPath, masterPath), this);
         return result;

     }
}

Now we need to remove the default view engine and plug in our custom view engine in Application_Start event of Global.asax.cs as follows:

ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new SB.Web.Mvc.CustomViewEngine());

Lastly we need to add the actual view paths in web.config as shown below:

<add key="Index" value="~/Pages/Index.aspx"/>
<add key="About" value="~/Pages/About.aspx"/>


In this post I will be discussing about the process of locating views in ASP.NET MVC and design of the classes & interfaces involved.In the controller class we normally write a method for each action to be performed and code in that method looks something like this:

public ActionResult About()
       {
           ViewData["Title"] = "About Page";

           return View();
       }

Note the method View() which is invoked at the last step.The method View() of System.Web.Mvc.Controller class returns System.Web.Mvc.ViewResult.Now let us quickly examine the class hierarchy of ViewResult class.

ViewResult Class Hierarchy

  1. System.Web.Mvc.ActionResult - This abstract class has the following abstract method
    • public abstract void ExecuteResult(System.Web.Mvc.ControllerContext context)
  2. System.Web.Mvc.ViewResultBase - This abstract class inherits from System.Web.Mvc.ActionResult.
    • It exposes an abstract method protected abstract System.Web.Mvc.ViewEngineResult FindView(System.Web.Mvc.ControllerContext context).
    • Provides an implementation of public override void ExecuteResult(System.Web.Mvc.ControllerContext context).The major steps in this method are
      • Calls FindView and executes the implementation provided by the subclasses.This method returns an instance of System.Web.Mvc.ViewEngineResult.
      • Calls System.Web.Mvc.ViewEngine.Result.View.Render method to render the View.
    • It also exposes a public property public System.Web.Mvc.IViewEngine ViewEngine { set; get; }
  3. System.Web.Mvc.ViewResult - This abstract class inherits from System.Web.Mvc.ViewResultBase and provides an implementation of FindView method.From FindView it invokes the method ViewEngine.FindView(ControllerContext, string, string)

Thus the control is transferred to the ViewEngine and ViewEngine plays the main role in locating the views.For simplicity of discussion I will discuss about Webforms based views only keeping partial views out of context.

View Engine Class Hierarchy

  1. System.Web.Mvc.IViewEngine- This interface defines the following method
    • System.Web.Mvc.ViewEngineResult FindView(System.Web.Mvc.ControllerContext controllerContext, string viewName, string masterName)
      • Locates the view based on View name,MasterPage name and controller name from the ControllerContext.
    • void ReleaseView(System.Web.Mvc.ControllerContext controllerContext, System.Web.Mvc.IView view)
      • Disposes the View instances if IDisposable is implemented
    • System.Web.Mvc.ViewEngineResult FindPartialView(System.Web.Mvc.ControllerContext controllerContext, string partialViewName)
  2. System.Web.Mvc.VirtualPathProviderViewEngine - This abstract class implements the IViewEngine interface and exposes two abstract methods
    • protected abstract System.Web.Mvc.IView CreatePartialView(System.Web.Mvc.ControllerContext controllerContext, string partialPath)
    • protected abstract System.Web.Mvc.IView CreateView(System.Web.Mvc.ControllerContext controllerContext, string viewPath, string masterPath)
      • This method is implemented by subclasses and creates an instance of type System.Web.Mvc.IView.
    • It also exposes three public properties
      • public string[] MasterLocationFormats { set; get; }
        • Specifies the format of the path where to look for master pages.The default values are ~/Views/{1}/{0}.master,~/Views/Shared/{0}.master where {0} is replaced by controller name and {1} by action name
      • public string[] ViewLocationFormats { set; get; }
        • Specifies the format of the path where to look for forms and user controls.The default values are ~/Views/{1}/{0}.aspx,~/Views/{1}/{0}.ascx,~/Views/Shared/{0}.aspx,~/Views/Shared/{0}.ascx where {0} is replaced by controller name and {1} by action name
      • public string[] PartialViewLocationFormats { set; get; }
  3. System.Web.Mvc.WebFormViewEngine - This class inherits from VirtualPathProviderViewEngine and provides implementation for CreateView and CreatePartialView.CreateView returns an instance of System.Web.Mvc.WebFormView.

View Class Hierarchy

  1. System.Web.Mvc.IView- This interface defines the following method
    • void Render(System.Web.Mvc.ViewContext viewContext, System.IO.TextWriter writer)
      • Renders the view content
  2. System.Web.Mvc.WebFormView- This class implements the IView interface and renders the aspx and ascx pages.

I was actually looking for a way to customize the way ASP.NET MVC locates the views and came to know about all these.I really liked this extensible design particularly the judicious use of Template Design Pattern.In my next post I will discuss about how to customize the locating of the views.


Tuesday, December 16, 2008 #

ASP.NET MVC Framework has been around for quite sometime now.It has now moved to BETA from it's initial CTP versions.I am a very big fan of this framework for it's simplicity and extensibility.For the next few posts I would like discuss about the various extensibility points of this framework.In this post I will note down my observations about implementing a custom controller factory.

When adding a controller to a ASP.NET MVC Web application we see that the Controller name must end with "Controller" and it is recommended that Controller classes to be put inside a subfolder named "Controllers" in the web application directory.Now think of a situation where I have many controller classes and for better maintainability I need to put them into separate assemblies.In addition to that I need to load the controller classes in a configurable manner based on controller name in the URI.

This can be done by implementing a custom controller factory to instantiate the right controller class based on config settings.To do this we need to implement the interface System.Web.Mvc.IControllerFactory .The two methods in this interface are

  1. System.Web.Mvc.IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName) - To create the controller instance based on RequestContext and controller name
  2. void ReleaseController(System.Web.Mvc.IController controller) - Release the controller instance

Following is the sample code with very rudimentary implementation:

public class CustomControllerFactory : IControllerFactory
{

    public IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
    {

        string controllerType = string.Empty;
        IController controller = null;

        //Read Controller Class & Assembly Name from Web.Config
        controllerType = ConfigurationManager.AppSettings[controllerName];

        if (controllerType == null) throw new ConfigurationErrorsException("Assembly not configured for controller " + controllerName);

        //Create Controller Instance

        controller = Activator.CreateInstance(Type.GetType(controllerType)) as IController;
        return controller;

    }

    public void ReleaseController(IController controller)
    {
        //This is a sample implementation
        //If pooling is used write code to return the object to pool
        if (controller is IDisposable)
        {
            (controller as IDisposable).Dispose();

        }
        controller = null;
    }

}

Now the question comes how do we integrate this ControllerFactory with the application.This can be done by setting the Controller factory using System.Web.Mvc.ControllerBuilder in Application_Start event of Global.asax.cs

protected void Application_Start()
{
           RegisterRoutes(RouteTable.Routes);
           ControllerBuilder.Current.SetControllerFactory(typeof(SB.Web.Mvc.CustomControllerFactory));
  }

Lastly we need to add the config entries for the Controller classes and assemblies in Web.config as shown below:

<appSettings>
  <add key="Home" value="SB.Controllers.HomeController, SB.Controllers"/>
</appSettings>

We can enhance this further by adding features like pooling,instancing mode (Singleton) etc.

In my next post I will discuss about my observations about the extension points related to Views.