Click here to Skip to main content
15,885,216 members
Articles / Web Development / ASP.NET

PixelDragonsMVC.NET - An open source MVC framework

Rate me:
Please Sign up or sign in to vote.
4.58/5 (8 votes)
29 Jun 200710 min read 84.9K   73  
An MVC framework with built-in NHibernate support that makes creating ASP.NET 2 applications easier by minimizing code and configuration
/***********************************************************
 * 
 * Copyright 2007 Pixel Dragons Ltd. All rights reserved.
 * 
 * http://www.codeplex.com/PixelDragonsMVC for licence, more
 * information and latest source code. 
 * 
 * Check out our other stuff at http://www.pixeldragons.com
 * 
 ***********************************************************/

using System;
using System.Web;
using System.Collections.Generic;
using System.Web.SessionState;
using System.Configuration;
using System.Collections.Specialized;
using System.IO;
using System.Xml;
using System.Web.Compilation;
using System.Reflection;
using System.Web.UI;

using PixelDragons.MVC.Interfaces;
using PixelDragons.MVC.Exceptions;
using PixelDragons.MVC.Controllers;
using PixelDragons.MVC.Configuration;
using PixelDragons.MVC.Persistence;

using NHibernate;
using log4net;

namespace PixelDragons.MVC
{
    public class MVCHandler : IHttpHandler, IRequiresSessionState
    {
        #region Fields
        private static readonly ILog _logger = LogManager.GetLogger(typeof(MVCHandler));
        private MVCConfigurationHandler _settings;
        private Dictionary<string, Type> _controllerTypeCache = new Dictionary<string, Type>();
        #endregion
        
        #region IHttpHander
        public bool IsReusable
        {
            get { return true; }
        }

        public void ProcessRequest(HttpContext context)
        {
            DateTime start = DateTime.Now;

            _logger.Info("**************** NEW REQUEST ****************");

            _logger.InfoFormat("Processing request '{0}'", context.Request.Path);

            if (_settings == null)
            { 
                //This is the first time this instance of the handler has run, so load the settings
                LoadSettings(context);
            }

            //Create an nHibernate session factory builder for this request    
            SessionManager sessionManager = new SessionManager(_settings);
            _logger.Debug("nHibernate session manager created");

            TransactionManager transactionManager = new TransactionManager(sessionManager);
            _logger.Debug("nHibernate transaction manager created");

            try
            {
                //Get the controller and action name (the command) from the request
                Command command = GetCommandFromContext(context);

                //Setup a transaction if required (based on the action name and settings)
                ITransaction transaction = transactionManager.AutoStartTransaction(_settings, command);

                //Get the controller class from the configuration and set it up
                IController controller = GetController(command);
                controller.Context = context;
                controller.Request = context.Request;
                controller.Server = context.Server;
                controller.Response = context.Response;
                controller.Session = context.Session;
                controller.Logger = LogManager.GetLogger(controller.GetType());
                controller.SessionManager = sessionManager;
                controller.TransactionManager = transactionManager;
                controller.PersistenceManager = new PersistenceManager(sessionManager);
                controller.AjaxViewPart = context.Request["ajaxViewPart"];
                controller.Command = command;
                
                try
                {
                    //Get the correct view to show
                    RunActionAndGetViewToShow(controller, command, context);
                }
                catch (Exception ex)
                {
                    //An unexpected error, so rollback the transaction (if PixelDragonsMVC.NET created one)
                    if (transaction != null)
                    {
                        transactionManager.Rollback(transaction);
                        _logger.Debug("Rolled back transaction");
                    }

                    //Log the error
                    string error = GetFullExceptionText(ex) + "\r\n\r\n";
                    error += ex.GetBaseException().StackTrace;
                    _logger.Error(error);

                    //throw (ex);
                }
                finally
                {
                    //Either render or redirect to the selected view
                    if (command.View.ViewType == ViewType.Render)
                    {
                        _logger.DebugFormat("Rendering view: {0}", command.View.ViewPath);
                        context.Server.Execute(command.View.ViewPath);
                    }
                    else
                    {
                        _logger.DebugFormat("Redirecting to url: {0}", command.View.ViewUrl);
                        context.Response.Redirect(command.View.ViewUrl);
                    }
                }
            }
            finally
            {
                //The request is now completed so close the nhibernate session
                _logger.Debug("Closing nHibernate active session");
                sessionManager.CloseActiveSession(transactionManager);

                TimeSpan duration = new TimeSpan(DateTime.Now.Ticks - start.Ticks);

                _logger.InfoFormat("*** Request '{0}' complete in {1}ms ***", context.Request.Path, duration.TotalMilliseconds);
            }
        }
        #endregion

