Merge branch 'netcore/netcore' into netcore/feature/auto-routing

# Conflicts:
#	src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs
This commit is contained in:
Bjarke Berg
2020-05-15 14:10:03 +02:00
25 changed files with 1001 additions and 30 deletions

View File

@@ -1,7 +1,5 @@
using System.IO;
using Umbraco.Core.Cache;
using Umbraco.Core.Cache;
using Umbraco.Core.Hosting;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Manifest;
using Umbraco.Core.Serialization;
@@ -10,9 +8,9 @@ namespace Umbraco.Core.Configuration.Grid
{
public class GridConfig : IGridConfig
{
public GridConfig(AppCaches appCaches, IIOHelper ioHelper, IManifestParser manifestParser, IJsonSerializer jsonSerializer, IHostingEnvironment hostingEnvironment)
public GridConfig(AppCaches appCaches, IManifestParser manifestParser, IJsonSerializer jsonSerializer, IHostingEnvironment hostingEnvironment, ILogger logger)
{
EditorsConfig = new GridEditorsConfig(appCaches, ioHelper, manifestParser, jsonSerializer, hostingEnvironment.IsDebugMode);
EditorsConfig = new GridEditorsConfig(appCaches, hostingEnvironment, manifestParser, jsonSerializer, logger);
}
public IGridEditorsConfig EditorsConfig { get; }

View File

@@ -1,9 +1,8 @@
using System;
using System.Collections.Generic;
using System.IO;
using Umbraco.Composing;
using Umbraco.Core.Cache;
using Umbraco.Core.IO;
using Umbraco.Core.Hosting;
using Umbraco.Core.Logging;
using Umbraco.Core.Manifest;
using Umbraco.Core.PropertyEditors;
@@ -14,18 +13,19 @@ namespace Umbraco.Core.Configuration.Grid
internal class GridEditorsConfig : IGridEditorsConfig
{
private readonly AppCaches _appCaches;
private readonly IIOHelper _ioHelper;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IManifestParser _manifestParser;
private readonly bool _isDebug;
private readonly IJsonSerializer _jsonSerializer;
public GridEditorsConfig(AppCaches appCaches, IIOHelper ioHelper, IManifestParser manifestParser,IJsonSerializer jsonSerializer, bool isDebug)
private readonly IJsonSerializer _jsonSerializer;
private readonly ILogger _logger;
public GridEditorsConfig(AppCaches appCaches, IHostingEnvironment hostingEnvironment, IManifestParser manifestParser,IJsonSerializer jsonSerializer, ILogger logger)
{
_appCaches = appCaches;
_ioHelper = ioHelper;
_hostingEnvironment = hostingEnvironment;
_manifestParser = manifestParser;
_jsonSerializer = jsonSerializer;
_isDebug = isDebug;
_logger = logger;
}
public IEnumerable<IGridEditorConfig> Editors
@@ -34,7 +34,7 @@ namespace Umbraco.Core.Configuration.Grid
{
List<IGridEditorConfig> GetResult()
{
var configFolder = new DirectoryInfo(_ioHelper.MapPath(Constants.SystemDirectories.Config));
var configFolder = new DirectoryInfo(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Config));
var editors = new List<IGridEditorConfig>();
var gridConfig = Path.Combine(configFolder.FullName, "grid.editors.config.js");
if (File.Exists(gridConfig))
@@ -47,7 +47,7 @@ namespace Umbraco.Core.Configuration.Grid
}
catch (Exception ex)
{
Current.Logger.Error<GridEditorsConfig>(ex, "Could not parse the contents of grid.editors.config.js into a JSON array '{Json}", sourceString);
_logger.Error<GridEditorsConfig>(ex, "Could not parse the contents of grid.editors.config.js into a JSON array '{Json}", sourceString);
}
}
@@ -61,7 +61,7 @@ namespace Umbraco.Core.Configuration.Grid
}
//cache the result if debugging is disabled
var result = _isDebug
var result = _hostingEnvironment.IsDebugMode
? GetResult()
: _appCaches.RuntimeCache.GetCacheItem<List<IGridEditorConfig>>(typeof(GridEditorsConfig) + ".Editors",GetResult, TimeSpan.FromMinutes(10));

View File

@@ -16,7 +16,6 @@ using Umbraco.Net;
using Umbraco.Core.Persistence;
using Umbraco.Core.Serialization;
using Umbraco.Core.Strings;
using Umbraco.Core.Sync;
using Umbraco.Web;
using Umbraco.Web.Routing;

View File

@@ -30,6 +30,7 @@
<ProjectReference Include="..\Umbraco.Infrastructure\Umbraco.Infrastructure.csproj" />
<ProjectReference Include="..\Umbraco.Tests.Common\Umbraco.Tests.Common.csproj" />
<ProjectReference Include="..\Umbraco.Web.BackOffice\Umbraco.Web.BackOffice.csproj" />
<ProjectReference Include="..\Umbraco.Web.Website\Umbraco.Web.Website.csproj" />
</ItemGroup>
<ItemGroup>

View File

@@ -22,6 +22,7 @@
<ProjectReference Include="..\Umbraco.Tests.Common\Umbraco.Tests.Common.csproj" />
<ProjectReference Include="..\Umbraco.Web.BackOffice\Umbraco.Web.BackOffice.csproj" />
<ProjectReference Include="..\Umbraco.Web.Common\Umbraco.Web.Common.csproj" />
<ProjectReference Include="..\Umbraco.Web.Website\Umbraco.Web.Website.csproj" />
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,236 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Moq;
using NUnit.Framework;
using Umbraco.Core.Cache;
using Umbraco.Core.Hosting;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Services;
using Umbraco.Tests.Common;
using Umbraco.Tests.Common.Builders;
using Umbraco.Tests.Testing;
using Umbraco.Web;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.Routing;
using Umbraco.Web.Website;
using Umbraco.Web.Website.Controllers;
namespace Umbraco.Tests.Integration
{
[TestFixture]
[UmbracoTest(WithApplication = true)]
public class SurfaceControllerTests
{
private IUmbracoContextAccessor _umbracoContextAccessor;
[SetUp]
public void SetUp()
{
_umbracoContextAccessor = new TestUmbracoContextAccessor();
}
[Test]
public void Can_Construct_And_Get_Result()
{
var httpContextAccessor = Mock.Of<IHttpContextAccessor>();
var hostingEnvironment = Mock.Of<IHostingEnvironment>();
var globalSettings = new GlobalSettingsBuilder().Build();
var umbracoContextFactory = new UmbracoContextFactory(
_umbracoContextAccessor,
Mock.Of<IPublishedSnapshotService>(),
new TestVariationContextAccessor(),
new TestDefaultCultureAccessor(),
globalSettings,
Mock.Of<IUserService>(),
hostingEnvironment,
new UriUtility(hostingEnvironment),
httpContextAccessor,
Mock.Of<ICookieManager>(),
Mock.Of<IRequestAccessor>());
var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext();
var umbracoContext = umbracoContextReference.UmbracoContext;
var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext);
var ctrl = new TestSurfaceController(umbracoContextAccessor, Mock.Of<IPublishedContentQuery>(), Mock.Of<IPublishedUrlProvider>());
var result = ctrl.Index();
Assert.IsNotNull(result);
}
[Test]
public void Umbraco_Context_Not_Null()
{
var globalSettings = new GlobalSettingsBuilder().Build();
var httpContextAccessor = Mock.Of<IHttpContextAccessor>();
var hostingEnvironment = Mock.Of<IHostingEnvironment>();
var umbracoContextFactory = new UmbracoContextFactory(
_umbracoContextAccessor,
Mock.Of<IPublishedSnapshotService>(),
new TestVariationContextAccessor(),
new TestDefaultCultureAccessor(),
globalSettings,
Mock.Of<IUserService>(),
hostingEnvironment,
new UriUtility(hostingEnvironment),
httpContextAccessor,
Mock.Of<ICookieManager>(),
Mock.Of<IRequestAccessor>());
var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext();
var umbCtx = umbracoContextReference.UmbracoContext;
var umbracoContextAccessor = new TestUmbracoContextAccessor(umbCtx);
var ctrl = new TestSurfaceController(umbracoContextAccessor, Mock.Of<IPublishedContentQuery>(), Mock.Of<IPublishedUrlProvider>());
Assert.IsNotNull(ctrl.UmbracoContext);
}
[Test]
public void Can_Lookup_Content()
{
var publishedSnapshot = new Mock<IPublishedSnapshot>();
publishedSnapshot.Setup(x => x.Members).Returns(Mock.Of<IPublishedMemberCache>());
var content = new Mock<IPublishedContent>();
content.Setup(x => x.Id).Returns(2);
var publishedSnapshotService = new Mock<IPublishedSnapshotService>();
var httpContextAccessor = Mock.Of<IHttpContextAccessor>();
var hostingEnvironment = Mock.Of<IHostingEnvironment>();
var globalSettings = new GlobalSettingsBuilder().Build();
var umbracoContextFactory = new UmbracoContextFactory(
_umbracoContextAccessor,
publishedSnapshotService.Object,
new TestVariationContextAccessor(),
new TestDefaultCultureAccessor(),
globalSettings,
Mock.Of<IUserService>(),
hostingEnvironment,
new UriUtility(hostingEnvironment),
httpContextAccessor,
Mock.Of<ICookieManager>(),
Mock.Of<IRequestAccessor>());
var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext();
var umbracoContext = umbracoContextReference.UmbracoContext;
var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext);
var publishedContentQuery = Mock.Of<IPublishedContentQuery>(query => query.Content(2) == content.Object);
var ctrl = new TestSurfaceController(umbracoContextAccessor, publishedContentQuery, Mock.Of<IPublishedUrlProvider>());
var result = ctrl.GetContent(2) as PublishedContentResult;
Assert.IsNotNull(result);
Assert.IsNotNull(result.Content);
Assert.AreEqual(2, result.Content.Id);
}
[Test]
public void Mock_Current_Page()
{
var globalSettings = new GlobalSettingsBuilder().Build();
var httpContextAccessor = Mock.Of<IHttpContextAccessor>();
var hostingEnvironment = Mock.Of<IHostingEnvironment>();
var umbracoContextFactory = new UmbracoContextFactory(
_umbracoContextAccessor,
Mock.Of<IPublishedSnapshotService>(),
new TestVariationContextAccessor(),
new TestDefaultCultureAccessor(),
globalSettings,
Mock.Of<IUserService>(),
hostingEnvironment,
new UriUtility(hostingEnvironment),
httpContextAccessor,
Mock.Of<ICookieManager>(),
Mock.Of<IRequestAccessor>());
var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext();
var umbracoContext = umbracoContextReference.UmbracoContext;
var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext);
var content = Mock.Of<IPublishedContent>(publishedContent => publishedContent.Id == 12345);
var publishedRequestMock = new Mock<IPublishedRequest>();
publishedRequestMock.Setup(x => x.PublishedContent).Returns(content);
var routeDefinition = new RouteDefinition
{
PublishedRequest = publishedRequestMock.Object
};
var routeData = new RouteData();
routeData.DataTokens.Add(Core.Constants.Web.UmbracoRouteDefinitionDataToken, routeDefinition);
var ctrl = new TestSurfaceController(umbracoContextAccessor, Mock.Of<IPublishedContentQuery>(), Mock.Of<IPublishedUrlProvider>());
ctrl.ControllerContext = new ControllerContext()
{
HttpContext = Mock.Of<HttpContext>(),
RouteData = routeData
};
var result = ctrl.GetContentFromCurrentPage() as PublishedContentResult;
Assert.AreEqual(12345, result.Content.Id);
}
public class TestSurfaceController : SurfaceController
{
private readonly IPublishedContentQuery _publishedContentQuery;
public TestSurfaceController(IUmbracoContextAccessor umbracoContextAccessor, IPublishedContentQuery publishedContentQuery, IPublishedUrlProvider publishedUrlProvider)
: base(umbracoContextAccessor, null, ServiceContext.CreatePartial(), AppCaches.Disabled, null, null, publishedUrlProvider)
{
_publishedContentQuery = publishedContentQuery;
}
public IActionResult Index()
{
// ReSharper disable once Mvc.ViewNotResolved
return View();
}
public IActionResult GetContent(int id)
{
var content = _publishedContentQuery.Content(id);
return new PublishedContentResult(content);
}
public IActionResult GetContentFromCurrentPage()
{
var content = CurrentPage;
return new PublishedContentResult(content);
}
}
public class PublishedContentResult : IActionResult
{
public IPublishedContent Content { get; set; }
public PublishedContentResult(IPublishedContent content)
{
Content = content;
}
public Task ExecuteResultAsync(ActionContext context)
{
return Task.CompletedTask;
}
}
}
}

