Showing posts with label asp.net mvc. Show all posts
Showing posts with label asp.net mvc. Show all posts

Apr 29, 2010

Loading common model into view

In our view, there are some data which are require across all page, such as site bar data, menu, and so on. We can split them into partial view and using Html.RenderAction. But this should not be overused, because of performance issue and also it kind of violate separation of concerns. In this case, the view is controlling data. If we want to use Html.RenderPartial, data can be explictly passed in Parent View, or we can inject the data into ViewData in the stack before ActionResult is returned from controller. For example, we can use base controller, and override

public void BaseController : Controller
{
    protected override OnActionExecuting(ActionExecutingContext context)
    {
        ViewData.Add(your_data);
    }
}

Another option is use ActionFilterAttribute. It is designed to solve cross-cutting concerns, like authorization, logging and so on. But we can use it to load common data as well.


public class RequireCommonDataAttribute : ActionFilterAttribute
{
   public override void OnActionExecuting(ActionExecutionContext filterContext)
   {
        filterContext.Controller.ViewData.Add(your_data);

   }
}

public class MyController
{
   [RequireCommonDataAtrribute]
   public ActionResult View()
   {
    }
}

Mar 2, 2010

change in mvc2 project

  1. project file change, replace guid {603c0e0b-db56-11dc-be95-000d561079b0} with {F85E285D-A4E0-4152-9332-AB1D724D3325} in ProejctTypeGuids nodes
  2. Reference dll change, update all reference in web.config files and project files update runtime binding.
    <runtime>  <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">    <dependentAssembly>      
    <assemblyIdentity name="System.Web.Mvc"           publicKeyToken="31bf3856ad364e35"/>      <bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0"/>    </dependentAssembly>  </assemblyBinding></runtime>
    
  3. javascript file changes, copy new script from new project to old project

Jan 19, 2010

Links in asp.net

There are serveral helper class which help to generate links.

//When you generate link with the same controller 
Html.ActionLink();

//When you generate link with the different controller
Html.RouteLink();


Ajax.ActionLink(); //ajax version of Html.ActionLink(..)
Ajax.RouteLink();  //ajax version of Html.RouteLink(..)

//generate url only, not anchor tag
Url.RouteUrl();


//eventually all method will call
internal static string GenerateUrl(string routeName, string actionName, string controllerName, RouteValueDictionary routeValues, RouteCollection routeCollection, RequestContext requestContext, bool includeImplicitMvcValues) {
    RouteValueDictionary mergedRouteValues = RouteValuesHelpers.MergeRouteValues(actionName, controllerName, requestContext.RouteData.Values, routeValues, includeImplicitMvcValues);

    VirtualPathData vpd = routeCollection.GetVirtualPath(requestContext, routeName, mergedRouteValues);
    if (vpd == null) {
        return null;
    }

    string modifiedUrl = PathHelpers.GenerateClientUrl(requestContext.HttpContext, vpd.VirtualPath);
    return modifiedUrl;
}

Jan 15, 2010

Namespace and Controller in asp.net mvc

In .net a type has a namespace, if two type names are the same, but they live in different namespace, this is not a problem. So when asp.net resolve the type with the controller name, DefaultController factory by default does not care namespace. So if there are two Controllers with the sname name in your reference dlls, then there will be an exception saying ambigous controllers found. But you can help the DefaultControllerFactory to resolve this conflict using the follow sample code.

string[] routeContollerSearchHints_NameSpace = new string[] { "MvcAppTest.Controllers" };
string routeName = "Default";
string url = "{controller}/{action}/{id}";
object defaults = new { controller = "Home", action = "Index", id = "" };
object constrants = null;
routes.MapRoute(routeName, url, defaults, constrants, routeContollerSearchHints_NameSpace);

// or 
string[] globalContollerSearchHints_NameSpace = new string[] { "MvcAppTest.Controllers" };
ControllerBuilder.Current.DefaultNamespaces.UnionWith(globalContollerSearchHints_NameSpace);

Test Example in System.Web.Mvc

Because the mvc project and test project are seperate, we need the test project to see the internal member in System.Web.Mvc project, so there is a attribute in the mvc project.

[assembly: InternalsVisibleTo("System.Web.Mvc.Test")]

The effort to make the project testable of the mvc project is very obvious. For example, to make DefaultControllerFactory testable, the class has a property setter, so that test project can inject mocking object.

DefaultControllerFactory factory = new DefaultControllerFactory();
MockBuildManager buildManagerMock = new MockBuildManager(new Assembly[] { });
ControllerTypeCache controllerTypeCache = new ControllerTypeCache();

factory.BuildManager = buildManagerMock;
factory.ControllerTypeCache = controllerTypeCache;

// Act
Type controllerType = factory.GetControllerType("sometype");

// Assert
Assert.IsNull(controllerType, "Shouldn't have found a controller type.");
Assert.AreEqual<int>(0, controllerTypeCache.Count, "Cache should be empty.");

//member of DefaultControllerFactory
internal IBuildManager BuildManager {
    get {
        if (_buildManager == null) {
            _buildManager = new BuildManagerWrapper();
        }
        return _buildManager;
    }
    set {
        _buildManager = value;
    }
}

internal ControllerBuilder ControllerBuilder {
    get {
        return _controllerBuilder ?? ControllerBuilder.Current;
    }
    set {
        _controllerBuilder = value;
    }
}

Another example is as follow. Even we need to increase performance of the by using staticControllerTypeCase, we still allow test project to inject an _instanceControllerTypeCase for testing.

//member of DefaultControllerFactory
internal ControllerTypeCache ControllerTypeCache {
    get {
        return _instanceControllerTypeCache ?? _staticControllerTypeCache;
    }
    set {
        _instanceControllerTypeCache = value;
    }
}

//member of ControllerContext
//RouteData can be mocked.
public virtual RouteData RouteData {
    get {
        if (_routeData == null) {
            _routeData = (_requestContext != null) ? _requestContext.RouteData : new RouteData();
        }
        return _routeData;
    }
    set {
        _routeData = value;
    }
}

Jan 12, 2010

Custom T4 host for asp.net mvc