        #region Methods
        private void RunActionAndGetViewToShow(IController controller, Command command, HttpContext context)
        {
            //Call the actions as required and get the view to show
            command.View = null;
            try
            {
                //Allow the controller to execution some code before the action is called
                _logger.DebugFormat("Calling BeforeAction() in controller: {0}", controller.GetType().ToString());
                controller.BeforeAction();

                //Call the action for this command
                _logger.DebugFormat("Calling action in controller: {0}", controller.GetType().ToString());
                CallAction(controller, command, context);

                //Allow the controller to execution some code after the action has been called
                _logger.DebugFormat("Calling AfterAction() in controller: {0}", controller.GetType().ToString());
                controller.AfterAction();
            }
            catch (ActionException ex)
            {
                //An exception was thrown to override the view to display
                _logger.DebugFormat("ActionException thrown, getting the view '{0}'", ex.ViewName);

                command.View = GetView(command, ex.ViewName);
            }
            finally
            {

                //Store the model (as set by the action) for the view to access when rendering
                context.Items.Add("model", controller.Model);

                if (command.View == null)
                {
                    //Get the view
                    _logger.Debug("Getting the correct view...");
                    string viewName = controller.ViewName;
                    if (String.IsNullOrEmpty(viewName))
                    {
                        //The controller hasn't specified a view so use the default
                        _logger.Debug("No view specified by the controller, using the default...");
                        command.View = GetDefaultView(command);
                    }
                    else
                    {
                        //The controller specified a view, so look it up in the config
                        _logger.DebugFormat("Getting the view '{0}' which was specified by the controller...", viewName);
                        command.View = GetView(command, viewName);
                    }
                }

            }

        }

        private string GetFullExceptionText(Exception ex)
        {
            string message = "";

            message = ex.Message;

            if (ex.InnerException != null)
            {
                message += "\r\n" + GetFullExceptionText(ex.InnerException);
            }

            return message;
        }

        private void LoadSettings(HttpContext context)
        {
            _logger.Debug("Loading MVC settings...");

            _settings = (MVCConfigurationHandler)ConfigurationManager.GetSection("mvc");

            if (_settings != null)
            {
                _settings.MappingFilePath = Path.Combine(context.Request.PhysicalApplicationPath, _settings.MappingFile);

                //Load the config xml
                XmlDocument xml = new XmlDocument();
                xml.Load(_settings.MappingFilePath);

                _settings.ConfigXml = xml;

                _logger.Debug("Loaded settings successfully");
            }
            else
            { 
                throw(new Exception("Unable to find MVC element in web.config"));
            }
        }

        private Command GetCommandFromContext(HttpContext context)
        {
            //From the context, get the controller and action. In PixelDragons.MVC.NET, 
            //this is in the format: http://......./controllerName-actionName.ext 
            //(ext is the file extention mapped in the web.config, ashx by default). 
            //Parameters for the action can be passed on the querystring or posted.
            
            //TODO: Convert this to use a regular expression defined in the config file
            
            string path = context.Request.Path;
            int start = path.LastIndexOf('/') + 1;
            int end = path.LastIndexOf('.');

            string commandText = path.Substring(start, end - start);

            string[] controllerActionPair = commandText.Split(new char[] { '-' });
            string controllerName = controllerActionPair[0];
            string actionName = null;
            if (controllerActionPair.Length == 2)
            {
                actionName = controllerActionPair[1];
            }

            _logger.DebugFormat("Command for request '{0}' parsed. Command Text: '{1}', Controller Name: '{2}', Action Name: '{3}'", path, commandText, controllerName, actionName);

            return new Command(commandText, controllerName, actionName);
        }