View File

@@ -0,0 +1,52 @@
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
namespace Umbraco.Web.BackOffice.ActionResults
{
/// <summary>
/// Custom json result using newtonsoft json.net
/// </summary>
public class JsonNetResult : IActionResult
{
public Encoding ContentEncoding { get; set; }
public string ContentType { get; set; }
public object Data { get; set; }
public JsonSerializerSettings SerializerSettings { get; set; }
public Formatting Formatting { get; set; }
public JsonNetResult()
{
SerializerSettings = new JsonSerializerSettings();
}
public Task ExecuteResultAsync(ActionContext context)
{
if (context is null)
throw new ArgumentNullException(nameof(context));
var response = context.HttpContext.Response;
response.ContentType = string.IsNullOrEmpty(ContentType) == false
? ContentType
: System.Net.Mime.MediaTypeNames.Application.Json;
if (!(ContentEncoding is null))
response.Headers.Add(Microsoft.Net.Http.Headers.HeaderNames.ContentEncoding, ContentEncoding.ToString());
if (!(Data is null))
{
using var bodyWriter = new StreamWriter(response.Body);
using var writer = new JsonTextWriter(bodyWriter) { Formatting = Formatting };
var serializer = JsonSerializer.Create(SerializerSettings);
serializer.Serialize(writer, Data);
}
return Task.CompletedTask;
}
}
}