T4 template can be hosted by different environment. By default template is not HostSpecific. When you want to reuse a template to generate different file, you can customize the generation process by providing a dialog window. This is what MvcTextTemplateHost does. ASP.NET MVC tools is included in C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\Microsoft.VisualStudio.Web.Extensions.dll , which is not a open source project. But you can use Reflector to view some of its implementation. The dll provide some context menu to add view , add controller , and some dialog box for use to customize the code generation. Here is article about the MvcTextTemplateHost

Dec 17, 2009

A dummy example of html helper

public static class HtmlHelperExt
{
    public static string GetDebugInfo(this HtmlHelper html)
    {
        return "Hello";
    }

    public static void Debug(this HtmlHelper html)
    {
        var writer = html.ViewContext.HttpContext.Response.Output;
        writer.Write("

hello

"); } }

Nov 11, 2009

How Routing Works in asp.net mvc

The mvc stack start with the UrlRoutingModule. The module handle two HttpApplication event.

/* the order of application event is:
OnBeginRequest
OnAuthenticateRequest
OnPostAuthenticateRequest
OnAuthorizeRequest
OnPostAuthorizeRequest
OnResolveRequestCache
OnPostResolveRequestCache --> handled by  PostResolveRequestCache
OnPostMapRequestHandler --> handled by PostMapRequestHandler
OnAcquireRequestState
OnPostAcquireRequestState
OnPreRequestHandlerExecute
Page_Load Event of the Page
OnPostRequestHandlerExecute
OnReleaseRequestState
OnPostReleaseRequestState
OnUpdateRequestCache
OnPostUpdateRequestCache
OnEndRequest
OnPreSendRequestHeaders */
public virtual void PostResolveRequestCache(HttpContextBase context) { 
    // Match the incoming URL against the route table
    RouteData routeData = RouteCollection.GetRouteData(context); 

    // Do nothing if no route found
    if (routeData == null) { 
        return;
    }

    // If a route was found, get an IHttpHandler from the route's RouteHandler 
    IRouteHandler routeHandler = routeData.RouteHandler;
    if (routeHandler == null) { 
        throw new InvalidOperationException( 
            String.Format(
                CultureInfo.CurrentUICulture, 
                RoutingResources.UrlRoutingModule_NoRouteHandler));
    }

    // This is a special IRouteHandler that tells the routing module to stop processing 
    // routes and to let the fallback handler handle the request.
    if (routeHandler is StopRoutingHandler) { 
        return; 
    }

    RequestContext requestContext = new RequestContext(context, routeData);

    IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
    if (httpHandler == null) { 
        throw new InvalidOperationException(
            String.Format( 
                CultureInfo.CurrentUICulture, 
                RoutingResources.UrlRoutingModule_NoHttpHandler,
                routeHandler.GetType())); 
    }

    // Save data to be used later in pipeline
    context.Items[_requestDataKey] = new RequestData() { 
        OriginalPath = context.Request.Path,
        HttpHandler = httpHandler 
    }; 

    // Rewrite path to something registered as a managed handler in IIS.  This is necessary so IIS7 will 
    // execute our managed handler (instead of say the static file handler).
    context.RewritePath("~/UrlRouting.axd");
}

public virtual void PostMapRequestHandler(HttpContextBase context)
{
    RequestData requestData = (RequestData)context.Items[_requestDataKey];
    if (requestData != null)
    {
        // Rewrite the path back to its original value, so the request handler only sees the original path.
        context.RewritePath(requestData.OriginalPath);

        // Set Context.Handler to the IHttpHandler determined earlier in the pipeline.
        context.Handler = requestData.HttpHandler;
    }
}

The route matching is handled by "RouteData routeData = RouteCollection.GetRouteData(context);". It search the Routes defined in start up event. If RouteData is not found, the handling will be passed back. Below is the code how RouteCollection handle the matching.

//RouteCollection member
public RouteData GetRouteData(HttpContextBase httpContext) { 
//... code skip ...
    // Go through all the configured routes and find the first one that returns a match
    using (GetReadLock()) { 
        foreach (RouteBase route in this) {
            RouteData routeData = route.GetRouteData(httpContext);
            if (routeData != null) {
                //the first matching wins
                return routeData; 
            }
        } 
    } 
    return null; 
}

//Route member
public override RouteData GetRouteData(HttpContextBase httpContext) {
    // Parse incoming URL (we trim off the first two chars since they're always "~/")
    string requestPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo;

    RouteValueDictionary values = _parsedRoute.Match(requestPath, Defaults); 

    if (values == null) {
        // If we got back a null value set, that means the URL did not match 
        return null;
    }

    RouteData routeData = new RouteData(this, RouteHandler); 

    // Validate the values 
    if (!ProcessConstraints(httpContext, values, RouteDirection.IncomingRequest)) { 
        return null;
    } 

    // Copy the matched values
    foreach (var value in values) {
        routeData.Values.Add(value.Key, value.Value); 
    }

    // Copy the DataTokens from the Route to the RouteData 
    if (DataTokens != null) {
        foreach (var prop in DataTokens) { 
            routeData.DataTokens[prop.Key] = prop.Value;
        }
    }

    return routeData;
} 

public string Url {
    get { 
        return _url ?? String.Empty; 
    }
    set { 
        // The parser will throw for invalid routes. We don't have to worry
        // about _parsedRoute getting out of sync with _url since the latter
        // won't get set unless we can parse the route.
        _parsedRoute = RouteParser.Parse(value); 

        // If we passed the parsing stage, save the original URL value 
        _url = value; 
    }
} 

When the Url is set for a route, a _parsedRoute is created based on the Url, the _parseRoute is used to match the request url with the help of default values. Then the constraint is applied. If match is found, the RouteData can be returned. Before that DataTokens is copied to RouteData as well. DataToken is not actually used in the process of matching, we can think of DataToken is some predefined RouteData. RouteCollection has another use , to generate url based on routes. When search RouteData, constraints are also tested. Normally, it is an regular expression, but you can roll out your customized constraints, here is an article about this.

//route collection member
public VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) {
    requestContext = GetRequestContext(requestContext); 

    // Go through all the configured routes and find the first one that returns a match 
    using (GetReadLock()) { 
        foreach (RouteBase route in this) {
            VirtualPathData vpd = route.GetVirtualPath(requestContext, values); 
            if (vpd != null) {
                vpd.VirtualPath = GetUrlWithApplicationPath(requestContext, vpd.VirtualPath);
                return vpd;
            } 
        }
    } 

    return null;
} 

public VirtualPathData GetVirtualPath(RequestContext requestContext, string name, RouteValueDictionary values) {
    requestContext = GetRequestContext(requestContext);

    if (!String.IsNullOrEmpty(name)) {
        RouteBase namedRoute; 
        bool routeFound; 
        using (GetReadLock()) {
            routeFound = _namedMap.TryGetValue(name, out namedRoute); 
        }
        if (routeFound) {
            VirtualPathData vpd = namedRoute.GetVirtualPath(requestContext, values);
            if (vpd != null) { 
                vpd.VirtualPath = GetUrlWithApplicationPath(requestContext, vpd.VirtualPath);
                return vpd; 
            } 
            return null;
        } 
        else {
            throw new ArgumentException(
                String.Format(
                    CultureInfo.CurrentUICulture, 
                    RoutingResources.RouteCollection_NameNotFound,
                    name), 
                "name"); 
        }
    } 
    else {
        return GetVirtualPath(requestContext, values);
    }
} 

You can choose a named route or use all routes to build a url. It delegate build url to Route.GetVirtualPath method

public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) {
    // Try to generate a URL that represents the values passed in based on current 
    // values from the RouteData and new values using the specified Route.

    BoundUrl result = _parsedRoute.Bind(requestContext.RouteData.Values, values, Defaults, Constraints);

    if (result == null) {
        return null; 
    } 

    // Verify that the route matches the validation rules 
    if (!ProcessConstraints(requestContext.HttpContext, result.Values, RouteDirection.UrlGeneration)) {
        return null;
    }

    VirtualPathData vpd = new VirtualPathData(this, result.Url);

    // Add the DataTokens from the Route to the VirtualPathData 
    if (DataTokens != null) {
        foreach (var prop in DataTokens) { 
            vpd.DataTokens[prop.Key] = prop.Value;
        }
    }
    return vpd; 
}

Route is importaint object, it is used in url generation. It has Constraints, DataTokens, Defaults properties.

Nov 10, 2009

HttpContextBase.Items

When you using controller, you can easily access HttpContext property, which is not an System.Web.HttpContext, but a HttpContextBase object. This object has a handy Items dictionary, which can allowed you share data over a the whole request.

TempData of Controller

TempData is property of Controller. The TempData is used to save the state from page redirected from to the page redirected. So let say, your request is handled by controller1.method1, for some reason, it should be redirected to controller2.method2. Before the redirection take place, you can save some data in the TempData, then http status code 302 and new url are returned to browser, and browser make a new request using new url. In the new action method, the TempData can be restored. This behavior is showed in the following code in the Controller

//Controller member
protected override void ExecuteCore() {
    TempData.Load(ControllerContext, TempDataProvider);

    try {
        string actionName = RouteData.GetRequiredString("action");
        if (!ActionInvoker.InvokeAction(ControllerContext, actionName)) {
            HandleUnknownAction(actionName);
        }
    }
    finally {
        TempData.Save(ControllerContext, TempDataProvider);
    }
}

This piece of code does not show how data is actually load and save, the job is actually performed by TempDataProvider. And the default provider is SessionStateTempDataProvider. We can override this TempDataProvider by injecting this property using custom ControllerFactory.

//Controller member
public ITempDataProvider TempDataProvider {
    get {
        if (_tempDataProvider == null) {
            _tempDataProvider = new SessionStateTempDataProvider();
        }
        return _tempDataProvider;
    }
    set {
        _tempDataProvider = value;
    }
}

//TempDataDictionary members
public void Load(ControllerContext controllerContext, ITempDataProvider tempDataProvider) {
    IDictionary<string, object> providerDictionary = tempDataProvider.LoadTempData(controllerContext);
    _data = (providerDictionary != null) ? new Dictionary<string, object>(providerDictionary, StringComparer.OrdinalIgnoreCase) :
        new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
    _initialKeys = new HashSet<string>(_data.Keys);
    _modifiedKeys.Clear();
}

public void Save(ControllerContext controllerContext, ITempDataProvider tempDataProvider) {
   //this is very important, if there is no modified, we don't need to save it
   //for next use 
   if (_modifiedKeys.Count > 0) {

        // Apply change tracking.
        foreach (string x in _initialKeys) {
            if (!_modifiedKeys.Contains(x)) {
                _data.Remove(x);
            }
        }

        // Store the dictionary
        tempDataProvider.SaveTempData(controllerContext, _data);
    }
}

public class SessionStateTempDataProvider : ITempDataProvider {
    internal const string TempDataSessionStateKey = "__ControllerTempData";

