Nov 10, 2009

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()); }