View File

@@ -1,9 +1,18 @@
using System.Threading.Tasks;
using System;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.Grid;
using Umbraco.Core.Hosting;
using Umbraco.Core.Services;
using Umbraco.Core.WebAssets;
using Umbraco.Net;
using Umbraco.Web.BackOffice.ActionResults;
using Umbraco.Web.BackOffice.Filters;
using Umbraco.Web.Common.ActionResults;
using Umbraco.Web.WebAssets;
@@ -18,12 +27,20 @@ namespace Umbraco.Web.BackOffice.Controllers
private readonly IRuntimeMinifier _runtimeMinifier;
private readonly IGlobalSettings _globalSettings;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IUmbracoApplicationLifetime _umbracoApplicationLifetime;
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private readonly ILocalizedTextService _textService;
private readonly IGridConfig _gridConfig;
public BackOfficeController(IRuntimeMinifier runtimeMinifier, IGlobalSettings globalSettings, IHostingEnvironment hostingEnvironment)
public BackOfficeController(IRuntimeMinifier runtimeMinifier, IGlobalSettings globalSettings, IHostingEnvironment hostingEnvironment, IUmbracoApplicationLifetime umbracoApplicationLifetime, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService, IGridConfig gridConfig)
{
_runtimeMinifier = runtimeMinifier;
_globalSettings = globalSettings;
_hostingEnvironment = hostingEnvironment;
_umbracoApplicationLifetime = umbracoApplicationLifetime;
_umbracoContextAccessor = umbracoContextAccessor;
_textService = textService;
_gridConfig = gridConfig ?? throw new ArgumentNullException(nameof(gridConfig));
}
[HttpGet]
@@ -44,5 +61,53 @@ namespace Umbraco.Web.BackOffice.Controllers
return new JavaScriptResult(result);
}
/// <summary>
/// Get the json localized text for a given culture or the culture for the current user
/// </summary>
/// <param name="culture"></param>
/// <returns></returns>
[HttpGet]
public JsonNetResult LocalizedText(string culture = null)
{
var securityHelper = _umbracoContextAccessor.GetRequiredUmbracoContext().Security;
var isAuthenticated = securityHelper.IsAuthenticated();
var cultureInfo = string.IsNullOrWhiteSpace(culture)
//if the user is logged in, get their culture, otherwise default to 'en'
? isAuthenticated
//current culture is set at the very beginning of each request
? Thread.CurrentThread.CurrentCulture
: CultureInfo.GetCultureInfo(_globalSettings.DefaultUILanguage)
: CultureInfo.GetCultureInfo(culture);
var allValues = _textService.GetAllStoredValues(cultureInfo);
var pathedValues = allValues.Select(kv =>
{
var slashIndex = kv.Key.IndexOf('/');
var areaAlias = kv.Key.Substring(0, slashIndex);
var valueAlias = kv.Key.Substring(slashIndex + 1);
return new
{
areaAlias,
valueAlias,
value = kv.Value
};
});
var nestedDictionary = pathedValues
.GroupBy(pv => pv.areaAlias)
.ToDictionary(pv => pv.Key, pv =>
pv.ToDictionary(pve => pve.valueAlias, pve => pve.value));
return new JsonNetResult { Data = nestedDictionary, Formatting = Formatting.None };
}
//[UmbracoAuthorize(Order = 0)] TODO: Re-implement UmbracoAuthorizeAttribute
[HttpGet]
public JsonNetResult GetGridConfig()
{
return new JsonNetResult { Data = _gridConfig.EditorsConfig.Editors, Formatting = Formatting.None };
}
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Text;
using Umbraco.Composing;
using Umbraco.Core;
using Umbraco.Core.Models.Membership;
using Umbraco.Web.Security;
@@ -11,46 +12,46 @@ namespace Umbraco.Web.Common.Security
public class WebSecurity : IWebSecurity
{
public IUser CurrentUser => throw new NotImplementedException();
public IUser CurrentUser => new User(Current.Configs.Global());
public ValidateRequestAttempt AuthorizeRequest(bool throwExceptions = false)
{
throw new NotImplementedException();
return ValidateRequestAttempt.Success;
}
public void ClearCurrentLogin()
{
throw new NotImplementedException();
//throw new NotImplementedException();
}
public Attempt<int> GetUserId()
{
throw new NotImplementedException();
return Attempt.Succeed(-1);
}
public bool IsAuthenticated()
{
throw new NotImplementedException();
return true;
}
public double PerformLogin(int userId)
{
throw new NotImplementedException();
return 100;
}
public bool UserHasSectionAccess(string section, IUser user)
{
throw new NotImplementedException();
return true;
}
public bool ValidateCurrentUser()
{
throw new NotImplementedException();
return true;
}
public ValidateRequestAttempt ValidateCurrentUser(bool throwExceptions, bool requiresApproval = true)
{
throw new NotImplementedException();
return ValidateRequestAttempt.Success;
}
}
}