    public virtual IDictionary<string, object> LoadTempData(ControllerContext controllerContext) {
        HttpContextBase httpContext = controllerContext.HttpContext;
        
        if (httpContext.Session == null) {
            throw new InvalidOperationException(MvcResources.SessionStateTempDataProvider_SessionStateDisabled);
        }

        Dictionary<string, object> tempDataDictionary = httpContext.Session[TempDataSessionStateKey] as Dictionary<string, object>;

        if (tempDataDictionary != null) {
            // If we got it from Session, remove it so that no other request gets it
            httpContext.Session.Remove(TempDataSessionStateKey);
            return tempDataDictionary;
        }
        else {
            return new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
        }
    }

    public virtual void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values) {
        HttpContextBase httpContext = controllerContext.HttpContext;

        if (httpContext.Session == null) {
            throw new InvalidOperationException(MvcResources.SessionStateTempDataProvider_SessionStateDisabled);
        }

        httpContext.Session[TempDataSessionStateKey] = values;
    }        
}

Extension point in asp.net mvc

  1. Extending route.

    Route matching is first step mvc framework, we can customize how route is matched by adding constraint, and default, and dataToken. We can implement IRouteConstraint. Here is ASP.NET MVC Tip #30 – Create Custom Route Constraints

  2. Extending IRouteHandler

    When we register route, by default the route handler is MvcRouteHandler. We can create a class implmenting IRouteHandler, of we can sub class MvcRouteHandler

  3. Exending MvcHandler

    MvcHandler is IHttpHandler, we can substitute with a MvcHandler sub class, or a complete new IHttpHandler , or override MvcHandler's ProcessRequest method. We can override ProcessRequest(HttpContextBase httpContext) method.

  4. Extending ControllerFactory and Controller. We can decide how a controller is created. A dummy example is as follow. But you can create a controller by using IoC container.

    ControllerBuilder.Current.SetControllerFactory(new ObjectControllerFactory());
    
    public class ObjectControllerFactory:DefaultControllerFactory
    {
    
        public override IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
        {
            if (controllerName.ToLower() == "object")
            {
                return new ObjectController(new object());
            }
            else
            {
                return base.CreateController(requestContext, controllerName);
            }
        }
    
    }
    
  5. Extending the ActionResult.

  6. Extending the Controller.

  7. Extending ModelBinder

  8. Extending ViewResult

  9. Extending ViewEngine, and IView

    We can subclass the default WebFormViewEngine, or implement and IViewEngine. The interesting method is FindView and FindPartialView.

  10. Extending ViewPage or ViewUserControl

  11. Extending HtmlHelper by adding HtmlHelper extension method.

How a view is found

There are some confusing names here. ViewResult is kind of ActionResult. It is return by an object of Controller class. ViewResult uses VewEngineCollection, a collection of IViewEngine, to find a ViewEngineResult. ViewEngineResult may or may not contain an IView. IView is implemented by ViewPage, ViewUserControl.

After a action result is returned from the action method, if it is ViewResult. The render method will find an ViewEngineResult through ViewEngineCollection object. The following code show how ViewEngineCollection finds ViewEngineResult, ViewEngineCollection enumerate all the ViewEngine to fine ViewEngineResult.

//ViewEngineCollection member
private ViewEngineResult Find(Func<IViewEngine, ViewEngineResult> cacheLocator, Func<IViewEngine, ViewEngineResult> locator) {
    ViewEngineResult result;

    foreach (IViewEngine engine in Items) {
        if (engine != null) {
            result = cacheLocator(engine);

            if (result.View != null) {
                return result; //With IView
            }
        }
    }

    List<string> searched = new List<string>();

    foreach (IViewEngine engine in Items) {
        if (engine != null) {
            result = locator(engine);

            if (result.View != null) {
                return result; //With IView
            }

            searched.AddRange(result.SearchedLocations);
        }
    }
    //a ViewEngineResult with no IView
    return new ViewEngineResult(searched);
}

If the ViewEngineResult return by by IViewEngine has an IView object, it will be returned immediately, it will be returned immediately to the ViewResult caller. If you want to use your special IView object, you need to add IViewEngine to locate your IView. If there is no ViewEngineResult with an IView is found, return new ViewEngineResult with no IView to caller ViewResult, let see how ViewResult handle this.

protected override ViewEngineResult FindView(ControllerContext context) {
    ViewEngineResult result = ViewEngineCollection.FindView(context, ViewName, MasterName);
    if (result.View != null) {
        return result;
    }

    // we need to generate an exception containing all the locations we searched
    StringBuilder locationsText = new StringBuilder();
    foreach (string location in result.SearchedLocations) {
        locationsText.AppendLine();
        locationsText.Append(location);
    }
    throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture,
        MvcResources.Common_ViewNotFound, ViewName, locationsText));
}

It throw an exception. If a ViewEngineResult with IView is returned, ViewResult will render the IView.

//ViewResultBase member
public override void ExecuteResult(ControllerContext context) {
    if (context == null) {
        throw new ArgumentNullException("context");
    }
    if (String.IsNullOrEmpty(ViewName)) {
        ViewName = context.RouteData.GetRequiredString("action");
    }

    ViewEngineResult result = null;

    if (View == null) {
        result = FindView(context);
        View = result.View;
    }

    ViewContext viewContext = new ViewContext(context, View, ViewData, TempData);
    View.Render(viewContext, context.HttpContext.Response.Output);

    if (result != null) {
        result.ViewEngine.ReleaseView(context, View);
    }
}

Here is example to how to implement an IView.

public class StaticView : IView
{

    public void Render(ViewContext viewContext, System.IO.TextWriter writer)
    {
        writer.Write("

Hello world

"); } } public class StaticViewEngine : IViewEngine { public ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache) { ViewEngineResult result = new ViewEngineResult(new StaticView(), this); return result; } public ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) { ViewEngineResult result = new ViewEngineResult(new StaticView(), this); return result; } public void ReleaseView(ControllerContext controllerContext, IView view) { //do nothing } } protected void Application_Start() { RegisterRoutes(RouteTable.Routes); ViewEngines.Engines.Insert(0, new StaticViewEngine()); }

Nov 9, 2009

Context data in asp.net mvc

Along the asp.net mvc stack, there are lots context data being passing around. The first context data is passed into MvcRouteHandler by the System.Web.Routing.UrlRoutingModule.

public class RequestContext
{
    public RequestContext(HttpContextBase httpContext, RouteData routeData);

    public HttpContextBase HttpContext { get; }
    public RouteData RouteData { get; }
}

Then the RequestContext is passed into MvcHandler via constructor. When MvcHandler.ProcessRequest is called, it wrap the httpContext into HttpContextBase, which is System.Web.Abstractions class. The reason to use HttpContext to replace the dependency on HttpContext object, which is sealed, with dependency on HttpContextBase which is an abstraction.

//MvcHandler member
public MvcHandler(RequestContext requestContext) {
    if (requestContext == null) {
        throw new ArgumentNullException("requestContext");
    }
    RequestContext = requestContext;
}

protected virtual void ProcessRequest(HttpContext httpContext) {
    HttpContextBase iHttpContext = new HttpContextWrapper(httpContext);
    ProcessRequest(iHttpContext);
}

protected internal virtual void ProcessRequest(HttpContextBase httpContext) {
 //skip
 IController controller = factory.CreateController(RequestContext, controllerName);
 //skip
  controller.Execute(RequestContext);
}

When creating Controller, the RequestContext is passed into the ControllerFactory.CreateController method, after the controller is created the RequestContext is passed into controller.Execute method. By default, the controller is a System.Web.Mvc.Controller. A ControllerContext is created based on the RequestContext, the controller itself. A UrlHelper is also created with RequestContext. So nowt the Controller has a ControllerContext, and UrlHelper.

//Controller member
protected override void Initialize(RequestContext requestContext) {
    base.Initialize(requestContext);
    Url = new UrlHelper(requestContext);
}
//ControllerBase member
protected virtual void Initialize(RequestContext requestContext) {
    ControllerContext = new ControllerContext(requestContext, this);
}

Then ControllerContext is used to create ControllerDescriptor, and ActionDescriptor, and FilterInfo(Filters), AuthorizationContext, parameters for actio method. ActionExecutedContext. It is also used in InvokeActionResultWithFilters method. Eventually, ActionResult use ControllerContext to ExecuteResult. ContextController is used to find ViewEngineResult

public abstract class ActionResult {

    public abstract void ExecuteResult(ControllerContext context);

}

A ViewContext is created by using ControllerContext, and IView use viewContext to render view. ViewContext.ViewData is passed into ViewPage or ViewUserControl

acton parameters

The asp.net mvc framework matches action parameters in the following order:

  1. Form values
  2. Route arguments
  3. Querystring parameters

The parameters that is passed in the action method is handled by ControllerActionInvoker.The ControllerActionInvoker tries to using reflection and other techniques to extract information for client's request, as the source code shows below.

//ControllerActionInvoker members
public bool InvokeAction(ControllerContext controllerContext, string actionName)
{
//..code skip
IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor);
ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters);
InvokeActionResultWithFilters(controllerContext, filterInfo.ResultFilters, postActionContext.Result);
//..
}
protected virtual IDictionary<string, object> GetParameterValues(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
    Dictionary<string, object> parametersDict = new Dictionary(StringComparer.OrdinalIgnoreCase);
    ParameterDescriptor[] parameterDescriptors = actionDescriptor.GetParameters();

    foreach (ParameterDescriptor parameterDescriptor in parameterDescriptors)
    {
        parametersDict[parameterDescriptor.ParameterName] = GetParameterValue(controllerContext, parameterDescriptor);
    }
    return parametersDict;
}

protected virtual object GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor)
{
    // collect all of the necessary binding properties
    Type parameterType = parameterDescriptor.ParameterType;
    //you can register your binder, but default is DefaultModelBinder
    IModelBinder binder = GetModelBinder(parameterDescriptor);
    IDictionary<string, ValueProviderResult> valueProvider = controllerContext.Controller.ValueProvider;
    string parameterName = parameterDescriptor.BindingInfo.Prefix ?? parameterDescriptor.ParameterName;
    Predicate<string> propertyFilter = GetPropertyFilter(parameterDescriptor);

    // finally, call into the binder
    ModelBindingContext bindingContext = new ModelBindingContext()
    {
        FallbackToEmptyPrefix = (parameterDescriptor.BindingInfo.Prefix == null), // only fall back if prefix not specified
        ModelName = parameterName,
        ModelState = controllerContext.Controller.ViewData.ModelState,
        ModelType = parameterType,
        PropertyFilter = propertyFilter,
        ValueProvider = valueProvider
    };
    object result = binder.BindModel(controllerContext, bindingContext);
    return result;
}

You can also register your ModelBinder to help mvc to build your parameter in the application startup like the code below.

public class ConferenceModelBinder : DefaultModelBinder
{
 private readonly IConferenceRepository _repository;

 //We can require dependencies in binders just like normal classes
 public ConferenceModelBinder(IConferenceRepository repository)
 {
  _repository = repository;
 }

 public override object BindModel(ControllerContext controllerContext,
                                  ModelBindingContext bindingContext)
 {
  //first, we get the value from the request that matches the name
  ValueProviderResult providerResult =
   bindingContext.ValueProvider[bindingContext.ModelName];

  //next we use our repository to find the Conference by the key
  Conference conference = _repository.GetByKey(providerResult.AttemptedValue);
  return conference;
 }
}

ModelBinders.Binders.Add(typeof (Conference),
new ConferenceModelBinder(
new ConferenceRepositoryStub()));

Nov 8, 2009

The options to render a partial view

Normally you can do this,

<% Html.RenderPartial("LogOnUserControl"); %>

Or you can do this

ViewResult partialResult = View("next");
return View(partialResult);
<%ViewData.Model.ExecuteResult(ViewContext); %>

Nov 6, 2009

asp.net mvc request walkthrough

ASP.NET MVC request begin with a UrlRoutingModule. This component is not actually part of the System.Web.Mvc.dll, but of System.Web.Routing. You have to wire up in web.config like the following.

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

After that you can define your matching url pattern and the IRouteHandler which handle the matched url. You can add route like the following code.

Route route = new Route("{controller}/{action}/{id}", new MvcRouteHandler()) ;
route.Defaults = new RouteValueDictionary( new { controller = "Home", action = "Index", id = "" });
RouteTable.Routes.Add("Default", route);

Of course, you can add the use the extension method define in System.Web.Mvc.RouteCollectionExtensions. You can add a route with your own IRouteHandler. The purpose of IRouteHandler is very simple, to create a IHttpHandler.

public class MvcRouteHandler : IRouteHandler {
    protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext) {
        return new MvcHandler(requestContext);
    }

    #region IRouteHandler Members
    IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext) {
        return GetHttpHandler(requestContext);
    }
    #endregion
}

The RequestContext object is interesting object.It contains a HttpContextBase object and a RouteData object.This object will be used through out the mvc stack. You can also change the mvc behavior by subclassing the MvcHandler.

protected virtual void ProcessRequest(HttpContext httpContext) {
    HttpContextBase iHttpContext = new HttpContextWrapper(httpContext);
    ProcessRequest(iHttpContext);
}

protected internal virtual void ProcessRequest(HttpContextBase httpContext) {
    AddVersionHeader(httpContext);

    // Get the controller type
    string controllerName = RequestContext.RouteData.GetRequiredString("controller");

    // Instantiate the controller and call Execute
    IControllerFactory factory = ControllerBuilder.GetControllerFactory();
    IController controller = factory.CreateController(RequestContext, controllerName);
    if (controller == null) {
        throw new InvalidOperationException(
            String.Format(
                CultureInfo.CurrentUICulture,
                MvcResources.ControllerBuilder_FactoryReturnedNull,
                factory.GetType(),
                controllerName));
    }
    try {
        controller.Execute(RequestContext);
    }
    finally {
        factory.ReleaseController(controller);
    }
}

The IControllerFactory by default is DefaultControllerFactory. This can be changed in the application_startup method. The _factoryThunk is an Func delegate. ControllerFactory is used to build a controller. Sometimes, you need to use IoC to inject dependency to into the container, if so you need to create your ControllerFactory.