        private IController GetController(Command command)
        {
            //First try to get the controller type from the cache
            Type controllerType = null;
            if (_controllerTypeCache.ContainsKey(command.CommandText))
            {
                _logger.DebugFormat("Getting controller from cache for command '{0}'", command.CommandText);

                controllerType = _controllerTypeCache[command.CommandText];
            }
            else
            {
                _logger.DebugFormat("Trying to get controller from pattern '{0}'", _settings.ControllerPattern);

                //Otherwise, try to get the controller from the controller pattern
                string controllerClass = _settings.ControllerPattern.Replace("[ControllerName]", command.ControllerName);
                controllerType = BuildManager.GetType(controllerClass, false, false);
                if (controllerType == null)
                {
                    _logger.DebugFormat("Controller class '{0}' does not exist, trying to lookup in config", controllerClass);

                    //Couldn't get the controller type from the controller pattern, so look up in the config
                    XmlElement controllerNode = (XmlElement)_settings.ConfigXml.DocumentElement.SelectSingleNode(String.Format("controllers/controller[@name='{0}']", command.ControllerName.ToLower()));
                    if (controllerNode != null && controllerNode.HasAttribute("class"))
                    {
                        controllerClass = controllerNode.GetAttribute("class");
                        controllerType = BuildManager.GetType(controllerClass, true, false);
                    }
                }

                if (controllerType == null)
                {
                    _logger.DebugFormat("No controller available for '{0}', so using the default controller", command.CommandText);

                    //No controller found so use the default controller. This means that a command 
                    //doesn't need it's own controller if there it just needs to show the default
                    //view.
                    controllerType = BuildManager.GetType(_settings.DefaultController, true, false);
                }

                //Cache this for next time
                _controllerTypeCache.Add(command.CommandText, controllerType);
            }

            _logger.DebugFormat("Creating controller: {0}", controllerType.ToString());

            return (IController)Activator.CreateInstance(controllerType);
        }

        private View GetDefaultView(Command command)
        {
            string viewPath;
            if (command.ActionName != null)
            {
                _logger.DebugFormat("Trying to get view path from pattern '{0}'", _settings.ViewWithActionPattern);

                viewPath = _settings.ViewWithActionPattern.Replace("[ControllerName]", command.ControllerName);
                viewPath = viewPath.Replace("[ActionName]", command.ActionName);
            }
            else
            {
                _logger.DebugFormat("Trying to get view path from pattern '{0}'", _settings.ViewWithNoActionPattern);

                viewPath = _settings.ViewWithNoActionPattern.Replace("[ControllerName]", command.ControllerName);   
            }

            _logger.DebugFormat("Using view path '{0}' to render", viewPath);
            return new View(viewPath, "", ViewType.Render);
        }

        private View GetView(Command command, string viewName)
        {
            _logger.DebugFormat("Getting view from the config xml for command '{0}' and view name '{1}'...", command.CommandText, viewName);

            //For this command, get the view that should be displayed from the config
            View view = null;
            string xpath = String.Format("controllers/controller[@name='{0}']/action[@name='{1}']/view[@name='{2}']", command.OriginalControllerName, command.OriginalActionName, viewName);
            XmlElement viewNode = (XmlElement)_settings.ConfigXml.DocumentElement.SelectSingleNode(xpath);
            if (viewNode != null)
            {
                _logger.Debug("Found an explicit view for the controller/action/view, looking up path...");
                view = GetViewFromConfigNode(viewNode);
            }
            else
            { 
                //There is no explicit view for this action, so check the shared area
                _logger.Debug("No explicit view for the controller/action/view, looking in shared area...");
                viewNode = (XmlElement)_settings.ConfigXml.DocumentElement.SelectSingleNode(String.Format("shared/view[@name='{0}']", viewName));
                if (viewNode != null)
                {
                    _logger.Debug("Found an explicit view in the shared area, looking up path...");
                    view = GetViewFromConfigNode(viewNode);
                }
            }

            if (view == null)
            {
                //Unable to find a view in the config so show the default
                _logger.Debug("Unable to find a view in the config so show the default");
                view = GetDefaultView(command);
            }

            return view;
        }

