using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Routing; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Dictionary; using Umbraco.Core.Dynamics; using Umbraco.Core.Logging; using Umbraco.Core.ObjectResolution; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Sync; using Umbraco.Web.Dictionary; using Umbraco.Web.Media; using Umbraco.Web.Media.ThumbnailProviders; using Umbraco.Web.Models; using Umbraco.Web.Mvc; using Umbraco.Web.PropertyEditors; using Umbraco.Web.Routing; using Umbraco.Web.WebApi; using umbraco.BusinessLogic; using umbraco.businesslogic; using umbraco.cms.businesslogic; using umbraco.presentation.cache; namespace Umbraco.Web { /// /// A bootstrapper for the Umbraco application which initializes all objects including the Web portion of the application /// public class WebBootManager : CoreBootManager { private readonly bool _isForTesting; public WebBootManager(UmbracoApplicationBase umbracoApplication) : this(umbracoApplication, false) { } /// /// Constructor for unit tests, ensures some resolvers are not initialized /// /// /// internal WebBootManager(UmbracoApplicationBase umbracoApplication, bool isForTesting) : base(umbracoApplication) { _isForTesting = isForTesting; } /// /// Initialize objects before anything during the boot cycle happens /// /// public override IBootManager Initialize() { base.Initialize(); // Backwards compatibility - set the path and URL type for ClientDependency 1.5.1 [LK] 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 the plugin view engine ViewEngines.Engines.Add(new PluginViewEngine()); //set model binder ModelBinders.Binders.Add(new KeyValuePair(typeof(RenderModel), new RenderModelBinder())); return this; } /// /// Override this method in order to ensure that the UmbracoContext is also created, this can only be /// created after resolution is frozen! /// protected override void FreezeResolution() { base.FreezeResolution(); //before we do anything, we'll ensure the umbraco context //see: http://issues.umbraco.org/issue/U4-1717 UmbracoContext.EnsureContext(new HttpContextWrapper(UmbracoApplication.Context), ApplicationContext); } /// /// Adds custom types to the ApplicationEventsResolver /// protected override void InitializeApplicationEventsResolver() { base.InitializeApplicationEventsResolver(); ApplicationEventsResolver.Current.AddType(); ApplicationEventsResolver.Current.AddType(); //We need to remove these types because we've obsoleted them and we don't want them executing: ApplicationEventsResolver.Current.RemoveType(); } /// /// Ensure that the OnApplicationStarted methods of the IApplicationEvents are called /// /// /// public override IBootManager Complete(Action afterComplete) { //set routes CreateRoutes(); base.Complete(afterComplete); //Now, startup all of our legacy startup handler ApplicationEventsResolver.Current.InstantiateLegacyStartupHanlders(); return this; } /// /// Creates the routes /// protected internal void CreateRoutes() { var umbracoPath = GlobalSettings.UmbracoMvcArea; //Create the front-end route var defaultRoute = RouteTable.Routes.MapRoute( "Umbraco_default", umbracoPath + "/RenderMvc/{action}/{id}", new { controller = "RenderMvc", action = "Index", id = UrlParameter.Optional } ); defaultRoute.RouteHandler = new RenderRouteHandler(ControllerBuilder.Current.GetControllerFactory()); //Create the install routes var installPackageRoute = RouteTable.Routes.MapRoute( "Umbraco_install_packages", "Install/PackageInstaller/{action}/{id}", new { controller = "InstallPackage", action = "Index", id = UrlParameter.Optional } ); installPackageRoute.DataTokens.Add("area", umbracoPath); //Create the REST/web/script service routes var webServiceRoutes = RouteTable.Routes.MapRoute( "Umbraco_web_services", umbracoPath + "/RestServices/{controller}/{action}/{id}", new { controller = "SaveFileController", action = "Index", id = UrlParameter.Optional }, //look in this namespace for controllers new string[] { "Umbraco.Web.WebServices" } ); webServiceRoutes.DataTokens.Add("area", umbracoPath); RoutePluginControllers(); } private void RoutePluginControllers() { var umbracoPath = GlobalSettings.UmbracoMvcArea; //we need to find the plugin controllers and route them var pluginControllers = SurfaceControllerResolver.Current.RegisteredSurfaceControllers.Concat( UmbracoApiControllerResolver.Current.RegisteredUmbracoApiControllers).ToArray(); //local controllers do not contain the attribute var localControllers = pluginControllers.Where(x => PluginController.GetMetadata(x).AreaName.IsNullOrWhiteSpace()); foreach (var s in localControllers) { if (TypeHelper.IsTypeAssignableFrom(s)) { RouteLocalSurfaceController(s, umbracoPath); } else if (TypeHelper.IsTypeAssignableFrom(s)) { RouteLocalApiController(s, umbracoPath); } } //need to get the plugin controllers that are unique to each area (group by) var pluginSurfaceControlleres = pluginControllers.Where(x => !PluginController.GetMetadata(x).AreaName.IsNullOrWhiteSpace()); var groupedAreas = pluginSurfaceControlleres.GroupBy(controller => PluginController.GetMetadata(controller).AreaName); //loop through each area defined amongst the controllers foreach (var g in groupedAreas) { //create an area for the controllers (this will throw an exception if all controllers are not in the same area) var pluginControllerArea = new PluginControllerArea(g.Select(PluginController.GetMetadata)); //register it RouteTable.Routes.RegisterArea(pluginControllerArea); } } private void RouteLocalApiController(Type controller, string umbracoPath) { var meta = PluginController.GetMetadata(controller); var route = RouteTable.Routes.MapHttpRoute( string.Format("umbraco-{0}-{1}", "api", meta.ControllerName), umbracoPath + "/Api/" + meta.ControllerName + "/{action}/{id}",//url to match new { controller = meta.ControllerName, id = UrlParameter.Optional }, new { controller = @"(\w+)Api" }); //Must be suffixed with "Api" (i.e. MyApiController) //web api routes don't set the data tokens object if (route.DataTokens == null) { route.DataTokens = new RouteValueDictionary(); } route.DataTokens.Add("Namespaces", meta.ControllerNamespace); //look in this namespace to create the controller route.DataTokens.Add("umbraco", "api"); //ensure the umbraco token is set } private void RouteLocalSurfaceController(Type controller, string umbracoPath) { var meta = PluginController.GetMetadata(controller); var route = RouteTable.Routes.MapRoute( string.Format("umbraco-{0}-{1}", "surface", meta.ControllerName), umbracoPath + "/Surface/" + meta.ControllerName + "/{action}/{id}",//url to match new { controller = meta.ControllerName, action = "Index", id = UrlParameter.Optional }, //NOTE: There SHOULD be a constraint here to only match controllers with a "SurfaceController" suffix but we forgot to include it in the // 4.10 release so we can't include it now :( new[] { meta.ControllerNamespace }); //look in this namespace to create the controller route.DataTokens.Add("umbraco", "surface"); //ensure the umbraco token is set //make it use our custom/special SurfaceMvcHandler route.RouteHandler = new SurfaceRouteHandler(); } /// /// Initializes all web based and core resolves /// protected override void InitializeResolvers() { base.InitializeResolvers(); //we should not proceed to change this if the app/database is not configured since there will // be no user, plus we don't need to have server messages sent if this is the case. if (ApplicationContext.IsConfigured && ApplicationContext.DatabaseContext.IsDatabaseConfigured) { var user = User.GetUser(UmbracoSettings.DistributedCallUser); try { //Override the ServerMessengerResolver to set a username/password for the distributed calls ServerMessengerResolver.Current.SetServerMessenger(new DefaultServerMessenger( user.LoginName, user.GetPassword())); } catch (Exception e) { LogHelper.Error("An error occurred trying to set the IServerMessenger during application startup", e); } } //We are going to manually remove a few cache refreshers here because we've obsoleted them and we don't want them // to be registered more than once CacheRefreshersResolver.Current.RemoveType(); CacheRefreshersResolver.Current.RemoveType(); CacheRefreshersResolver.Current.RemoveType(); CacheRefreshersResolver.Current.RemoveType(); CacheRefreshersResolver.Current.RemoveType(); SurfaceControllerResolver.Current = new SurfaceControllerResolver( PluginManager.Current.ResolveSurfaceControllers()); UmbracoApiControllerResolver.Current = new UmbracoApiControllerResolver( PluginManager.Current.ResolveUmbracoApiControllers()); //the base creates the PropertyEditorValueConvertersResolver but we want to modify it in the web app and replace //the TinyMcePropertyEditorValueConverter with the RteMacroRenderingPropertyEditorValueConverter PropertyEditorValueConvertersResolver.Current.RemoveType(); PropertyEditorValueConvertersResolver.Current.AddType(); PublishedContentStoreResolver.Current = new PublishedContentStoreResolver(new DefaultPublishedContentStore()); PublishedMediaStoreResolver.Current = new PublishedMediaStoreResolver(new DefaultPublishedMediaStore()); 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) }); // the legacy 404 will run from within ContentFinderByNotFoundHandlers below // so for the time being there is no last chance finder ContentLastChanceFinderResolver.Current = new ContentLastChanceFinderResolver(); ContentFinderResolver.Current = new ContentFinderResolver( //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[] { typeof (ContentFinderByPageIdQuery), typeof (ContentFinderByNiceUrl), typeof (ContentFinderByIdPath), // these will be handled by ContentFinderByNotFoundHandlers // so they can be enabled/disabled even though resolvers are not public yet //typeof (ContentFinderByNiceUrlAndTemplate), //typeof (ContentFinderByProfile), //typeof (ContentFinderByUrlAlias), typeof (ContentFinderByNotFoundHandlers) }); RoutesCacheResolver.Current = new RoutesCacheResolver(new DefaultRoutesCache(_isForTesting == false)); ThumbnailProvidersResolver.Current = new ThumbnailProvidersResolver( PluginManager.Current.ResolveThumbnailProviders()); ImageUrlProviderResolver.Current = new ImageUrlProviderResolver( PluginManager.Current.ResolveImageUrlProviders()); CultureDictionaryFactoryResolver.Current = new CultureDictionaryFactoryResolver( new DefaultCultureDictionaryFactory()); } } }