ControllerBuilder.Current.SetControllerFactory(new 
StructureMapControllerFactory());

public class ControllerBuilder {
 //.. other members
public void SetControllerFactory(IControllerFactory controllerFactory) {
    if (controllerFactory == null) {
        throw new ArgumentNullException("controllerFactory");
    }

    _factoryThunk = () => controllerFactory;
}

public void SetControllerFactory(Type controllerFactoryType) {
    if (controllerFactoryType == null) {
        throw new ArgumentNullException("controllerFactoryType");
    }
    if (!typeof(IControllerFactory).IsAssignableFrom(controllerFactoryType)) {
        throw new ArgumentException(
            String.Format(
                CultureInfo.CurrentUICulture,
                MvcResources.ControllerBuilder_MissingIControllerFactory,
                controllerFactoryType),
            "controllerFactoryType");
    }

    _factoryThunk = delegate() {
        try {
            return (IControllerFactory)Activator.CreateInstance(controllerFactoryType);
        }
        catch (Exception ex) {
            throw new InvalidOperationException(
                String.Format(
                    CultureInfo.CurrentUICulture,
                    MvcResources.ControllerBuilder_ErrorCreatingControllerFactory,
                    controllerFactoryType),
                ex);
        }
    };
}

}

The most commonly used controller is System.Web.Mvc.Controller, which inherit ControllerBase class. It has a property ControllerContext, which holds a RequestContext object, and a reference the Controller itself. MvcHandler will call the controller's Execute(RequestContext) method, which calls ExecuteCore method. As a developer, you need to implement your action method, the ExecuteCore method will try to call you action method.

protected override void ExecuteCore() {
    TempData.Load(ControllerContext, TempDataProvider);

    try {
        string actionName = RouteData.GetRequiredString("action");
        if (!ActionInvoker.InvokeAction(ControllerContext, actionName)) {
            HandleUnknownAction(actionName);
        }
    }
    finally {
        TempData.Save(ControllerContext, TempDataProvider);
    }
}

public IActionInvoker ActionInvoker {
    get {
        if (_actionInvoker == null) {
            _actionInvoker = new ControllerActionInvoker();
        }
        return _actionInvoker;
    }
    set {
        _actionInvoker = value;
    }
}

The ActionInvoker is also an extension point, you can set this property when the controller is created, you can do this in your ControllerFactory. By default, it is ControllerActionInvoker. Below is the code how ControllerActionInvoker select a action method, and invoking it. This is most interesting part.

// class ControllerActionInvoker
public virtual bool InvokeAction(ControllerContext controllerContext, string actionName) {
    if (controllerContext == null) {
        throw new ArgumentNullException("controllerContext");
    }
    if (String.IsNullOrEmpty(actionName)) {
        throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");
    }
    //this will return ReflectedControllerDescriptor, which is used to expose
   //some meta of the controller, which implement System.Reflection.ICustomAttributeProvider
    ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext);
    //Use controllerDescriptor build a ActionDescripter which is a ReflectedActionDescriptor, which also implements ICustomAttributeProvider
    ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName);
    if (actionDescriptor != null) {
        //FilterInfo has four list,ActionFilters(IActionFilter), AuthorizationFilters(IAuthorizationFilter), ExceptionFilters(IExceptionFilter), ResultFilters(IResultFilter), these list contains FilterAttribute, but also the controller itself, because Controller implements  IActionFilter, IAuthorizationFilter, IExceptionFilter, IResultFilter
        FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor);

        try {
            AuthorizationContext authContext = InvokeAuthorizationFilters(controllerContext, filterInfo.AuthorizationFilters, actionDescriptor);
            if (authContext.Result != null) {
                // the auth filter signaled that we should let it short-circuit the request
                InvokeActionResult(controllerContext, authContext.Result);
            }
            else {
                if (controllerContext.Controller.ValidateRequest) {
                    ValidateRequest(controllerContext.HttpContext.Request);
                }
                //parameters can build from controllerContext, and will be feed to actionMethod later.
                IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor);
                //your action method will be executed, and ActionResult will be 
                //returned here, which normally is ViewResult
                ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters);
                //the ActionExecutedContext.Result will be examined, if it is ActionResult, it will be invoked to render a view
                InvokeActionResultWithFilters(controllerContext, filterInfo.ResultFilters, postActionContext.Result);
            }
        }
        catch (ThreadAbortException) {
            // This type of exception occurs as a result of Response.Redirect(), but we special-case so that
            // the filters don't see this as an error.
            throw;
        }
        catch (Exception ex) {
            // something blew up, so execute the exception filters
            ExceptionContext exceptionContext = InvokeExceptionFilters(controllerContext, filterInfo.ExceptionFilters, ex);
            if (!exceptionContext.ExceptionHandled) {
                throw;
            }
            InvokeActionResult(controllerContext, exceptionContext.Result);
        }

        return true;
    }
    // notify controller that no method matched
    return false;
}

There are lots implementation details here, I am going to skip most of them here. And focus on how a view is rendered. When ActionResult is returned from the method call InvokeActionMethodWithFilters, it will be passed in the method InvokeActionResultWithFilters, that is the beginning of rendering a view. The following code will be eventually run.

//ControllerActionInvoker member
protected virtual void InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult)
{
    actionResult.ExecuteResult(controllerContext);
}

ViewResult inherit from ViewResultBase, which inherite ActionResult. The interesting method is ExecuteResult(ControllerContext), it basically to find a view and then render the view.

//ViewResultBase member
public override void ExecuteResult(ControllerContext context) {
    if (context == null) {
        throw new ArgumentNullException("context");
    }
    if (String.IsNullOrEmpty(ViewName)) {
        ViewName = context.RouteData.GetRequiredString("action");
    }

    ViewEngineResult result = null;

    //View is property Of ViewResultBase, it is Type IView
    if (View == null) {
        result = FindView(context);
        View = result.View;
    }
    //push the ViewData, TempData into viewCotext, which is passed into View.Render()
    ViewContext viewContext = new ViewContext(context, View, ViewData, TempData);
    View.Render(viewContext, context.HttpContext.Response.Output);

    if (result != null) {
        result.ViewEngine.ReleaseView(context, View);
    }
}