        private View GetViewFromConfigNode(XmlElement viewNode)
        {
            View view = new View();
            view.ViewType = (ViewType)Enum.Parse(typeof(ViewType), viewNode.GetAttribute("type"), true);
            view.ViewUrl = viewNode.GetAttribute("url");

            string viewRef = viewNode.GetAttribute("ref");
            XmlElement viewPathNode = (XmlElement)_settings.ConfigXml.DocumentElement.SelectSingleNode(String.Format("views/view[@id='{0}']", viewRef));
            if (viewPathNode != null)
            {
                view.ViewPath = viewPathNode.GetAttribute("path");
            }

            _logger.DebugFormat("Found view in config ViewType: '{0}', ViewPath: '{1}', ViewUrl: '{2}'", view.ViewType.ToString(), view.ViewPath, view.ViewUrl);
            return view;
        }

        private void CallAction(IController controller, Command command, HttpContext context)
        {
            if (command.ActionName == null)
            {
                //There was no action name so call the default action
                _logger.DebugFormat("No action name, so calling DefaultAction() in controller: {0}", controller.GetType().ToString());
                controller.DefaultAction();
            }
            else
            {
                //Get the action method using reflection
                Type controllerType = controller.GetType();
                MethodInfo method = controllerType.GetMethod(command.ActionName);

                if (method != null)
                {
                    _logger.DebugFormat("Found action method '{0}' in controller: {1}, converting parameters...", command.ActionName, controller.GetType().ToString());

                    //Make up the list of parameters
                    ParameterInfo[] methodParams = method.GetParameters();
                    List<object> paramList = new List<object>();
                    foreach (ParameterInfo param in methodParams)
                    {
                        //Convert request param to correct type
                        object convertedValue = null;

                        if (param.ParameterType == typeof(HttpPostedFile))
                        {
                            //Get the posted file from the files collection
                            convertedValue = context.Request.Files[param.Name];
                        }
                        else if (param.ParameterType == typeof(Guid))
                        {
                            //Get the posted file from the files collection
                            string valueAsString = context.Request[param.Name];
                            if (valueAsString == null)
                            {
                                convertedValue = Guid.Empty;
                            }
                            else 
                            {
                                convertedValue = new Guid(valueAsString);
                            }
                        }
                        else
                        {
                            //Convert value to correct type
                            string valueAsString = context.Request[param.Name];
                            if (param.ParameterType.IsArray)
                            {
                                //This is an array
                                if (valueAsString.Length > 0)
                                {
                                    string[] array = valueAsString.Split(',');
                                    convertedValue = ArrayConverter.ConvertStringArray(array, param.ParameterType);
                                }
                            }
                            else
                            {
                                //Normal type (not an array)
                                try
                                {
                                    convertedValue = Convert.ChangeType(valueAsString, param.ParameterType);
                                }
                                catch(Exception)
                                {
                                    convertedValue = null;
                                }
                            }
                        }

                        if (convertedValue == null)
                        { 
                            //The converted value is null, create a new instance of this type
                            //to ensure we are using the defaults for value types.
                            if (param.ParameterType.IsValueType)
                            {
                                convertedValue = Activator.CreateInstance(param.ParameterType);
                            }
                        }

                        //Add converted value to param list
                        paramList.Add(convertedValue);
                    }

                    //Call the action with parameters
                    _logger.DebugFormat("Calling action method '{0}' in controller: {1}", command.ActionName, controller.GetType().ToString());
                    method.Invoke(controller, paramList.ToArray());
                }
                else
                {
                    //There is no action by that name, so call the default
                    _logger.DebugFormat("No action method by the name '{0}', so calling DefaultAction() in controller: {1}", command.ActionName, controller.GetType().ToString());
                    controller.DefaultAction();
                }
            }
        }
        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions