diff --git a/src/Umbraco.Core/CoreBootManager.cs b/src/Umbraco.Core/CoreBootManager.cs index 738def070a..118494dac9 100644 --- a/src/Umbraco.Core/CoreBootManager.cs +++ b/src/Umbraco.Core/CoreBootManager.cs @@ -17,9 +17,15 @@ namespace Umbraco.Core { private DisposableTimer _timer; + private bool _isInitialized = false; + private bool _isStarted = false; + private bool _isComplete = false; public virtual IBootManager Initialize() { + if (_isInitialized) + throw new InvalidOperationException("The boot manager has already been initialized"); + LogHelper.Info("Umbraco application starting"); _timer = DisposableTimer.Start(x => LogHelper.Info("Umbraco application startup complete" + " (took " + x + "ms)")); @@ -30,6 +36,9 @@ namespace Umbraco.Core }; InitializeResolvers(); + + _isInitialized = true; + return this; } @@ -40,10 +49,16 @@ namespace Umbraco.Core /// public virtual IBootManager Startup(Action afterStartup) { + if (_isStarted) + throw new InvalidOperationException("The boot manager has already been initialized"); + if (afterStartup != null) { afterStartup(ApplicationContext.Current); - } + } + + _isStarted = true; + return this; } @@ -54,6 +69,9 @@ namespace Umbraco.Core /// public virtual IBootManager Complete(Action afterComplete) { + if (_isComplete) + throw new InvalidOperationException("The boot manager has already been completed"); + //freeze resolution to not allow Resolvers to be modified Resolution.Freeze(); @@ -64,7 +82,9 @@ namespace Umbraco.Core { afterComplete(ApplicationContext.Current); } - + + _isComplete = true; + return this; } diff --git a/src/Umbraco.Tests/UmbracoModuleTests.cs b/src/Umbraco.Tests/UmbracoModuleTests.cs index c4a9fa3e8f..94b0f36279 100644 --- a/src/Umbraco.Tests/UmbracoModuleTests.cs +++ b/src/Umbraco.Tests/UmbracoModuleTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Configuration; using System.IO; using System.Linq; +using System.Threading; using System.Xml; using NUnit.Framework; using SqlCE4Umbraco; @@ -92,6 +93,42 @@ namespace Umbraco.Tests } } + /// + /// Initlializes the UmbracoContext with specific XML + /// + /// + /// + private void SetupUmbracoContextForTest(UmbracoContext umbracoContext, Template template) + { + umbracoContext.GetXmlDelegate = () => + { + var xDoc = new XmlDocument(); + + //create a custom xml structure to return + + xDoc.LoadXml(@" + + +]> + + + + + + + + + + + + +"); + //return the custom x doc + return xDoc; + }; + } + [TestCase("/umbraco_client/Tree/treeIcons.css", false)] [TestCase("/umbraco_client/Tree/Themes/umbraco/style.css?cdv=37", false)] [TestCase("/umbraco_client/scrollingmenu/style.css?cdv=37", false)] @@ -130,14 +167,19 @@ namespace Umbraco.Tests Assert.AreEqual(assert, result); } - [TestCase("/", true)] - [TestCase("/home.aspx", true)] - [TestCase("/home.aspx?altTemplate=blah", true)] - public void Process_Front_End_Document_Request(string url, bool assert) + //NOTE: This test shows how we can test most of the HttpModule, it however is testing too much, + // we need to write unit tests for each of the components: NiceUrlProvider, all of the Lookup classes, etc... + // to ensure that each one is individually tested. + + [TestCase("/", 1046)] + [TestCase("/home.aspx", 1046)] + [TestCase("/home/sub1.aspx", 1173)] + [TestCase("/home.aspx?altTemplate=blah", 1046)] + public void Process_Front_End_Document_Request_Match_Node(string url, int nodeId) { var httpContextFactory = new FakeHttpContextFactory(url); var httpContext = httpContextFactory.HttpContext; - var umbracoContext = new UmbracoContext(httpContext, ApplicationContext.Current, new DefaultRoutesCache(false)); + var umbracoContext = new UmbracoContext(httpContext, ApplicationContext.Current, new NullRoutesCache()); var contentStore = new ContentStore(umbracoContext); var niceUrls = new NiceUrlProvider(contentStore, umbracoContext); umbracoContext.RoutingContext = new RoutingContext( @@ -159,43 +201,46 @@ namespace Umbraco.Tests _module.AssignDocumentRequest(httpContext, umbracoContext, httpContext.Request.Url); - Assert.AreEqual(assert, umbracoContext.DocumentRequest != null); - if (assert) - { - Assert.AreEqual(assert, umbracoContext.DocumentRequest.Node != null); - } + Assert.IsNotNull(umbracoContext.DocumentRequest); + Assert.IsNotNull(umbracoContext.DocumentRequest.Node); + Assert.IsFalse(umbracoContext.DocumentRequest.IsRedirect); + Assert.IsFalse(umbracoContext.DocumentRequest.Is404); + Assert.AreEqual(umbracoContext.DocumentRequest.Culture, Thread.CurrentThread.CurrentCulture); + Assert.AreEqual(umbracoContext.DocumentRequest.Culture, Thread.CurrentThread.CurrentUICulture); + Assert.AreEqual(nodeId, umbracoContext.DocumentRequest.NodeId); + } - - private void SetupUmbracoContextForTest(UmbracoContext umbracoContext, Template template) + /// + /// Used for testing, does not cache anything + /// + private class NullRoutesCache : IRoutesCache { - umbracoContext.GetXmlDelegate = () => + public void Store(int nodeId, string route) { - var xDoc = new XmlDocument(); + + } - //create a custom xml structure to return + public string GetRoute(int nodeId) + { + return null; //default; + } - xDoc.LoadXml(@" - + public int GetNodeId(string route) + { + return 0; //default; + } -]> - - - - - - - - - - - - -"); - //return the custom x doc - return xDoc; - }; + public void ClearNode(int nodeId) + { + + } + + public void Clear() + { + + } } + } } \ No newline at end of file diff --git a/src/Umbraco.Tests/unit-test-log4net.config b/src/Umbraco.Tests/unit-test-log4net.config index 22c3aa9278..d285f72761 100644 --- a/src/Umbraco.Tests/unit-test-log4net.config +++ b/src/Umbraco.Tests/unit-test-log4net.config @@ -5,19 +5,6 @@ - - - - - - - - - - - - - diff --git a/src/Umbraco.Web/ContentStore.cs b/src/Umbraco.Web/ContentStore.cs index 45017bc842..e0631bb116 100644 --- a/src/Umbraco.Web/ContentStore.cs +++ b/src/Umbraco.Web/ContentStore.cs @@ -104,6 +104,8 @@ namespace Umbraco.Web return CreateXpathQuery(startNodeId, path, GlobalSettings.HideTopLevelNodeFromPath); } + + protected string CreateXpathQuery(int startNodeId, string path, bool hideTopLevelNodeFromPath) { string xpath; @@ -131,7 +133,7 @@ namespace Umbraco.Web int partsIndex = 0; xpathBuilder.Append("/root"); - + if (startNodeId == 0) { if (hideTopLevelNodeFromPath) diff --git a/src/Umbraco.Web/Mvc/Constants.cs b/src/Umbraco.Web/Mvc/Constants.cs new file mode 100644 index 0000000000..1ec35e4928 --- /dev/null +++ b/src/Umbraco.Web/Mvc/Constants.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Web.Mvc +{ + internal static class Constants + { + public const string ViewLocation = "~/Views"; + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/ControllerExtensions.cs b/src/Umbraco.Web/Mvc/ControllerExtensions.cs index 10c38985ab..22211e1f5f 100644 --- a/src/Umbraco.Web/Mvc/ControllerExtensions.cs +++ b/src/Umbraco.Web/Mvc/ControllerExtensions.cs @@ -1,8 +1,9 @@ using System; +using System.Threading; namespace Umbraco.Web.Mvc { - public static class ControllerExtensions + public static class ControllerExtensions { /// /// Return the controller name from the controller type diff --git a/src/Umbraco.Web/Mvc/FilteredControllerFactoriesResolver.cs b/src/Umbraco.Web/Mvc/FilteredControllerFactoriesResolver.cs new file mode 100644 index 0000000000..5f3465b4db --- /dev/null +++ b/src/Umbraco.Web/Mvc/FilteredControllerFactoriesResolver.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Resolving; + +namespace Umbraco.Web.Mvc +{ + /// + /// A resolver for storing IFilteredControllerFactories + /// + internal sealed class FilteredControllerFactoriesResolver : ManyObjectsResolverBase + { + /// + /// Constructor + /// + /// + internal FilteredControllerFactoriesResolver(IEnumerable factories) + : base(factories) + { + + } + + public IEnumerable Factories + { + get { return Values; } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/IFilteredControllerFactory.cs b/src/Umbraco.Web/Mvc/IFilteredControllerFactory.cs new file mode 100644 index 0000000000..9fbb3ac34c --- /dev/null +++ b/src/Umbraco.Web/Mvc/IFilteredControllerFactory.cs @@ -0,0 +1,17 @@ +using System.Linq; +using System.Web.Mvc; +using System.Web.Routing; + +namespace Umbraco.Web.Mvc +{ + public interface IFilteredControllerFactory : IControllerFactory + { + /// + /// Determines whether this instance can handle the specified request. + /// + /// The request. + /// true if this instance can handle the specified request; otherwise, false. + /// + bool CanHandle(RequestContext request); + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/MasterControllerFactory.cs b/src/Umbraco.Web/Mvc/MasterControllerFactory.cs new file mode 100644 index 0000000000..357dbf899c --- /dev/null +++ b/src/Umbraco.Web/Mvc/MasterControllerFactory.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Web.Mvc; +using System.Web.Routing; +using Umbraco.Core; + +namespace Umbraco.Web.Mvc +{ + /// + /// A controller factory which uses an internal list of in order to invoke + /// different controller factories dependent upon their implementation of for the current + /// request. Allows circumvention of MVC3's singly registered IControllerFactory. + /// + /// + internal class MasterControllerFactory : DefaultControllerFactory + { + private readonly FilteredControllerFactoriesResolver _slaveFactories; + private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(); + + private readonly ConcurrentDictionary _controllerCache = new ConcurrentDictionary(); + + public MasterControllerFactory(FilteredControllerFactoriesResolver factoryResolver) + { + _slaveFactories = factoryResolver; + } + + /// + /// Creates the specified controller by using the specified request context. + /// + /// The context of the HTTP request, which includes the HTTP context and route data. + /// The name of the controller. + /// The controller. + /// The parameter is null. + /// + /// The parameter is null or empty. + /// + public override IController CreateController(RequestContext requestContext, string controllerName) + { + var factory = _slaveFactories.Factories.FirstOrDefault(x => x.CanHandle(requestContext)); + return factory != null + ? factory.CreateController(requestContext, controllerName) + : base.CreateController(requestContext, controllerName); + } + + /// + /// Releases the specified controller. + /// + /// The controller to release. + /// + public override void ReleaseController(IController controller) + { + using (new WriteLock(_locker)) + { + bool released = false; + if (controller is Controller) + { + var requestContext = ((Controller)controller).ControllerContext.RequestContext; + var factory = _slaveFactories.Factories.FirstOrDefault(x => x.CanHandle(requestContext)); + if (factory != null) + { + factory.ReleaseController(controller); + released = true; + } + } + if (!released) base.ReleaseController(controller); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/RenderActionInvoker.cs b/src/Umbraco.Web/Mvc/RenderActionInvoker.cs new file mode 100644 index 0000000000..014b0c1e48 --- /dev/null +++ b/src/Umbraco.Web/Mvc/RenderActionInvoker.cs @@ -0,0 +1,35 @@ +using System.Web.Mvc; + +namespace Umbraco.Web.Mvc +{ + /// + /// Ensures that if an action for the Template name is not explicitly defined by a user, that the 'Index' action will execute + /// + public class RenderActionInvoker : ControllerActionInvoker + { + + /// + /// Ensures that if an action for the Template name is not explicitly defined by a user, that the 'Index' action will execute + /// + /// + /// + /// + /// + protected override ActionDescriptor FindAction(ControllerContext controllerContext, ControllerDescriptor controllerDescriptor, string actionName) + { + var ad = base.FindAction(controllerContext, controllerDescriptor, actionName); + + //now we need to check if it exists, if not we need to return the Index by default + if (ad == null) + { + //check if the controller is an instance of RenderMvcController + if (controllerContext.Controller is RenderMvcController) + { + return new ReflectedActionDescriptor(controllerContext.Controller.GetType().GetMethod("Index"), "Index", controllerDescriptor); + } + } + return ad; + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/RenderControllerFactory.cs b/src/Umbraco.Web/Mvc/RenderControllerFactory.cs new file mode 100644 index 0000000000..12b34f2e45 --- /dev/null +++ b/src/Umbraco.Web/Mvc/RenderControllerFactory.cs @@ -0,0 +1,93 @@ +using System; +using System.Web.Mvc; +using System.Web.Routing; +using System.Web.SessionState; + +namespace Umbraco.Web.Mvc +{ + /// + /// A controller factory for the render pipeline of Umbraco. This controller factory tries to create a controller with the supplied + /// name, and falls back to UmbracoController if none was found. + /// + /// + public class RenderControllerFactory : IFilteredControllerFactory + { + private readonly OverridenDefaultControllerFactory _innerFactory = new OverridenDefaultControllerFactory(); + + /// + /// Initializes a new instance of the class. + /// + public RenderControllerFactory() + { + + } + + + /// + /// Determines whether this instance can handle the specified request. + /// + /// The request. + /// true if this instance can handle the specified request; otherwise, false. + /// + public bool CanHandle(RequestContext request) + { + var dataToken = request.RouteData.DataTokens["area"]; + return dataToken == null || string.IsNullOrWhiteSpace(dataToken.ToString()); + } + + /// + /// Creates the specified controller by using the specified request context. + /// + /// + /// The controller. + /// + /// The request context.The name of the controller. + public IController CreateController(RequestContext requestContext, string controllerName) + { + Type controllerType = _innerFactory.GetControllerType(requestContext, controllerName) ?? + _innerFactory.GetControllerType(requestContext, ControllerExtensions.GetControllerName(typeof(RenderMvcController))); + + return _innerFactory.GetControllerInstance(requestContext, controllerType); + } + + /// + /// Gets the controller's session behavior. + /// + /// + /// The controller's session behavior. + /// + /// The request context.The name of the controller whose session behavior you want to get. + public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName) + { + return ((IControllerFactory)_innerFactory).GetControllerSessionBehavior(requestContext, controllerName); + } + + /// + /// Releases the specified controller. + /// + /// The controller. + public void ReleaseController(IController controller) + { + _innerFactory.ReleaseController(controller); + } + + /// + /// By default, only exposes which throws an exception + /// if the controller is not found. Since we want to try creating a controller, and then fall back to if one isn't found, + /// this nested class changes the visibility of 's internal methods in order to not have to rely on a try-catch. + /// + /// + public class OverridenDefaultControllerFactory : DefaultControllerFactory + { + public new IController GetControllerInstance(RequestContext requestContext, Type controllerType) + { + return base.GetControllerInstance(requestContext, controllerType); + } + + public new Type GetControllerType(RequestContext requestContext, string controllerName) + { + return base.GetControllerType(requestContext, controllerName); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/RenderModel.cs b/src/Umbraco.Web/Mvc/RenderModel.cs new file mode 100644 index 0000000000..1bb7d15e01 --- /dev/null +++ b/src/Umbraco.Web/Mvc/RenderModel.cs @@ -0,0 +1,15 @@ +using System.Xml; +using umbraco; +using umbraco.interfaces; + +namespace Umbraco.Web.Mvc +{ + public class RenderModel + { + public XmlNode CurrentXmlNode { get; set; } + public INode CurrentNode { get; set; } + + //TODO: We probably want to change this! + internal page UmbracoPage { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/RenderModelBinder.cs b/src/Umbraco.Web/Mvc/RenderModelBinder.cs new file mode 100644 index 0000000000..f66a53ea15 --- /dev/null +++ b/src/Umbraco.Web/Mvc/RenderModelBinder.cs @@ -0,0 +1,30 @@ +using System.Web.Mvc; + +namespace Umbraco.Web.Mvc +{ + public class RenderModelBinder : IModelBinder + { + + /// + /// Binds the model to a value by using the specified controller context and binding context. + /// + /// + /// The bound value. + /// + /// The controller context.The binding context. + public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) + { + var requestMatchesType = typeof(RenderModel) == bindingContext.ModelType; + + if (requestMatchesType) + { + //get the model from the route data + if (!controllerContext.RouteData.DataTokens.ContainsKey("umbraco")) + return null; + var model = controllerContext.RouteData.DataTokens["umbraco"] as RenderModel; + return model; + } + return null; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/RenderMvcController.cs b/src/Umbraco.Web/Mvc/RenderMvcController.cs new file mode 100644 index 0000000000..4d49fdfc8d --- /dev/null +++ b/src/Umbraco.Web/Mvc/RenderMvcController.cs @@ -0,0 +1,28 @@ +using System.IO; +using System.Web.Mvc; + +namespace Umbraco.Web.Mvc +{ + public class RenderMvcController : Controller + { + + public RenderMvcController() + { + ActionInvoker = new RenderActionInvoker(); + } + + public virtual ActionResult Index(RenderModel model) + { + var template = ControllerContext.RouteData.Values["action"].ToString(); + if (!System.IO.File.Exists( + Path.Combine(Server.MapPath(Constants.ViewLocation), template + ".cshtml"))) + { + return Content(""); + } + + return View(template, model); + + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs new file mode 100644 index 0000000000..59a82a60c3 --- /dev/null +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -0,0 +1,151 @@ +using System.Web; +using System.Web.Mvc; +using System.Web.Routing; +using Umbraco.Core; +using umbraco.cms.businesslogic.template; + +namespace Umbraco.Web.Mvc +{ + public class RenderRouteHandler : IRouteHandler + { + internal const string SingletonServiceName = "RenderRouteHandler"; + + public RenderRouteHandler(IControllerFactory controllerFactory) + { + _controllerFactory = controllerFactory; + } + + private readonly IControllerFactory _controllerFactory; + + #region IRouteHandler Members + + /// + /// Assigns the correct controller based on the Umbraco request and returns a standard MvcHandler to prcess the response, + /// this also stores the render model into the data tokens for the current RouteData. + /// + /// + /// + public IHttpHandler GetHttpHandler(RequestContext requestContext) + { + //need to ensure some http items are set! + var path = requestContext.HttpContext.Request.QueryString["path"]; + var qry = requestContext.HttpContext.Request.QueryString["qry"]; + requestContext.HttpContext.Items["UmbPage"] = requestContext.HttpContext.Request.QueryString["path"]; + requestContext.HttpContext.Items["VirtualUrl"] = string.Format("{0}{1}", path, qry); + + //var handlerUrl = requestHandler.cleanUrl(); + //var v4Handler = new requestHandler(UmbracoContext.Current.GetXml(), handlerUrl); + //var v4Page = new page(v4Handler.currentPage); + + ////this is a fix for this issue: + ////https://bitbucket.org/Shandem/umbramvco/issue/1/need-to-set-pageid-in-conextitems + ////to support some of the @Library methods: + //requestContext.HttpContext.Items["pageID"] = v4Page.PageID; + + ////this is a fix for issue: + //// https://bitbucket.org/Shandem/umbramvco/issue/2/current-culture + //// make sure we have the correct culture + //var tempCulture = v4Page.GetCulture(); + //if (tempCulture != "") + //{ + // Thread.CurrentThread.CurrentCulture = + // System.Globalization.CultureInfo.CreateSpecificCulture(tempCulture); + // Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture; + //} + + //var renderModel = new RenderModel() + //{ + // CurrentNode = new Node(v4Handler.currentPage), + // CurrentXmlNode = v4Handler.currentPage, + // UmbracoPage = v4Page + //}; + + ////put the render model into the current RouteData + //requestContext.RouteData.DataTokens.Add("umbraco", renderModel); + + //return GetHandlerForRoute(requestContext, renderModel); + return null; + } + + #endregion + + /// + /// Returns a RouteDefinition object based on the current renderModel + /// + /// + /// + /// + protected virtual RouteDefinition GetUmbracoRouteDefinition(RequestContext requestContext, RenderModel renderModel) + { + //creates the default route definition which maps to the 'UmbracoController' controller + var def = new RouteDefinition + { + ControllerName = ControllerExtensions.GetControllerName(), + Controller = new RenderMvcController(), + RenderModel = renderModel, + ActionName = ((Route)requestContext.RouteData.Route).Defaults["action"].ToString() + }; + + var templateId = renderModel.UmbracoPage.Template; + + //check that a template is defined) + if (templateId > 0) + { + + //check if there's a custom controller assigned, base on the document type alias. + var controller = _controllerFactory.CreateController(requestContext, renderModel.UmbracoPage.NodeTypeAlias); + + + //check if that controller exists + if (controller != null) + { + + //ensure the controller is of type 'UmbracoController' + if (controller is RenderMvcController) + { + //set the controller and name to the custom one + def.Controller = (ControllerBase)controller; + def.ControllerName = ControllerExtensions.GetControllerName(controller.GetType()); + } + else + { + //LogHelper.Warn("The current Document Type {0} matches a locally declared controller of type {1}. Custom Controllers for Umbraco routing must inherit from '{2}'.", renderModel.CurrentNode.ContentType.Alias, controller.GetType().FullName, typeof(UmbracoController).FullName); + //exit as we cannnot route to the custom controller, just route to the standard one. + return def; + } + + + var template = Template.GetTemplate(templateId); + if (template != null) + { + //check if the custom controller has an action with the same name as the template name (we convert ToUmbracoAlias since the template name might have invalid chars). + //NOTE: This also means that all custom actions MUST be PascalCase.. but that should be standard. + var templateName = template.Alias.Split('.')[0].ToUmbracoAlias(StringAliasCaseType.PascalCase); + def.ActionName = templateName; + } + } + } + + return def; + } + + /// + /// this will determine the controller and set the values in the route data + /// + /// + /// + protected internal IHttpHandler GetHandlerForRoute(RequestContext requestContext, RenderModel renderModel) + { + var routeDef = GetUmbracoRouteDefinition(requestContext, renderModel); + + //no post values, just route to the controller/action requried (local) + + requestContext.RouteData.Values["controller"] = routeDef.ControllerName; + if (!string.IsNullOrWhiteSpace(routeDef.ActionName)) + { + requestContext.RouteData.Values["action"] = routeDef.ActionName; + } + return new MvcHandler(requestContext); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/RenderViewEngine.cs b/src/Umbraco.Web/Mvc/RenderViewEngine.cs new file mode 100644 index 0000000000..53de5e7180 --- /dev/null +++ b/src/Umbraco.Web/Mvc/RenderViewEngine.cs @@ -0,0 +1,87 @@ +using System.Collections.Generic; +using System.Linq; +using System.Web.Mvc; +using Umbraco.Core; + +namespace Umbraco.Web.Mvc +{ + /// + /// A view engine to look into the template location specified in the config for the front-end/Rendering part of the cms, + /// this includes paths to render partial macros and media item templates. + /// + public class RenderViewEngine : RazorViewEngine + { + + private readonly IEnumerable _supplementedViewLocations = new[] { "/{0}.cshtml" }; + private readonly IEnumerable _supplementedPartialViewLocations = new[] { "/{0}.cshtml", "/Partials/{0}.cshtml", "/MacroPartials/{0}.cshtml" }; + + /// + /// Constructor + /// + public RenderViewEngine() + { + const string templateFolder = Constants.ViewLocation; + + var replaceWithUmbracoFolder = _supplementedViewLocations.ForEach(location => templateFolder + location); + var replacePartialWithUmbracoFolder = _supplementedPartialViewLocations.ForEach(location => templateFolder + location); + + //The Render view engine doesn't support Area's so make those blank + ViewLocationFormats = replaceWithUmbracoFolder.ToArray(); + PartialViewLocationFormats = replacePartialWithUmbracoFolder.ToArray(); + + AreaPartialViewLocationFormats = new string[] { }; + AreaViewLocationFormats = new string[] { }; + } + + public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) + { + if (!ShouldFindView(controllerContext, false)) + { + return new ViewEngineResult(new string[] { }); + } + + return base.FindView(controllerContext, viewName, masterName, useCache); + } + + public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache) + { + if (!ShouldFindView(controllerContext, true)) + { + return new ViewEngineResult(new string[] { }); + } + + return base.FindPartialView(controllerContext, partialViewName, useCache); + } + + /// + /// Determines if the view should be found, this is used for view lookup performance and also to ensure + /// less overlap with other user's view engines. This will return true if the Umbraco back office is rendering + /// and its a partial view or if the umbraco front-end is rendering but nothing else. + /// + /// + /// + /// + private bool ShouldFindView(ControllerContext controllerContext, bool isPartial) + { + //first check if we're rendering a partial view for the back office, or surface controller, etc... + //anything that is not IUmbracoRenderModel as this should only pertain to Umbraco views. + if (isPartial + && controllerContext.RouteData.DataTokens.ContainsKey("umbraco") + && !(controllerContext.RouteData.DataTokens["umbraco"] is RenderModel)) + { + return true; + } + + //only find views if we're rendering the umbraco front end + if (controllerContext.RouteData.DataTokens.ContainsKey("umbraco") + && controllerContext.RouteData.DataTokens["umbraco"] != null + && controllerContext.RouteData.DataTokens["umbraco"] is RenderModel) + { + return true; + } + + return false; + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/RenderViewPage.cs b/src/Umbraco.Web/Mvc/RenderViewPage.cs new file mode 100644 index 0000000000..8ced37ad1f --- /dev/null +++ b/src/Umbraco.Web/Mvc/RenderViewPage.cs @@ -0,0 +1,47 @@ +using System.Web.Mvc; + +namespace Umbraco.Web.Mvc +{ + /// + /// The View that front-end templates inherit from + /// + public abstract class RenderViewPage : WebViewPage + { + //protected RenderViewPage() + //{ + + //} + + //protected override void InitializePage() + //{ + // //set the model to the current node if it is not set, this is generally not the case + // if (Model != null) + // { + // //this.ViewData.Model = Model; + // var backingItem = new DynamicBackingItem(Model.CurrentNode); + // var dynamicNode = new DynamicNode(backingItem); + // CurrentPage = dynamicNode; + // } + //} + + + //public dynamic CurrentPage { get; private set; } + + //private ICultureDictionary _cultureDictionary; + //public string GetDictionary(string key) + //{ + // if (_cultureDictionary == null) + // { + // _cultureDictionary = new UmbracoCultureDictionary(); + // } + // return _cultureDictionary[key]; + //} + + //private RazorLibraryCore _library; + //public RazorLibraryCore Library + //{ + // get { return _library ?? (_library = new RazorLibraryCore(Model.CurrentNode)); } + //} + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/RouteDefinition.cs b/src/Umbraco.Web/Mvc/RouteDefinition.cs new file mode 100644 index 0000000000..59aae50b1f --- /dev/null +++ b/src/Umbraco.Web/Mvc/RouteDefinition.cs @@ -0,0 +1,23 @@ +using System.Web.Mvc; + +namespace Umbraco.Web.Mvc +{ + /// + /// Represents the data required to route to a specific controller/action during an Umbraco request + /// + public class RouteDefinition + { + public string ControllerName { get; set; } + public string ActionName { get; set; } + + /// + /// The Controller instance found for routing to + /// + public ControllerBase Controller { get; set; } + + /// + /// The current RenderModel found for the request + /// + public object RenderModel { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/NiceUrlProvider.cs b/src/Umbraco.Web/Routing/NiceUrlProvider.cs index 28ee454493..6e6d3917d8 100644 --- a/src/Umbraco.Web/Routing/NiceUrlProvider.cs +++ b/src/Umbraco.Web/Routing/NiceUrlProvider.cs @@ -65,7 +65,7 @@ namespace Umbraco.Web.Routing ? null : _umbracoContext.RoutesCache.GetRoute(nodeId); - if (route != null) + if (!string.IsNullOrEmpty(route)) { // route is / eg "-1/", "-1/foo", "123/", "123/foo/bar"... int pos = route.IndexOf('/'); diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 0e62ba8839..e1217b77c3 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -239,6 +239,19 @@ Properties\SolutionInfo.cs + + + + + + + + + + + + + diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index 293472d327..e18adf0ad7 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -26,9 +26,8 @@ namespace Umbraco.Web public class UmbracoModule : IHttpModule { /// - /// Checks if the current request should process the request as a front-end umbraco request, if this is tru - /// it then creates the DocumentRequest object, finds the document, domain and culture and stores this back - /// to the UmbracoContext + /// Assogms a new DocumentRequest to the UmbracoContext and finds the document, + /// domain and culture associated with the current front-end request. /// /// /// diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index d6a11aa23f..fb3dc7ec0b 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -1,6 +1,10 @@ using System; +using System.Collections.Generic; +using System.Web.Mvc; +using System.Web.Routing; using Umbraco.Core; using Umbraco.Web.Media.ThumbnailProviders; +using Umbraco.Web.Mvc; using Umbraco.Web.Routing; using umbraco.businesslogic; @@ -29,6 +33,24 @@ namespace Umbraco.Web ClientDependency.Core.CompositeFiles.Providers.XmlFileMapper.FileMapVirtualFolder = "~/App_Data/TEMP/ClientDependency"; ClientDependency.Core.CompositeFiles.Providers.BaseCompositeFileProcessingProvider.UrlTypeDefault = ClientDependency.Core.CompositeFiles.Providers.CompositeUrlType.Base64QueryStrings; + //set master controller factory + ControllerBuilder.Current.SetControllerFactory( + new MasterControllerFactory(FilteredControllerFactoriesResolver.Current)); + + //set the render view engine + ViewEngines.Engines.Add(new RenderViewEngine()); + + //set model binder + ModelBinders.Binders.Add(new KeyValuePair(typeof(RenderModel), new RenderModelBinder())); + + //set routes + var route = RouteTable.Routes.MapRoute( + "Umbraco_default", + "Umbraco/RenderMvc/{action}/{id}", + new { controller = "RenderMvc", action = "Index", id = UrlParameter.Optional } + ); + route.RouteHandler = new RenderRouteHandler(ControllerBuilder.Current.GetControllerFactory()); + //find and initialize the application startup handlers ApplicationStartupHandler.RegisterHandlers(); @@ -42,19 +64,27 @@ namespace Umbraco.Web { base.InitializeResolvers(); + FilteredControllerFactoriesResolver.Current = new FilteredControllerFactoriesResolver( + //add all known factories, devs can then modify this list on application startup either by binding to events + //or in their own global.asax + new[] + { + typeof (RenderControllerFactory) + }); + LastChanceLookupResolver.Current = new LastChanceLookupResolver(new DefaultLastChanceLookup()); DocumentLookupsResolver.Current = new DocumentLookupsResolver( //add all known resolvers in the correct order, devs can then modify this list on application startup either by binding to events //or in their own global.asax - new Type[] - { - typeof(LookupByNiceUrl), - typeof(LookupById), - typeof(LookupByNiceUrlAndTemplate), - typeof(LookupByProfile), - typeof(LookupByAlias) - }); + new[] + { + typeof (LookupByNiceUrl), + typeof (LookupById), + typeof (LookupByNiceUrlAndTemplate), + typeof (LookupByProfile), + typeof (LookupByAlias) + }); RoutesCacheResolver.Current = new RoutesCacheResolver(new DefaultRoutesCache());