From a7834b45da05124464f9d1fe9fb76f3ef4a0bf2d Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Sun, 30 Dec 2012 03:11:21 +0300 Subject: [PATCH] Created UmbracoAuthorizeAttribute, UmbracoAuthorizedController for all base MVC controllers that required a back office user to be logged in. Converted over the SaveTemplate and SavePartialView methods of the codeEditorSave legacy web service to use MVC services and marked them obsolete. Created a generic route to route all controllers found in the namespace Umbraco.Web.WebServices. Added a 'Url' property on the BasePage webforms class to expose a UrlHelper for use in views. Created UrlHelper extensions to easily get the base url path of the REST MVC web services. Converted the EditView page and JavaScript to use the new REST services with jQuery instead of crappy MS ajax. --- src/Umbraco.Core/Umbraco.Core.csproj | 1 + src/Umbraco.Core/UrlHelperExtensions.cs | 27 +++ src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 6 +- .../umbraco/config/lang/en_us.xml | 4 + .../umbraco/settings/views/EditView.aspx | 10 +- .../umbraco_client/Editors/EditView.js | 68 +++++--- .../UmbracoInstallAuthorizeAttribute.cs | 2 +- .../Mvc/UmbracoAuthorizeAttribute.cs | 83 ++++++++++ .../Mvc/UmbracoAuthorizedController.cs | 19 +++ src/Umbraco.Web/Umbraco.Web.csproj | 3 + src/Umbraco.Web/WebBootManager.cs | 11 ++ .../WebServices/SaveFileController.cs | 154 ++++++++++++++++++ .../webservices/codeEditorSave.asmx.cs | 75 ++++----- src/packages/repositories.config | 1 + .../BasePages/BasePage.cs | 14 ++ src/umbraco.businesslogic/packages.config | 7 + .../umbraco.businesslogic.csproj | 33 ++++ 17 files changed, 446 insertions(+), 72 deletions(-) create mode 100644 src/Umbraco.Core/UrlHelperExtensions.cs create mode 100644 src/Umbraco.Web/Mvc/UmbracoAuthorizeAttribute.cs create mode 100644 src/Umbraco.Web/Mvc/UmbracoAuthorizedController.cs create mode 100644 src/Umbraco.Web/WebServices/SaveFileController.cs create mode 100644 src/umbraco.businesslogic/packages.config diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 3b2eebb79c..be87bc21b8 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -666,6 +666,7 @@ + diff --git a/src/Umbraco.Core/UrlHelperExtensions.cs b/src/Umbraco.Core/UrlHelperExtensions.cs new file mode 100644 index 0000000000..9755dc24cc --- /dev/null +++ b/src/Umbraco.Core/UrlHelperExtensions.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Web.Mvc; +using Umbraco.Core.Configuration; + +namespace Umbraco.Core +{ + /// + /// Extension methods for UrlHelper + /// + public static class UrlHelperExtensions + { + /// + /// Returns the base path (not including the 'action') of the MVC controller "SaveFileController" + /// + /// + /// + public static string GetSaveFileServicePath(this UrlHelper url) + { + var result = url.Action("SavePartialView", "SaveFile", new {area = GlobalSettings.UmbracoMvcArea}); + return result.TrimEnd("SavePartialView"); + } + + } +} diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 6796e19173..cbdb999bb6 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -780,7 +780,11 @@ To manage your website, simply open the umbraco back office and start adding con Xslt could not be saved, check file permissions Xslt saved No errors in xslt - Content unpublished + Content unpublished + Partial view saved + Partial view saved without any errors! + Partial view not saved + An error occurred saving the file. Uses CSS syntax ex: h1, .redHeader, .blueTex diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index e024bd6197..dc593fec55 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -779,6 +779,10 @@ To manage your website, simply open the umbraco back office and start adding con Xslt could not be saved, check file permissions Xslt saved No errors in xslt + Partial view saved + Partial view saved without any errors! + Partial view not saved + An error occurred saving the file. Uses CSS syntax ex: h1, .redHeader, .blueTex diff --git a/src/Umbraco.Web.UI/umbraco/settings/views/EditView.aspx b/src/Umbraco.Web.UI/umbraco/settings/views/EditView.aspx index 91b892738a..e07c2d48d4 100644 --- a/src/Umbraco.Web.UI/umbraco/settings/views/EditView.aspx +++ b/src/Umbraco.Web.UI/umbraco/settings/views/EditView.aspx @@ -2,6 +2,7 @@ CodeBehind="EditView.aspx.cs" Inherits="Umbraco.Web.UI.Umbraco.Settings.Views.EditView" ValidateRequest="False" %> +<%@ Import Namespace="Umbraco.Core" %> <%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> <%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> @@ -16,17 +17,12 @@ var editView = new Umbraco.Editors.EditView({ editorType: "<%= EditorType.ToString() %>", originalFileName: "<%=OriginalFileName %>", + restServiceLocation: "<%= Url.GetSaveFileServicePath() %>", masterPageDropDown: $("#<%= MasterTemplate.ClientID %>"), nameTxtBox: $("#<%= NameTxt.ClientID %>"), aliasTxtBox: $("#<%= AliasTxt.ClientID %>"), saveButton: $("#<%= ((Control)SaveButton).ClientID %>"), - templateId: '<%= Request.QueryString["templateID"] %>', - msgs: { - templateErrorHeader: "<%= umbraco.ui.Text("speechBubbles", "templateErrorHeader") %>", - templateErrorText: "<%= umbraco.ui.Text("speechBubbles", "templateErrorText") %>", - templateSavedHeader: "<%= umbraco.ui.Text("speechBubbles", "templateSavedHeader") %>", - templateSavedText: "<%= umbraco.ui.Text("speechBubbles", "templateSavedText") %>" - } + templateId: '<%= Request.QueryString["templateID"] %>' }); //initialize it. editView.init(); diff --git a/src/Umbraco.Web.UI/umbraco_client/Editors/EditView.js b/src/Umbraco.Web.UI/umbraco_client/Editors/EditView.js index a76d59d948..e1b29cd201 100644 --- a/src/Umbraco.Web.UI/umbraco_client/Editors/EditView.js +++ b/src/Umbraco.Web.UI/umbraco_client/Editors/EditView.js @@ -2,8 +2,9 @@ (function ($) { - Umbraco.Editors.EditView = base2.Base.extend({ + /// Defines the EditView class to controll the persisting of the view file and UI interaction + //private methods/variables _opts: null, @@ -36,39 +37,48 @@ if (this._opts.editorType == "Template") { //saving a template view - - umbraco.presentation.webservices.codeEditorSave.SaveTemplate( - this._opts.nameTxtBox.val(), - this._opts.aliasTxtBox.val(), - codeVal, - this._opts.templateId, - this._opts.masterPageDropDown.val(), - function(t) { self.submitSuccess(t); }, - function(t) { self.submitFailure(t); }); + + $.post(self._opts.restServiceLocation + "SaveTemplate", + JSON.stringify({ + templateName: this._opts.nameTxtBox.val(), + templateAlias: this._opts.aliasTxtBox.val(), + templateContents: codeVal, + templateId: this._opts.templateId, + masterTemplateId: this._opts.masterPageDropDown.val() + }), + function(e) { + if (e.success) { + self.submitSuccess(e.message, e.header); + } else { + self.submitFailure(e.message, e.header); + } + }); } else { //saving a partial view - - umbraco.presentation.webservices.codeEditorSave.SavePartialView( - this._opts.nameTxtBox.val(), - this._opts.originalFileName, - codeVal, - function (t) { self.submitSuccess(t); }, - function (t) { self.submitFailure(t); }); + + $.post(self._opts.restServiceLocation + "SavePartialView", + JSON.stringify({ + filename: this._opts.nameTxtBox.val(), + oldName: this._opts.originalFileName, + contents: codeVal + }), + function(e) { + if (e.success) { + self.submitSuccess(e.message, e.header); + } else { + self.submitFailure(e.message, e.header); + } + }); } }, - submitSuccess: function (t) { - if (t != 'true') { - top.UmbSpeechBubble.ShowMessage('error', this._opts.msgs.templateErrorHeader, this._opts.msgs.templateErrorText); - } - else { - top.UmbSpeechBubble.ShowMessage('save', this._opts.msgs.templateSavedHeader, this._opts.msgs.templateSavedText); - } + submitSuccess: function (err, header) { + top.UmbSpeechBubble.ShowMessage('save', header, err); }, - submitFailure: function (t) { - top.UmbSpeechBubble.ShowMessage('error', this._opts.msgs.templateErrorHeader, this._opts.msgs.templateErrorText); + submitFailure: function (err, header) { + top.UmbSpeechBubble.ShowMessage('error', header, err); }, changeMasterPageFile: function ( ) { @@ -102,5 +112,11 @@ }); + //Set defaults for jQuery ajax calls. + $.ajaxSetup({ + dataType: 'json', + cache: false, + contentType: 'application/json; charset=utf-8' + }); })(jQuery); \ No newline at end of file diff --git a/src/Umbraco.Web/Install/UmbracoInstallAuthorizeAttribute.cs b/src/Umbraco.Web/Install/UmbracoInstallAuthorizeAttribute.cs index a990088fdf..75360a1bba 100644 --- a/src/Umbraco.Web/Install/UmbracoInstallAuthorizeAttribute.cs +++ b/src/Umbraco.Web/Install/UmbracoInstallAuthorizeAttribute.cs @@ -27,7 +27,7 @@ namespace Umbraco.Web.Install } /// - /// Ensures that the user must be in the Administrator or the Install role + /// Ensures that the user must be logged in or that the application is not configured just yet. /// /// /// diff --git a/src/Umbraco.Web/Mvc/UmbracoAuthorizeAttribute.cs b/src/Umbraco.Web/Mvc/UmbracoAuthorizeAttribute.cs new file mode 100644 index 0000000000..5d25dd69de --- /dev/null +++ b/src/Umbraco.Web/Mvc/UmbracoAuthorizeAttribute.cs @@ -0,0 +1,83 @@ +using System; +using System.Web; +using System.Web.Mvc; +using Umbraco.Core; +using umbraco.BasePages; + +namespace Umbraco.Web.Mvc +{ + /// + /// Ensures authorization occurs for the installer if it has already completed. If install has not yet occured + /// then the authorization is successful + /// + internal class UmbracoAuthorizeAttribute : AuthorizeAttribute + { + private readonly ApplicationContext _applicationContext; + + public UmbracoAuthorizeAttribute(ApplicationContext appContext) + { + if (appContext == null) throw new ArgumentNullException("appContext"); + _applicationContext = appContext; + } + + public UmbracoAuthorizeAttribute() + : this(ApplicationContext.Current) + { + + } + + /// + /// Ensures that the user must be in the Administrator or the Install role + /// + /// + /// + protected override bool AuthorizeCore(HttpContextBase httpContext) + { + if (httpContext == null) + { + throw new ArgumentNullException("httpContext"); + } + + try + { + //we need to that the app is configured and that a user is logged in + if (!_applicationContext.IsConfigured) + return false; + var isLoggedIn = BasePage.ValidateUserContextID(BasePage.umbracoUserContextID); + return isLoggedIn; + } + catch (Exception) + { + return false; + } + } + + /// + /// Override the OnAuthorization so that we can return a custom response. + /// + /// + public override void OnAuthorization(AuthorizationContext filterContext) + { + Mandate.ParameterNotNull(filterContext, "filterContext"); + if (OutputCacheAttribute.IsChildActionCacheActive(filterContext)) + throw new InvalidOperationException("Cannot use " + typeof(UmbracoAuthorizeAttribute).FullName + " on a child action"); + if (AuthorizeCore(filterContext.HttpContext)) + { + //with a little help from dotPeek... this is what it normally would do + var cache = filterContext.HttpContext.Response.Cache; + cache.SetProxyMaxAge(new TimeSpan(0L)); + cache.AddValidationCallback(CacheValidateHandler, null); + } + else + { + //they aren't authorized + throw new HttpException((int)global::System.Net.HttpStatusCode.Unauthorized, "You must login to view this resource."); + } + } + + private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus) + { + validationStatus = OnCacheAuthorization(new HttpContextWrapper(context)); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/UmbracoAuthorizedController.cs b/src/Umbraco.Web/Mvc/UmbracoAuthorizedController.cs new file mode 100644 index 0000000000..044109ee12 --- /dev/null +++ b/src/Umbraco.Web/Mvc/UmbracoAuthorizedController.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Web.Mvc; + +namespace Umbraco.Web.Mvc +{ + /// + /// A base MVC controller for use in the back office that ensures that every call to it authorizes the current user. + /// + /// + /// This controller essentially just uses a global UmbracoAuthorizeAttribute, inheritors that require more granular control over the + /// authorization of each method can use this attribute instead of inheriting from this controller. + /// + [UmbracoAuthorize] + public abstract class UmbracoAuthorizedController : Controller + { + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index bf6d842b2d..842ea217b9 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -280,6 +280,8 @@ + + @@ -1845,6 +1847,7 @@ Reference.map + diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index 28e6dad464..58b62c6425 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -32,6 +32,7 @@ namespace Umbraco.Web public WebBootManager(UmbracoApplication umbracoApplication) : this(umbracoApplication, false) { + } /// @@ -158,6 +159,16 @@ namespace Umbraco.Web ); installPackageRoute.DataTokens.Add("area", umbracoPath); + //Create the REST/web/script service routes + var webServiceRoutes = RouteTable.Routes.MapRoute( + "Umbraco_web_services", + "Umbraco/RestServices/{controller}/{action}/{id}", + new {controller = "SaveFileController", action = "Index", id = UrlParameter.Optional}, + //VERY IMPORTANT! for this route, only match controllers in this namespace! + new string[] { "Umbraco.Web.WebServices" } + ); + webServiceRoutes.DataTokens.Add("area", umbracoPath); + //we need to find the surface controllers and route them var surfaceControllers = SurfaceControllerResolver.Current.RegisteredSurfaceControllers.ToArray(); diff --git a/src/Umbraco.Web/WebServices/SaveFileController.cs b/src/Umbraco.Web/WebServices/SaveFileController.cs new file mode 100644 index 0000000000..e5bac84d06 --- /dev/null +++ b/src/Umbraco.Web/WebServices/SaveFileController.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Web.Mvc; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Web.Mvc; +using umbraco; +using umbraco.BasePages; +using umbraco.cms.businesslogic.template; +using umbraco.presentation.cache; + +namespace Umbraco.Web.WebServices +{ + + /// + /// A REST controller used to save files such as templates, partial views, macro files, etc... + /// + /// + /// This isn't fully implemented yet but we should migrate all of the logic in the umbraco.presentation.webservices.codeEditorSave + /// over to this controller. + /// + public class SaveFileController : UmbracoAuthorizedController + { + + /// + /// Saves a partial view for a partial view macr + /// + /// + /// + /// + /// + [HttpPost] + public JsonResult SavePartialView(string filename, string oldName, string contents) + { + var folderPath = SystemDirectories.MvcViews + "/Partials/"; + + // validate file + IOHelper.ValidateEditPath(IOHelper.MapPath(folderPath + filename), folderPath); + // validate extension + IOHelper.ValidateFileExtension(IOHelper.MapPath(folderPath + filename), new[] { "cshtml" }.ToList()); + + var val = contents; + var saveOldPath = oldName.StartsWith("~/") ? IOHelper.MapPath(oldName) : IOHelper.MapPath(folderPath + oldName); + var savePath = filename.StartsWith("~/") ? IOHelper.MapPath(filename) : IOHelper.MapPath(folderPath + filename); + + //Directory check.. only allow files in script dir and below to be edited + if (!savePath.StartsWith(IOHelper.MapPath(folderPath))) + { + return Failed( + ui.Text("speechBubbles", "partialViewErrorText"), ui.Text("speechBubbles", "partialViewErrorHeader"), + //pass in a new exception ... this will also append the the message + new ArgumentException("Illegal path: " + savePath)); + } + + //deletes the old file + if (savePath != saveOldPath) + { + if (System.IO.File.Exists(saveOldPath)) + System.IO.File.Delete(saveOldPath); + } + using (var sw = System.IO.File.CreateText(savePath)) + { + sw.Write(val); + } + return Success(ui.Text("speechBubbles", "partialViewSavedText"), ui.Text("speechBubbles", "partialViewSavedHeader")); + } + + /// + /// Saves a template + /// + /// + /// + /// + /// + /// + /// + [HttpPost] + public JsonResult SaveTemplate(string templateName, string templateAlias, string templateContents, int templateId, int masterTemplateId) + { + Template t; + try + { + t = new Template(templateId) + { + Text = templateName, + Alias = templateAlias, + MasterTemplate = masterTemplateId, + Design = templateContents + }; + } + catch (ArgumentException ex) + { + //the template does not exist + return Failed("Template does not exist", ui.Text("speechBubbles", "templateErrorHeader"), ex); + } + + try + { + t.Save(); + + // Clear cache in rutime + if (UmbracoSettings.UseDistributedCalls) + dispatcher.Refresh(new Guid("dd12b6a0-14b9-46e8-8800-c154f74047c8"), t.Id); + else + template.ClearCachedTemplate(t.Id); + + return Success(ui.Text("speechBubbles", "templateSavedText"), ui.Text("speechBubbles", "templateSavedHeader")); + } + catch (Exception ex) + { + return Failed(ui.Text("speechBubbles", "templateErrorText"), ui.Text("speechBubbles", "templateErrorHeader"), ex); + } + } + + /// + /// Returns a successful message + /// + /// The message to display in the speach bubble + /// The header to display in the speach bubble + /// + private JsonResult Success(string message, string header) + { + return Json(new + { + success = true, + message = message, + header = header + }); + } + + /// + /// Returns a failed message + /// + /// The message to display in the speach bubble + /// The header to display in the speach bubble + /// The exception if there was one + /// + private JsonResult Failed(string message, string header, Exception exception = null) + { + if (exception != null) + LogHelper.Error("An error occurred saving a file. " + message, exception); + return Json(new + { + success = false, + header = header, + message = message + (exception == null ? "" : (exception.Message + ". Check log for details.")) + }); + } + + } +} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/codeEditorSave.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/codeEditorSave.asmx.cs index 1802307a20..9d34946b21 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/codeEditorSave.asmx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/codeEditorSave.asmx.cs @@ -338,48 +338,48 @@ namespace umbraco.presentation.webservices return "false"; } - [WebMethod] - public string SavePartialView(string filename, string oldName, string contents) - { - if (BasePage.ValidateUserContextID(BasePage.umbracoUserContextID)) - { - var folderPath = SystemDirectories.MvcViews + "/Partials/"; + //[WebMethod] + //public string SavePartialView(string filename, string oldName, string contents) + //{ + // if (BasePage.ValidateUserContextID(BasePage.umbracoUserContextID)) + // { + // var folderPath = SystemDirectories.MvcViews + "/Partials/"; - // validate file - IOHelper.ValidateEditPath(IOHelper.MapPath(folderPath + filename), folderPath); - // validate extension - IOHelper.ValidateFileExtension(IOHelper.MapPath(folderPath + filename), new[] {"cshtml"}.ToList()); + // // validate file + // IOHelper.ValidateEditPath(IOHelper.MapPath(folderPath + filename), folderPath); + // // validate extension + // IOHelper.ValidateFileExtension(IOHelper.MapPath(folderPath + filename), new[] {"cshtml"}.ToList()); - var val = contents; - string returnValue; - var saveOldPath = oldName.StartsWith("~/") ? IOHelper.MapPath(oldName) : IOHelper.MapPath(folderPath + oldName); - var savePath = filename.StartsWith("~/") ? IOHelper.MapPath(filename) : IOHelper.MapPath(folderPath + filename); + // var val = contents; + // string returnValue; + // var saveOldPath = oldName.StartsWith("~/") ? IOHelper.MapPath(oldName) : IOHelper.MapPath(folderPath + oldName); + // var savePath = filename.StartsWith("~/") ? IOHelper.MapPath(filename) : IOHelper.MapPath(folderPath + filename); - //Directory check.. only allow files in script dir and below to be edited - if (savePath.StartsWith(IOHelper.MapPath(folderPath))) - { - //deletes the old file - if (savePath != saveOldPath) - { - if (File.Exists(saveOldPath)) - File.Delete(saveOldPath); - } - using (var sw = File.CreateText(savePath)) - { - sw.Write(val); - } - returnValue = "true"; - } - else - { - returnValue = "illegalPath"; - } + // //Directory check.. only allow files in script dir and below to be edited + // if (savePath.StartsWith(IOHelper.MapPath(folderPath))) + // { + // //deletes the old file + // if (savePath != saveOldPath) + // { + // if (File.Exists(saveOldPath)) + // File.Delete(saveOldPath); + // } + // using (var sw = File.CreateText(savePath)) + // { + // sw.Write(val); + // } + // returnValue = "true"; + // } + // else + // { + // returnValue = "illegalPath"; + // } - return returnValue; - } - return "false"; - } + // return returnValue; + // } + // return "false"; + //} [WebMethod] public string SaveScript(string filename, string oldName, string contents) @@ -445,6 +445,7 @@ namespace umbraco.presentation.webservices return "false"; } + [Obsolete("This method has been superceded by the REST service /Umbraco/RestServices/SaveFile/SaveTemplate which is powered by the SafeFileController.")] [WebMethod] public string SaveTemplate(string templateName, string templateAlias, string templateContents, int templateID, int masterTemplateID) { diff --git a/src/packages/repositories.config b/src/packages/repositories.config index ea7ace2ef7..f811520cac 100644 --- a/src/packages/repositories.config +++ b/src/packages/repositories.config @@ -1,6 +1,7 @@  + diff --git a/src/umbraco.businesslogic/BasePages/BasePage.cs b/src/umbraco.businesslogic/BasePages/BasePage.cs index a74f61b355..c6655d76a6 100644 --- a/src/umbraco.businesslogic/BasePages/BasePage.cs +++ b/src/umbraco.businesslogic/BasePages/BasePage.cs @@ -2,6 +2,8 @@ using System; using System.Data; using System.Web; using System.Linq; +using System.Web.Mvc; +using System.Web.Routing; using System.Web.Security; using Umbraco.Core.Logging; using umbraco.BusinessLogic; @@ -70,6 +72,18 @@ namespace umbraco.BasePages } } + private UrlHelper _url; + /// + /// Returns a UrlHelper + /// + /// + /// This URL helper is created without any route data and an empty request context + /// + public UrlHelper Url + { + get { return _url ?? (_url = new UrlHelper(new RequestContext(new HttpContextWrapper(Context), new RouteData()))); } + } + /// /// Returns a refernce of an instance of ClientTools for access to the pages client API /// diff --git a/src/umbraco.businesslogic/packages.config b/src/umbraco.businesslogic/packages.config new file mode 100644 index 0000000000..8fb83abb39 --- /dev/null +++ b/src/umbraco.businesslogic/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/umbraco.businesslogic/umbraco.businesslogic.csproj b/src/umbraco.businesslogic/umbraco.businesslogic.csproj index 9e8b7b478c..e07ae3cc1d 100644 --- a/src/umbraco.businesslogic/umbraco.businesslogic.csproj +++ b/src/umbraco.businesslogic/umbraco.businesslogic.csproj @@ -53,6 +53,8 @@ false true + ..\ + true bin\Debug\ @@ -102,6 +104,9 @@ AllRules.ruleset + + True + @@ -116,6 +121,30 @@ 3.5 + + True + ..\packages\Microsoft.AspNet.WebPages.2.0.20710.0\lib\net40\System.Web.Helpers.dll + + + True + ..\packages\Microsoft.AspNet.Mvc.4.0.20710.0\lib\net40\System.Web.Mvc.dll + + + True + ..\packages\Microsoft.AspNet.Razor.2.0.20715.0\lib\net40\System.Web.Razor.dll + + + True + ..\packages\Microsoft.AspNet.WebPages.2.0.20710.0\lib\net40\System.Web.WebPages.dll + + + True + ..\packages\Microsoft.AspNet.WebPages.2.0.20710.0\lib\net40\System.Web.WebPages.Deployment.dll + + + True + ..\packages\Microsoft.AspNet.WebPages.2.0.20710.0\lib\net40\System.Web.WebPages.Razor.dll + System.XML @@ -241,6 +270,9 @@ + + + @@ -248,4 +280,5 @@ + \ No newline at end of file