View File

@@ -1,6 +1,7 @@
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@@ -49,6 +50,13 @@ namespace Umbraco.Web.UI.BackOffice
{
options.ShouldProfile = request => false; // WebProfiler determine and start profiling. We should not use the MiniProfilerMiddleware to also profile
});
// If using Kestrel: https://stackoverflow.com/a/55196057
services.Configure<KestrelServerOptions>(options =>
{
options.AllowSynchronousIO = true;
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.

View File

@@ -63,6 +63,7 @@
</ItemGroup>
<ItemGroup>
<Content Include="Config\grid.editors.config.js" />
<Content Include="Config\logviewer.searches.config.js" />
</ItemGroup>

View File

@@ -142,7 +142,6 @@
<DesignTimeSharedInput>True</DesignTimeSharedInput>
<DependentUpon>Settings.settings</DependentUpon>
</Compile>
<Content Include="Config\grid.editors.config.js" />
<Content Include="Config\Lang\cs-CZ.user.xml" />
<Content Include="Config\Lang\da-DK.user.xml" />
<Content Include="Config\Lang\de-DE.user.xml" />

View File

@@ -0,0 +1,178 @@
using System;
using System.Collections.Specialized;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Core;
using Umbraco.Core.IO;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Web.Routing;
namespace Umbraco.Web.Website.ActionResults
{
/// <summary>
/// Redirects to an Umbraco page by Id or Entity
/// </summary>
public class RedirectToUmbracoPageResult : IActionResult
{
private IPublishedContent _publishedContent;
private readonly int _pageId;
private readonly NameValueCollection _queryStringValues;
private readonly IPublishedUrlProvider _publishedUrlProvider;
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private string _url;
private string Url
{
get
{
if (!string.IsNullOrWhiteSpace(_url)) return _url;
if (PublishedContent is null)
throw new InvalidOperationException($"Cannot redirect, no entity was found for id {PageId}");
var result = _publishedUrlProvider.GetUrl(PublishedContent.Id);
if (result == "#")
throw new InvalidOperationException(
$"Could not route to entity with id {PageId}, the NiceUrlProvider could not generate a URL");
_url = result;
return _url;
}
}
private int PageId => _pageId;
private IPublishedContent PublishedContent
{
get
{
if (!(_publishedContent is null)) return _publishedContent;
//need to get the URL for the page
_publishedContent = _umbracoContextAccessor.GetRequiredUmbracoContext().Content.GetById(_pageId);
return _publishedContent;
}
}
/// <summary>
/// Creates a new RedirectToUmbracoResult
/// </summary>
/// <param name="pageId"></param>
/// <param name="publishedUrlProvider"></param>
public RedirectToUmbracoPageResult(int pageId, IPublishedUrlProvider publishedUrlProvider, IUmbracoContextAccessor umbracoContextAccessor)
{
_pageId = pageId;
_publishedUrlProvider = publishedUrlProvider;
_umbracoContextAccessor = umbracoContextAccessor;
}
/// <summary>
/// Creates a new RedirectToUmbracoResult
/// </summary>
/// <param name="pageId"></param>
/// <param name="queryStringValues"></param>
/// <param name="publishedUrlProvider"></param>
public RedirectToUmbracoPageResult(int pageId, NameValueCollection queryStringValues, IPublishedUrlProvider publishedUrlProvider, IUmbracoContextAccessor umbracoContextAccessor)
{
_pageId = pageId;
_queryStringValues = queryStringValues;
_publishedUrlProvider = publishedUrlProvider;
_umbracoContextAccessor = umbracoContextAccessor;
}
/// <summary>
/// Creates a new RedirectToUmbracoResult
/// </summary>
/// <param name="pageId"></param>
/// <param name="queryString"></param>
/// <param name="publishedUrlProvider"></param>
public RedirectToUmbracoPageResult(int pageId, string queryString, IPublishedUrlProvider publishedUrlProvider, IUmbracoContextAccessor umbracoContextAccessor)
{
_pageId = pageId;
_queryStringValues = ParseQueryString(queryString);
_publishedUrlProvider = publishedUrlProvider;
_umbracoContextAccessor = umbracoContextAccessor;
}
/// <summary>
/// Creates a new RedirectToUmbracoResult
/// </summary>
/// <param name="publishedContent"></param>
/// <param name="publishedUrlProvider"></param>
/// <param name="umbracoContextAccessor"></param>
public RedirectToUmbracoPageResult(IPublishedContent publishedContent, IPublishedUrlProvider publishedUrlProvider, IUmbracoContextAccessor umbracoContextAccessor)
{
_publishedContent = publishedContent;
_pageId = publishedContent.Id;
_publishedUrlProvider = publishedUrlProvider;
_umbracoContextAccessor = umbracoContextAccessor;
}
/// <summary>
/// Creates a new RedirectToUmbracoResult
/// </summary>
/// <param name="publishedContent"></param>
/// <param name="queryStringValues"></param>
/// <param name="publishedUrlProvider"></param>
/// <param name="umbracoContextAccessor"></param>
public RedirectToUmbracoPageResult(IPublishedContent publishedContent, NameValueCollection queryStringValues, IPublishedUrlProvider publishedUrlProvider, IUmbracoContextAccessor umbracoContextAccessor)
{
_publishedContent = publishedContent;
_pageId = publishedContent.Id;
_queryStringValues = queryStringValues;
_publishedUrlProvider = publishedUrlProvider;
_umbracoContextAccessor = umbracoContextAccessor;
}
/// <summary>
/// Creates a new RedirectToUmbracoResult
/// </summary>
/// <param name="publishedContent"></param>
/// <param name="queryString"></param>
/// <param name="publishedUrlProvider"></param>
/// <param name="umbracoContextAccessor"></param>
public RedirectToUmbracoPageResult(IPublishedContent publishedContent, string queryString, IPublishedUrlProvider publishedUrlProvider, IUmbracoContextAccessor umbracoContextAccessor)
{
_publishedContent = publishedContent;
_pageId = publishedContent.Id;
_queryStringValues = ParseQueryString(queryString);
_publishedUrlProvider = publishedUrlProvider;
_umbracoContextAccessor = umbracoContextAccessor;
}
public Task ExecuteResultAsync(ActionContext context)
{
if (context is null) throw new ArgumentNullException(nameof(context));
var httpContext = context.HttpContext;
var ioHelper = httpContext.RequestServices.GetRequiredService<IIOHelper>();
var destinationUrl = ioHelper.ResolveUrl(Url);
if (!(_queryStringValues is null) && _queryStringValues.Count > 0)
{
destinationUrl += "?" + string.Join("&",
_queryStringValues.AllKeys.Select(x => x + "=" + HttpUtility.UrlEncode(_queryStringValues[x])));
}
var tempDataDictionaryFactory = context.HttpContext.RequestServices.GetRequiredService<ITempDataDictionaryFactory>();
var tempData = tempDataDictionaryFactory.GetTempData(context.HttpContext);
tempData?.Keep();
httpContext.Response.Redirect(destinationUrl);
return Task.CompletedTask;
}
private NameValueCollection ParseQueryString(string queryString)
{
return !string.IsNullOrEmpty(queryString) ? HttpUtility.ParseQueryString(queryString) : null;
}
}
}

View File

@@ -0,0 +1,44 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.Extensions.DependencyInjection;
namespace Umbraco.Web.Website.ActionResults
{
/// <summary>
/// Redirects to the current URL rendering an Umbraco page including it's query strings
/// </summary>
/// <remarks>
/// This is useful if you need to redirect
/// to the current page but the current page is actually a rewritten URL normally done with something like
/// Server.Transfer. It is also handy if you want to persist the query strings.
/// </remarks>
public class RedirectToUmbracoUrlResult : IActionResult
{
private readonly IUmbracoContext _umbracoContext;
/// <summary>
/// Creates a new RedirectToUmbracoResult
/// </summary>
/// <param name="umbracoContext"></param>
public RedirectToUmbracoUrlResult(IUmbracoContext umbracoContext)
{
_umbracoContext = umbracoContext;
}
public Task ExecuteResultAsync(ActionContext context)
{
if (context is null) throw new ArgumentNullException(nameof(context));
var destinationUrl = _umbracoContext.OriginalRequestUrl.PathAndQuery;
var tempDataDictionaryFactory = context.HttpContext.RequestServices.GetRequiredService<ITempDataDictionaryFactory>();
var tempData = tempDataDictionaryFactory.GetTempData(context.HttpContext);
tempData?.Keep();
context.HttpContext.Response.Redirect(destinationUrl);
return Task.CompletedTask;
}
}
}

View File

@@ -0,0 +1,169 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Extensions;
namespace Umbraco.Web.Website.ActionResults
{
/// <summary>
/// Used by posted forms to proxy the result to the page in which the current URL matches on
/// </summary>
public class UmbracoPageResult : IActionResult
{
private readonly IProfilingLogger _profilingLogger;
public UmbracoPageResult(IProfilingLogger profilingLogger)
{
_profilingLogger = profilingLogger;
}
public Task ExecuteResultAsync(ActionContext context)
{
var routeData = context.RouteData;
ResetRouteData(routeData);
ValidateRouteData(routeData);
var factory = context.HttpContext.RequestServices.GetRequiredService<IControllerFactory>();
Controller controller = null;
if (!(context is ControllerContext controllerContext))
return Task.FromCanceled(new System.Threading.CancellationToken());
try
{
controller = CreateController(controllerContext, factory);
CopyControllerData(controllerContext, controller);
ExecuteControllerAction(controllerContext, controller);
}
finally
{
CleanupController(controllerContext, controller, factory);
}
return Task.CompletedTask;
}
/// <summary>
/// Executes the controller action
/// </summary>
private void ExecuteControllerAction(ControllerContext context, Controller controller)
{
using (_profilingLogger.TraceDuration<UmbracoPageResult>("Executing Umbraco RouteDefinition controller", "Finished"))
{
//TODO I do not think this will work, We need to test this, when we can, in the .NET Core executable.
var aec = new ActionExecutingContext(context, new List<IFilterMetadata>(), new Dictionary<string, object>(), controller);
var actionExecutedDelegate = CreateActionExecutedDelegate(aec);
controller.OnActionExecutionAsync(aec, actionExecutedDelegate);
}
}
/// <summary>
/// Creates action execution delegate from ActionExecutingContext
/// </summary>
private static ActionExecutionDelegate CreateActionExecutedDelegate(ActionExecutingContext context)
{
var actionExecutedContext = new ActionExecutedContext(context, context.Filters, context.Controller)
{
Result = context.Result,
};
return () => Task.FromResult(actionExecutedContext);
}
/// <summary>
/// Since we could be returning the current page from a surface controller posted values in which the routing values are changed, we
/// need to revert these values back to nothing in order for the normal page to render again.
/// </summary>
private static void ResetRouteData(RouteData routeData)
{
routeData.DataTokens["area"] = null;
routeData.DataTokens["Namespaces"] = null;
}
/// <summary>
/// Validate that the current page execution is not being handled by the normal umbraco routing system
/// </summary>
private static void ValidateRouteData(RouteData routeData)
{
if (routeData.DataTokens.ContainsKey(Constants.Web.UmbracoRouteDefinitionDataToken) == false)
{
throw new InvalidOperationException("Can only use " + typeof(UmbracoPageResult).Name +
" in the context of an Http POST when using a SurfaceController form");
}
}
/// <summary>
/// Ensure ModelState, ViewData and TempData is copied across
/// </summary>
private static void CopyControllerData(ControllerContext context, Controller controller)
{
controller.ViewData.ModelState.Merge(context.ModelState);
foreach (var d in controller.ViewData)
controller.ViewData[d.Key] = d.Value;
// We cannot simply merge the temp data because during controller execution it will attempt to 'load' temp data
// but since it has not been saved, there will be nothing to load and it will revert to nothing, so the trick is
// to Save the state of the temp data first then it will automatically be picked up.
// http://issues.umbraco.org/issue/U4-1339
var targetController = controller;
var tempDataDictionaryFactory = context.HttpContext.RequestServices.GetRequiredService<ITempDataDictionaryFactory>();
var tempData = tempDataDictionaryFactory.GetTempData(context.HttpContext);
targetController.TempData = tempData;
targetController.TempData.Save();
}
/// <summary>
/// Creates a controller using the controller factory
/// </summary>
private static Controller CreateController(ControllerContext context, IControllerFactory factory)
{
if (!(factory.CreateController(context) is Controller controller))
throw new InvalidOperationException("Could not create controller with name " + context.ActionDescriptor.ControllerName + ".");
return controller;
}
/// <summary>
/// Cleans up the controller by releasing it using the controller factory, and by disposing it.
/// </summary>
private static void CleanupController(ControllerContext context, Controller controller, IControllerFactory factory)
{
if (!(controller is null))
factory.ReleaseController(context, controller);
controller?.DisposeIfDisposable();
}
private class DummyView : IView
{
public DummyView(string path)
{
Path = path;
}
public Task RenderAsync(ViewContext context)
{
return Task.CompletedTask;
}
public string Path { get; }
}
}
}

View File

@@ -0,0 +1,181 @@
using System;
using System.Collections.Specialized;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Persistence;
using Umbraco.Core.Services;
using Umbraco.Web.Common.Controllers;
using Umbraco.Web.Routing;
using Umbraco.Web.Website.ActionResults;
namespace Umbraco.Web.Website.Controllers
{
/// <summary>
/// Provides a base class for front-end add-in controllers.
/// </summary>
// TODO: Migrate MergeModelStateToChildAction and MergeParentContextViewData action filters
// [MergeModelStateToChildAction]
// [MergeParentContextViewData]
public abstract class SurfaceController : PluginController
{
private readonly IPublishedUrlProvider _publishedUrlProvider;
/// <summary>
/// Gets the current page.
/// </summary>
protected virtual IPublishedContent CurrentPage
{
get
{
var routeDefAttempt = TryGetRouteDefinitionFromAncestorViewContexts();
if (routeDefAttempt.Success == false)
throw routeDefAttempt.Exception;
var routeDef = routeDefAttempt.Result;
return routeDef.PublishedRequest.PublishedContent;
}
}
protected SurfaceController(IUmbracoContextAccessor umbracoContextAccessor, IUmbracoDatabaseFactory databaseFactory, ServiceContext services, AppCaches appCaches, ILogger logger, IProfilingLogger profilingLogger, IPublishedUrlProvider publishedUrlProvider)
: base(umbracoContextAccessor, databaseFactory, services, appCaches, logger, profilingLogger)
{
_publishedUrlProvider = publishedUrlProvider;
}
/// <summary>
/// Redirects to the Umbraco page with the given id
/// </summary>
/// <param name="pageId"></param>
/// <returns></returns>
protected RedirectToUmbracoPageResult RedirectToUmbracoPage(int pageId)
{
return new RedirectToUmbracoPageResult(pageId, _publishedUrlProvider, UmbracoContextAccessor);
}
/// <summary>
/// Redirects to the Umbraco page with the given id and passes provided querystring
/// </summary>
/// <param name="pageId"></param>
/// <param name="queryStringValues"></param>
/// <returns></returns>
protected RedirectToUmbracoPageResult RedirectToUmbracoPage(int pageId, NameValueCollection queryStringValues)
{
return new RedirectToUmbracoPageResult(pageId, queryStringValues, _publishedUrlProvider, UmbracoContextAccessor);
}
/// <summary>
/// Redirects to the Umbraco page with the given id and passes provided querystring
/// </summary>
/// <param name="pageId"></param>
/// <param name="queryString"></param>
/// <returns></returns>
protected RedirectToUmbracoPageResult RedirectToUmbracoPage(int pageId, string queryString)
{
return new RedirectToUmbracoPageResult(pageId, queryString, _publishedUrlProvider, UmbracoContextAccessor);
}
/// <summary>
/// Redirects to the Umbraco page with the given published content
/// </summary>
/// <param name="publishedContent"></param>
/// <returns></returns>
protected RedirectToUmbracoPageResult RedirectToUmbracoPage(IPublishedContent publishedContent)
{
return new RedirectToUmbracoPageResult(publishedContent, _publishedUrlProvider, UmbracoContextAccessor);
}
/// <summary>
/// Redirects to the Umbraco page with the given published content and passes provided querystring
/// </summary>
/// <param name="publishedContent"></param>
/// <param name="queryStringValues"></param>
/// <returns></returns>
protected RedirectToUmbracoPageResult RedirectToUmbracoPage(IPublishedContent publishedContent, NameValueCollection queryStringValues)
{
return new RedirectToUmbracoPageResult(publishedContent, queryStringValues, _publishedUrlProvider, UmbracoContextAccessor);
}
/// <summary>
/// Redirects to the Umbraco page with the given published content and passes provided querystring
/// </summary>
/// <param name="publishedContent"></param>
/// <param name="queryString"></param>
/// <returns></returns>
protected RedirectToUmbracoPageResult RedirectToUmbracoPage(IPublishedContent publishedContent, string queryString)
{
return new RedirectToUmbracoPageResult(publishedContent, queryString, _publishedUrlProvider, UmbracoContextAccessor);
}
/// <summary>
/// Redirects to the currently rendered Umbraco page
/// </summary>
/// <returns></returns>
protected RedirectToUmbracoPageResult RedirectToCurrentUmbracoPage()
{
return new RedirectToUmbracoPageResult(CurrentPage, _publishedUrlProvider, UmbracoContextAccessor);
}
/// <summary>
/// Redirects to the currently rendered Umbraco page and passes provided querystring
/// </summary>
/// <param name="queryStringValues"></param>
/// <returns></returns>
protected RedirectToUmbracoPageResult RedirectToCurrentUmbracoPage(NameValueCollection queryStringValues)
{
return new RedirectToUmbracoPageResult(CurrentPage, queryStringValues, _publishedUrlProvider, UmbracoContextAccessor);
}
/// <summary>
/// Redirects to the currently rendered Umbraco page and passes provided querystring
/// </summary>
/// <param name="queryString"></param>
/// <returns></returns>
protected RedirectToUmbracoPageResult RedirectToCurrentUmbracoPage(string queryString)
{
return new RedirectToUmbracoPageResult(CurrentPage, queryString, _publishedUrlProvider, UmbracoContextAccessor);
}
/// <summary>
/// Redirects to the currently rendered Umbraco URL
/// </summary>
/// <returns></returns>
/// <remarks>
/// This is useful if you need to redirect
/// to the current page but the current page is actually a rewritten URL normally done with something like
/// Server.Transfer.*
/// </remarks>
protected RedirectToUmbracoUrlResult RedirectToCurrentUmbracoUrl()
{
return new RedirectToUmbracoUrlResult(UmbracoContext);
}
/// <summary>
/// Returns the currently rendered Umbraco page
/// </summary>
/// <returns></returns>
protected UmbracoPageResult CurrentUmbracoPage()
{
return new UmbracoPageResult(ProfilingLogger);
}
/// <summary>
/// we need to recursively find the route definition based on the parent view context
/// </summary>
/// <returns></returns>
private Attempt<RouteDefinition> TryGetRouteDefinitionFromAncestorViewContexts()
{
var currentContext = ControllerContext;
while (!(currentContext is null))
{
var currentRouteData = currentContext.RouteData;
if (currentRouteData.DataTokens.ContainsKey(Core.Constants.Web.UmbracoRouteDefinitionDataToken))
return Attempt.Succeed((RouteDefinition)currentRouteData.DataTokens[Core.Constants.Web.UmbracoRouteDefinitionDataToken]);
}
return Attempt<RouteDefinition>.Fail(
new InvalidOperationException("Cannot find the Umbraco route definition in the route values, the request must be made in the context of an Umbraco request"));
}
}
}

View File

@@ -0,0 +1,29 @@
using System;
using Umbraco.Web.Routing;
namespace Umbraco.Web.Website
{
/// <summary>
/// Represents the data required to route to a specific controller/action during an Umbraco request
/// </summary>
public class RouteDefinition
{
public string ControllerName { get; set; }
public string ActionName { get; set; }
/// <summary>
/// The Controller type found for routing to
/// </summary>
public Type ControllerType { get; set; }
/// <summary>
/// Everything related to the current content request including the requested content
/// </summary>
public IPublishedRequest PublishedRequest { get; set; }
/// <summary>
/// Gets/sets whether the current request has a hijacked route/user controller routed for it
/// </summary>
public bool HasHijackedRoute { get; set; }
}
}

View File

@@ -194,6 +194,7 @@ namespace Umbraco.Web.Editors
/// </summary>
/// <param name="culture"></param>
/// <returns></returns>
/// <remarks>Migrated already to .Net Core</remarks>
[HttpGet]
public JsonNetResult LocalizedText(string culture = null)
{
@@ -239,7 +240,8 @@ namespace Umbraco.Web.Editors
return JavaScript(result);
}
/// Migrated already to .Net Core
[UmbracoAuthorize(Order = 0)]
[HttpGet]
public JsonNetResult GetGridConfig()

View File

@@ -13,6 +13,7 @@ namespace Umbraco.Web.Mvc
/// <summary>
/// Custom json result using newtonsoft json.net
/// </summary>
/// Migrated already to .Net Core
public class JsonNetResult : ActionResult
{
public Encoding ContentEncoding { get; set; }

View File

@@ -14,6 +14,7 @@ namespace Umbraco.Web.Mvc
/// <summary>
/// Redirects to an Umbraco page by Id or Entity
/// </summary>
/// Migrated already to .Net Core
public class RedirectToUmbracoPageResult : ActionResult
{
private IPublishedContent _publishedContent;

View File

@@ -11,6 +11,7 @@ namespace Umbraco.Web.Mvc
/// to the current page but the current page is actually a rewritten URL normally done with something like
/// Server.Transfer. It is also handy if you want to persist the query strings.
/// </remarks>
/// Migrated already to .Net Core
public class RedirectToUmbracoUrlResult : ActionResult
{
private readonly IUmbracoContext _umbracoContext;

View File

@@ -7,6 +7,7 @@ namespace Umbraco.Web.Mvc
/// <summary>
/// Represents the data required to route to a specific controller/action during an Umbraco request
/// </summary>
/// Migrated already to .Net Core
public class RouteDefinition
{
public string ControllerName { get; set; }

View File

@@ -13,6 +13,8 @@ namespace Umbraco.Web.Mvc
/// <summary>
/// Provides a base class for front-end add-in controllers.
/// </summary>
/// Migrated already to .Net Core without MergeModelStateToChildAction and MergeParentContextViewData action filters
/// TODO: Migrate MergeModelStateToChildAction and MergeParentContextViewData action filters
[MergeModelStateToChildAction]
[MergeParentContextViewData]
public abstract class SurfaceController : PluginController

View File

@@ -10,6 +10,7 @@ namespace Umbraco.Web.Mvc
/// <summary>
/// Used by posted forms to proxy the result to the page in which the current URL matches on
/// </summary>
/// Migrated already to .Net Core
public class UmbracoPageResult : ActionResult
{
private readonly IProfilingLogger _profilingLogger;