public interface IView {
    void Render(ViewContext viewContext, TextWriter writer);
}

//ViewResult member
protected override ViewEngineResult FindView(ControllerContext context) {
    //ViewEngineCollection
    ViewEngineResult result = ViewEngineCollection.FindView(context, ViewName, MasterName);
    if (result.View != null) {
        return result;
    }

    // we need to generate an exception containing all the locations we searched
    StringBuilder locationsText = new StringBuilder();
    foreach (string location in result.SearchedLocations) {
        locationsText.AppendLine();
        locationsText.Append(location);
    }
    throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture,
        MvcResources.Common_ViewNotFound, ViewName, locationsText));
}

//ViewResultBase member, this is an extension point
//we can add our ViewEngine here.
[SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly",
    Justification = "This entire type is meant to be mutable.")]
public ViewEngineCollection ViewEngineCollection {
    get {
        return _viewEngineCollection ?? ViewEngines.Engines;
    }
    set {
        _viewEngineCollection = value;
    }
}

public static class ViewEngines {

    private readonly static ViewEngineCollection _engines = new ViewEngineCollection {
        new WebFormViewEngine() 
    };

    public static ViewEngineCollection Engines {
        get {
            return _engines;
        }
    }
}

We can see here, to find a view, we need a ViewEngineCollection. By default this ViewEngineCollection has one IViewEngin(WebFormViewEngine). The purpose a IViewEngine is to find partial view and view, like the following. But WebFormViewEngine inherit VirtualPathProviderViewEngine, which implmenet IViewEngine. To find View, first we need to locate the Physical path of the view file(aspx or ascx), to decide whether a file exist, there is to ask a buildManager to build an instance of it. Physical path searhing follow a couple of pattern, if the view file can not be located. Then an exeption will be throw. If found a the path will be cached for next probing.

public interface IViewEngine {
    ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache);
    ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache);
    void ReleaseView(ControllerContext controllerContext, IView view);
}

//VirtualPathProviderViewEngine member
public virtual ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) {
    if (controllerContext == null) {
        throw new ArgumentNullException("controllerContext");
    }
    if (String.IsNullOrEmpty(viewName)) {
        throw new ArgumentException(MvcResources.Common_NullOrEmpty, "viewName");
    }

    string[] viewLocationsSearched;
    string[] masterLocationsSearched;

    string controllerName = controllerContext.RouteData.GetRequiredString("controller");
    string viewPath = GetPath(controllerContext, ViewLocationFormats, "ViewLocationFormats", viewName, controllerName, _cacheKeyPrefix_View, useCache, out viewLocationsSearched);
    string masterPath = GetPath(controllerContext, MasterLocationFormats, "MasterLocationFormats", masterName, controllerName, _cacheKeyPrefix_Master, useCache, out masterLocationsSearched);

    if (String.IsNullOrEmpty(viewPath) || (String.IsNullOrEmpty(masterPath) && !String.IsNullOrEmpty(masterName))) {
        return new ViewEngineResult(viewLocationsSearched.Union(masterLocationsSearched));
    }

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


//WebFormViewEngine constructor
public WebFormViewEngine() {
    MasterLocationFormats = new[] {
        "~/Views/{1}/{0}.master",
        "~/Views/Shared/{0}.master"
    };

    ViewLocationFormats = new[] {
        "~/Views/{1}/{0}.aspx",
        "~/Views/{1}/{0}.ascx",
        "~/Views/Shared/{0}.aspx",
        "~/Views/Shared/{0}.ascx"
    };

    PartialViewLocationFormats = ViewLocationFormats;
}

The WebFormViewEngine defines how to create view, it implement two abstract method of VirtualPathProviderViewEngine.

protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath) {
    return new WebFormView(partialPath, null);
}

protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath) {
    return new WebFormView(viewPath, masterPath);
}

The WebFormView use tranditional aspx ViewPage or ViewUserControl ascx to render view.

//WebFormView  members
public virtual void Render(ViewContext viewContext, TextWriter writer) {
    if (viewContext == null) {
        throw new ArgumentNullException("viewContext");
    }
    
    //viewInstance is an instance of ViewPage(aspx or ascx)
    object viewInstance = BuildManager.CreateInstanceFromVirtualPath(ViewPath, typeof(object));
    if (viewInstance == null) {
        throw new InvalidOperationException(
            String.Format(
                CultureInfo.CurrentUICulture,
                MvcResources.WebFormViewEngine_ViewCouldNotBeCreated,
                ViewPath));
    }


    ViewPage viewPage = viewInstance as ViewPage;
    if (viewPage != null) {
        RenderViewPage(viewContext, viewPage);
        return;
    }
    //we can see that viewInstance does not necessary to be a ViewPage, it can be ViewUserControl as well
    ViewUserControl viewUserControl = viewInstance as ViewUserControl;
    if (viewUserControl != null) {
        RenderViewUserControl(viewContext, viewUserControl);
        return;
    }

    throw new InvalidOperationException(
        String.Format(
            CultureInfo.CurrentUICulture,
            MvcResources.WebFormViewEngine_WrongViewBase,
            ViewPath));
}

private void RenderViewPage(ViewContext context, ViewPage page) {
    if (!String.IsNullOrEmpty(MasterPath)) {
        page.MasterLocation = MasterPath;
    }
    //passed in ViewData to aspx or ascx
    page.ViewData = context.ViewData;
    page.RenderView(context);
}

private void RenderViewUserControl(ViewContext context, ViewUserControl control) {
    if (!String.IsNullOrEmpty(MasterPath)) {
        throw new InvalidOperationException(MvcResources.WebFormViewEngine_UserControlCannotHaveMaster);
    }

    //passed in ViewData to aspx or ascx
    control.ViewData = context.ViewData;
    control.RenderView(context);
}

To render a ViewPage, firstly, it initialized three very handy object will be used extensively in the aspx or ascx, they are Ajax(AjaxHelper), Html(HtmlHelper),Url(UrlHelper). Then it delegate the control to ProcessRequest method of Page class.

//public class ViewPage : Page, IViewDataContainer
public virtual void RenderView(ViewContext viewContext) {
    ViewContext = viewContext;
    InitHelpers();
    // Tracing requires Page IDs to be unique.
    ID = Guid.NewGuid().ToString();
    ProcessRequest(HttpContext.Current);
}


public virtual void InitHelpers() {
    Ajax = new AjaxHelper(ViewContext, this);
    Html = new HtmlHelper(ViewContext, this);
    Url = new UrlHelper(ViewContext.RequestContext);
}

HtmlHelper is responsible to render partial view.

internal virtual void RenderPartialInternal(string partialViewName, ViewDataDictionary viewData, object model, ViewEngineCollection viewEngineCollection) {
    if (String.IsNullOrEmpty(partialViewName)) {
        throw new ArgumentException(MvcResources.Common_NullOrEmpty, "partialViewName");
    }
    
    //a new copy a ViewData is created 
    ViewDataDictionary newViewData = null;

    if (model == null) {
        if (viewData == null) {
            newViewData = new ViewDataDictionary(ViewData);
        }
        else {
            newViewData = new ViewDataDictionary(viewData);
        }
    }
    else {
        if (viewData == null) {
            newViewData = new ViewDataDictionary(model);
        }
        else {
            newViewData = new ViewDataDictionary(viewData) { Model = model };
        }
    }
    //a newViewContext is created, not shared with passed in data
    ViewContext newViewContext = new ViewContext(ViewContext, ViewContext.View, newViewData, ViewContext.TempData);
    //use the passed in viewEngineCollectio to newViewContext to find a partial view
    IView view = FindPartialView(newViewContext, partialViewName, viewEngineCollection);
    view.Render(newViewContext, ViewContext.HttpContext.Response.Output);
}

//ViewDataDictionary member
[SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors",
    Justification = "See note on SetModel() method.")]
public ViewDataDictionary(ViewDataDictionary dictionary) {
    if (dictionary == null) {
        throw new ArgumentNullException("dictionary");
    }

    foreach (var entry in dictionary) {
        _innerDictionary.Add(entry.Key, entry.Value);
    }
    foreach (var entry in dictionary.ModelState) {
        ModelState.Add(entry.Key, entry.Value);
    }
    Model = dictionary.Model;
}

Jun 23, 2009

Test asp.net mvc route

//arrange
RouteCollection routes = new RouteCollection();
MvcApplication.RegisterRoutes(routes);
var httpContextMock = new Mock<HttpContextBase>();
httpContextMock.Expect(c => c.Request.AppRelativeCurrentExecutionFilePath).Return("~/product/list");

//act 
RouteData routeData = routes.GetRouteData(httpContextMock.Object);

//assert
Assert.IsNotNull(routeData, "Should have found the route");
Assert.AreEqual("product", routeData.Value["Controller"]);
Assert.AreEqual("list", routeData.Value["action"]);
Assert.AreEqual("", routeData.Values["id"]);

Jun 11, 2009

mvc routing

Routing is actually not part of the asp.net mvc component. But mvc depends on this asp.net components. To add a route to the route table, you can use the following code.

Route r = new Route("url", new SomeRouteHandler());
r.Constraints.Add("key", "value");
r.Defaults.Add("key", "value");
r.DataTokens.Add("key", "value");
RouteTable.Routes.Add("route_name", r);

// or you can write the code in .net 3.0 syntax, they do the same job
//but it looks cleaner.
 RouteTable.Routes.Add(new Route("url", new SomeRouteHandler())
 {
     Constraints = new RouteValueDictionary(new { key = value }),
     Defaults = new RouteValueDictionary(new { key = value }),
     DataTokens = new RouteValueDictionary(new { key = value }),
 });

asp.net mvc, add some extension method to the RouteCollection like the following.

public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces) {
    if (routes == null) {
        throw new ArgumentNullException("routes");
    }
    if (url == null) {
        throw new ArgumentNullException("url");
    }

    Route route = new Route(url, new MvcRouteHandler()) {
        Defaults = new RouteValueDictionary(defaults),
        Constraints = new RouteValueDictionary(constraints)
    };

    if ((namespaces != null) && (namespaces.Length > 0)) {
        route.DataTokens = new RouteValueDictionary();
        route.DataTokens["Namespaces"] = namespaces;
    }

    routes.Add(name, route);

    return route;
}

This method create a route use MvcRouteHandler as IRouteHandler. So if use this method, the MVC component comes into play. So you can these extension method to simplify the mvc routing

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

The default values only work when every URL parameters after the one with default also has a default value assigned. So the following code doesn't work.

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

The following code demo a catch-all parameter

routes.MapRoute("r2", "catch/{*all}", new { controller = "test", action = "catch", all="empty" });

public string Catch(string all)
{
    return string.Format("

all:{0}

", all); }

Jan 9, 2009

The order of route is important in asp.net mvc

I have try to add route to reroute the default.aspx to home handler.

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

routes.MapRoute("default.aspx", "default.aspx", new { controller = "Home", action = "Index", id = "" });

It throw an exception "The controller for path '/default.aspx' could not be found or it does not implement IController.". Why, it turns out the order of adding route is important. I should put my routing of "default.aspx" on top of "{controller}/{action}/{id}".

Oct 31, 2008

View is the king.

ASP.NET MVC is very hot today, it catches more and more attention from asp.net developer. There are many problems it tries to solve, like testability, separation of concern ect. But to me the most important think it solves is the View. There are not many web 2.0 web site use asp.net to develop, but PHP is very widely used for these web site? Why? There are many reasons, but the most important reason is the View is back.

In session PC21 ASP.NET MVC: A New Framework for Building Web Applications of latest PDC 2008, an audience asked Haack, "do we need to known html?". What kind of question is that? When you love somebody, you can give of a long list of the merits of her or him. I love asp.net mvc, because it is testable, separation of concern, inversion of control. But to me, it think the force of driving mvc is the view. ASP.NET MVC put the view to the control of developer and designer. Naive developers love the server control, in fact they are spoiled to a degree to ask the question of "Do we need to know html". There is nothing wrong with from this practice, in fact asp.net page model and server control framework does encapsulate lots of html, javascript. But developers are quite limited to what the server control has to offer. Although server control is very extensible, developers need to have learn very deep to fully leverage the power of it, until we understand internal of viewstate, render control, javascript, css, we can develop our own server control. I think that is why there are many web 2.0 site is not implemented in asp.net. Now the view is back, we can fully control how the view is rendered.