From 67675ee46dcad14dab7d45ff1a303d8cc0da4eb6 Mon Sep 17 00:00:00 2001 From: Tobias Klika Date: Mon, 26 Oct 2020 13:59:46 +0100 Subject: [PATCH] first version of route resolving works :-) --- zero.Core/Api/ApplicationContext.cs | 38 +++-- zero.Core/Api/PageTreeApi.cs | 1 + .../ApplicationContextMiddleware.cs | 16 +- zero.Core/Routing/AbtractRouteProvider.cs | 36 ++++- ...IPageUrlResolver.cs => IPageUrlBuilder.cs} | 2 +- .../Routing/{legacy => }/IResolvedRoute.cs | 0 zero.Core/Routing/IRouteProvider.cs | 15 +- zero.Core/Routing/PageRouteProvider.cs | 90 ++++++++--- .../PageUrlResolver.cs => PageUrlBuilder.cs} | 6 +- zero.Core/Routing/PageUrlProvider-super.cs | 93 ------------ zero.Core/Routing/RouteProviderEndpoint.cs | 9 ++ zero.Core/Routing/Routes.cs | 120 ++++++++++++++- zero.Core/Routing/legacy/IUrlProvider.cs | 16 -- .../Routing/legacy/PageUrlProvider-test.cs | 140 ------------------ zero.Core/Routing/legacy/PageUrlProvider.cs | 129 ---------------- zero.Core/Routing/legacy/UrlResult.cs | 108 -------------- .../Controllers/DefaultRouteController.cs | 18 +++ zero.Web/Defaults/ZeroBackofficePlugin.cs | 10 +- zero.Web/Routing/ZeroRoutesTransformer.cs | 37 +++++ zero.Web/ZeroApplicationBuilderExtensions.cs | 9 ++ 20 files changed, 343 insertions(+), 550 deletions(-) rename zero.Core/Routing/{legacy/IPageUrlResolver.cs => IPageUrlBuilder.cs} (92%) rename zero.Core/Routing/{legacy => }/IResolvedRoute.cs (100%) rename zero.Core/Routing/{legacy/PageUrlResolver.cs => PageUrlBuilder.cs} (94%) delete mode 100644 zero.Core/Routing/PageUrlProvider-super.cs create mode 100644 zero.Core/Routing/RouteProviderEndpoint.cs delete mode 100644 zero.Core/Routing/legacy/IUrlProvider.cs delete mode 100644 zero.Core/Routing/legacy/PageUrlProvider-test.cs delete mode 100644 zero.Core/Routing/legacy/PageUrlProvider.cs delete mode 100644 zero.Core/Routing/legacy/UrlResult.cs create mode 100644 zero.Web/Controllers/DefaultRouteController.cs create mode 100644 zero.Web/Routing/ZeroRoutesTransformer.cs diff --git a/zero.Core/Api/ApplicationContext.cs b/zero.Core/Api/ApplicationContext.cs index e561ead7..f8d19f65 100644 --- a/zero.Core/Api/ApplicationContext.cs +++ b/zero.Core/Api/ApplicationContext.cs @@ -29,6 +29,8 @@ namespace zero.Core.Api protected UserManager UserManager { get; private set; } + static IList Apps { get; set; } + public ApplicationContext(IDocumentStore raven, IZeroOptions options, UserManager userManager) @@ -172,16 +174,24 @@ namespace zero.Core.Api } + /// + public bool IsBackofficeRequest(HttpContext context) + { + string path = Options.BackofficePath.EnsureStartsWith('/').TrimEnd('/'); + return context.Request.Path.ToString().StartsWith(path); + } + + /// /// Get matching application from an URI /// - IApplication ResolveFromUriInternal(Uri uri, IList apps) + IApplication ResolveFromUriInternal(Uri uri, IList apps) { string[] protocols = new string[3] { "https://", "http://", "//" }; IApplication currentApp = null; - foreach (Application app in apps) + foreach (IApplication app in apps) { if (app.Domains?.Length < 1) { @@ -221,11 +231,16 @@ namespace zero.Core.Api /// /// Get all applications to choose from /// - async Task> GetApplications() + async Task> GetApplications() { + if (Apps != null) + { + return Apps; + } using (IAsyncDocumentSession session = Raven.OpenAsyncSession()) { - return await session.Query().ToListAsync(); + Apps = await session.Query().ToListAsync(); + return Apps; } } @@ -243,16 +258,6 @@ namespace zero.Core.Api return appIds; } - - - /// - /// Whether the current request is a backoffice request - /// - bool IsBackofficeRequest(HttpContext context) - { - string path = Options.BackofficePath.EnsureStartsWith('/').TrimEnd('/'); - return context.Request.Path.ToString().StartsWith(path); - } } @@ -312,5 +317,10 @@ namespace zero.Core.Api /// Creates a new application context for the specified application. /// Task ForId(string appId); + + /// + /// Whether the current request is a backoffice request + /// + bool IsBackofficeRequest(HttpContext context); } } diff --git a/zero.Core/Api/PageTreeApi.cs b/zero.Core/Api/PageTreeApi.cs index 8e8ef0cc..0b130e45 100644 --- a/zero.Core/Api/PageTreeApi.cs +++ b/zero.Core/Api/PageTreeApi.cs @@ -30,6 +30,7 @@ namespace zero.Core.Api .Query() .Scope(Scope) .WhereIf(x => x.ParentId == parentId, !parentId.IsNullOrEmpty(), x => x.ParentId == null) + .OrderBy(x => x.Sort) .ToListAsync(); diff --git a/zero.Core/Middlewares/ApplicationContextMiddleware.cs b/zero.Core/Middlewares/ApplicationContextMiddleware.cs index 5c5e4da9..8720f3fe 100644 --- a/zero.Core/Middlewares/ApplicationContextMiddleware.cs +++ b/zero.Core/Middlewares/ApplicationContextMiddleware.cs @@ -1,6 +1,9 @@ using Microsoft.AspNetCore.Http; +using Newtonsoft.Json; using System.Threading.Tasks; using zero.Core.Api; +using zero.Core.Entities; +using zero.Core.Routing; namespace zero.Core.Middlewares { @@ -15,9 +18,18 @@ namespace zero.Core.Middlewares } - public async Task Invoke(HttpContext httpContext, IApplicationContext appContext) + public async Task Invoke(HttpContext httpContext, IApplicationContext appContext, IRoutes routes) { - await appContext.Resolve(httpContext); + IApplication app = await appContext.Resolve(httpContext); + + //if (!appContext.IsBackofficeRequest(httpContext)) + //{ + // IResolvedRoute route = await routes.ResolveUrl(app.Id, httpContext.Request.Path); + // httpContext.Response.ContentType = "application/json"; + // await httpContext.Response.WriteAsync(JsonConvert.SerializeObject(route)); + // return; + //} + await Next(httpContext); } } diff --git a/zero.Core/Routing/AbtractRouteProvider.cs b/zero.Core/Routing/AbtractRouteProvider.cs index 17c04fde..d80d6d90 100644 --- a/zero.Core/Routing/AbtractRouteProvider.cs +++ b/zero.Core/Routing/AbtractRouteProvider.cs @@ -1,29 +1,43 @@ using Raven.Client.Documents.Session; using System; +using System.Collections.Generic; using System.Threading.Tasks; namespace zero.Core.Routing { - public abstract class AbtractRouteProvider : IRouteProvider, IRouteProvider + public abstract class AbtractRouteProvider : IRouteProvider { public virtual string Alias { get; } public virtual Type[] AffectedTypes { get; } + public virtual string Controller { get; } + + public virtual string Action { get; } + + public const string ID_PREFIX = "routes."; + + public const char SLASH = '/'; + public AbtractRouteProvider(string alias) { Alias = alias; AffectedTypes = new Type[1] { typeof(T) }; + Controller = "DefaultRoute"; + Action = "Index"; } /// - public abstract Task GetRoute(IAsyncDocumentSession session, T model); + public virtual async Task GetRoute(IAsyncDocumentSession session, T model) + { + return await session.LoadAsync(GetRouteId(model)); + } /// - public async Task GetRoute(IAsyncDocumentSession session, object model) + public virtual async Task GetRoute(IAsyncDocumentSession session, object model) { if (!(model is T)) { @@ -42,16 +56,22 @@ namespace zero.Core.Routing /// - public string GetRouteId(T model) - { - throw new NotImplementedException(); - } + public abstract Task> GetAllRoutes(IAsyncDocumentSession session); + + + /// + public abstract string GetRouteId(T model); /// public string GetRouteId(object model) { - throw new NotImplementedException(); + if (!(model is T)) + { + throw new ArgumentException($"Parameter has to be of type {typeof(T)}", nameof(model)); + } + + return GetRouteId((T)model); } } } diff --git a/zero.Core/Routing/legacy/IPageUrlResolver.cs b/zero.Core/Routing/IPageUrlBuilder.cs similarity index 92% rename from zero.Core/Routing/legacy/IPageUrlResolver.cs rename to zero.Core/Routing/IPageUrlBuilder.cs index b702ca42..ac6afbee 100644 --- a/zero.Core/Routing/legacy/IPageUrlResolver.cs +++ b/zero.Core/Routing/IPageUrlBuilder.cs @@ -8,7 +8,7 @@ using zero.Core.Entities; namespace zero.Core.Routing { - public interface IPageUrlResolver + public interface IPageUrlBuilder { /// /// Get URL for a page diff --git a/zero.Core/Routing/legacy/IResolvedRoute.cs b/zero.Core/Routing/IResolvedRoute.cs similarity index 100% rename from zero.Core/Routing/legacy/IResolvedRoute.cs rename to zero.Core/Routing/IResolvedRoute.cs diff --git a/zero.Core/Routing/IRouteProvider.cs b/zero.Core/Routing/IRouteProvider.cs index 9b877551..2c2477f9 100644 --- a/zero.Core/Routing/IRouteProvider.cs +++ b/zero.Core/Routing/IRouteProvider.cs @@ -1,5 +1,7 @@ using Raven.Client.Documents.Session; using System; +using System.Collections.Generic; +using System.Linq.Expressions; using System.Threading.Tasks; namespace zero.Core.Routing @@ -12,7 +14,7 @@ namespace zero.Core.Routing Task GetRoute(IAsyncDocumentSession session, T model); /// - /// Generate non-random route ID for a model + /// Generate unique route ID for a model /// string GetRouteId(T model); } @@ -24,6 +26,10 @@ namespace zero.Core.Routing Type[] AffectedTypes { get; } + string Controller { get; } + + string Action { get; } + /// /// Resolve a route and load optional dependencies into it. /// This is the data which is passed to the specified controller action. @@ -36,7 +42,12 @@ namespace zero.Core.Routing Task GetRoute(IAsyncDocumentSession session, object model); /// - /// Generate non-random route ID for a model + /// Rebuild all routes for this provider so they can be hydrated into the database + /// + Task> GetAllRoutes(IAsyncDocumentSession session); + + /// + /// Generate unique route ID for a model /// string GetRouteId(object model); } diff --git a/zero.Core/Routing/PageRouteProvider.cs b/zero.Core/Routing/PageRouteProvider.cs index a82473ac..58382569 100644 --- a/zero.Core/Routing/PageRouteProvider.cs +++ b/zero.Core/Routing/PageRouteProvider.cs @@ -13,40 +13,22 @@ namespace zero.Core.Routing public class PageRouteProvider : AbtractRouteProvider { protected const string REF_KEY = "page"; + protected const string PAGE_TYPE_KEY = "pageType"; protected ILogger Logger { get; set; } + protected IPageUrlBuilder UrlBuilder { get; set; } - public PageRouteProvider(ILogger logger) : base("zero.pages") + + public PageRouteProvider(ILogger logger, IPageUrlBuilder urlBuilder) : base("zero.pages") { Logger = logger; - } - - - public string GetRouteId(IPage model) - { - return Alias + "." + model.Hash; + UrlBuilder = urlBuilder; } /// - public override async Task GetRoute(IAsyncDocumentSession session, IPage model) - { - IList routes = await session.Query() - .Where(x => x.AppId == model.AppId && x.ProviderAlias == Alias && x.References[0].Collection == REF_KEY && x.References[0].Id == model.Id) - .ToListAsync(); - - if (routes.Count > 1) - { - Logger.LogWarning("Multiple routes {routes} were found for page {id}", routes.Select(x => x.Id), model.Id); - } - else if (routes.Count < 1) - { - return null; - } - - return routes.First(); - } + public override string GetRouteId(IPage model) => ID_PREFIX + model.Hash; /// @@ -77,5 +59,65 @@ namespace zero.Core.Routing return resolved; } + + + /// + public override async Task> GetAllRoutes(IAsyncDocumentSession session) + { + IList TraversePageChildren(IPage parent, IEnumerable parents, IEnumerable allPages) + { + List routes = new List(); + IEnumerable currentPages = allPages.Where(x => x.ParentId == parent?.Id); + + foreach (IPage page in currentPages) + { + IRoute route = BuildRoute(page, parents); + routes.Add(route); + routes.AddRange(TraversePageChildren(page, parents.Union(new List() { page }), allPages)); + } + + return routes; + } + + List allRoutes = new List(); + IList pages = await session.Query().ToListAsync(); + IEnumerable> groupedPages = pages.GroupBy(x => x.AppId); + + foreach (var group in groupedPages) + { + IList routes = TraversePageChildren(null, new List() { }, group); + allRoutes.AddRange(routes); + } + + return allRoutes; + } + + + /// + /// Build route entity from page + /// + protected IRoute BuildRoute(IPage page, IEnumerable parents) + { + IRoute route = new Route() + { + Id = GetRouteId(page), + AppId = page.AppId, + Url = UrlBuilder.GetUrl(page, parents), + ProviderAlias = Alias + }; + + route.Params.Add(PAGE_TYPE_KEY, page.PageTypeAlias); + route.References.Add(new RouteReference(page.Id, REF_KEY)); + + if (parents != null) + { + foreach (IPage parent in parents) + { + route.Dependencies.Add(parent.Id); + } + } + + return route; + } } } diff --git a/zero.Core/Routing/legacy/PageUrlResolver.cs b/zero.Core/Routing/PageUrlBuilder.cs similarity index 94% rename from zero.Core/Routing/legacy/PageUrlResolver.cs rename to zero.Core/Routing/PageUrlBuilder.cs index 923c3d33..dcfd8b37 100644 --- a/zero.Core/Routing/legacy/PageUrlResolver.cs +++ b/zero.Core/Routing/PageUrlBuilder.cs @@ -12,7 +12,7 @@ using zero.Core.Extensions; namespace zero.Core.Routing { - public class PageUrlResolver : IPageUrlResolver + public class PageUrlBuilder : IPageUrlBuilder { const char PATH_SEPARATOR = '/'; @@ -21,7 +21,7 @@ namespace zero.Core.Routing protected IDocumentStore Raven { get; private set; } - public PageUrlResolver(IDocumentStore raven) + public PageUrlBuilder(IDocumentStore raven) { Raven = raven; } @@ -89,7 +89,7 @@ namespace zero.Core.Routing AddPart(page); - if (!TRAILING_SLASH) + if (!TRAILING_SLASH && stringBuilder.Length > 1) { stringBuilder.Remove(stringBuilder.Length - 1, 1); } diff --git a/zero.Core/Routing/PageUrlProvider-super.cs b/zero.Core/Routing/PageUrlProvider-super.cs deleted file mode 100644 index 4b1d34db..00000000 --- a/zero.Core/Routing/PageUrlProvider-super.cs +++ /dev/null @@ -1,93 +0,0 @@ -using Microsoft.Extensions.Logging; -using Raven.Client.Documents; -using Raven.Client.Documents.Linq; -using Raven.Client.Documents.Session; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using zero.Core.Api; -using zero.Core.Entities; -using zero.Core.Messages; -using zero.Core.Utils; - -namespace zero.Core.Routing -{ - public class PageUrlProviderSuper : IUrlProviderSuper - { - public string Alias => "zero.pages"; - - public const string PAGE_COLLECTION = "page"; - - public const string PAGE_TYPE = "pageType"; - - protected IDocumentStore Raven { get; private set; } - - protected IMessageAggregator Messages { get; private set; } - - protected IPageUrlResolver PageUrlResolver { get; set; } - - public PageUrlProviderSuper(IDocumentStore raven, IMessageAggregator messages, IPageUrlResolver pageUrlResolver) - { - Raven = raven; - Messages = messages; - PageUrlResolver = pageUrlResolver; - } - - public Task ResolveRoute(IAsyncDocumentSession session, IRoute route) - { - throw new NotImplementedException(); - } - } - - - public interface IUrlProviderSuper - { - string Alias { get; } - Task ResolveRoute(IAsyncDocumentSession session, IRoute route); - } - - - public class Test - { - IEnumerable Providers; - IDocumentStore Raven; - ILogger Logger; - - public Test() - { - - } - - - public async Task ResolveUrl(string appId, string path) - { - using IAsyncDocumentSession session = Raven.OpenAsyncSession(); - - IList routes = await session.Query() - .Where(x => x.AppId == appId) - .Where(x => (!x.AllowSuffix && x.Url == path) || (x.AllowSuffix && path.StartsWith(x.Url))) - .ToListAsync(); - - if (routes.Count > 1) - { - Logger.LogWarning("Multiple routes {routes} were found for {path}", routes.Select(x => x.Id), path); - } - else if (routes.Count < 1) - { - return null; - } - - IRoute route = routes.First(); - IUrlProviderSuper provider = Providers.FirstOrDefault(x => x.Alias == route.ProviderAlias); - - if (provider == null) - { - Logger.LogWarning("Could not locate URL provider {provider}", route.ProviderAlias); - return null; - } - - return await provider.ResolveRoute(session, route); - } - } -} diff --git a/zero.Core/Routing/RouteProviderEndpoint.cs b/zero.Core/Routing/RouteProviderEndpoint.cs new file mode 100644 index 00000000..1eded02e --- /dev/null +++ b/zero.Core/Routing/RouteProviderEndpoint.cs @@ -0,0 +1,9 @@ +namespace zero.Core.Routing +{ + public class RouteProviderEndpoint + { + public string Controller { get; set; } + + public string Action { get; set; } + } +} diff --git a/zero.Core/Routing/Routes.cs b/zero.Core/Routing/Routes.cs index 893c072a..ba04b1a3 100644 --- a/zero.Core/Routing/Routes.cs +++ b/zero.Core/Routing/Routes.cs @@ -1,11 +1,16 @@ -using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; using Raven.Client.Documents; +using Raven.Client.Documents.BulkInsert; +using Raven.Client.Documents.Linq; using Raven.Client.Documents.Session; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using zero.Core.Api; using zero.Core.Entities; +using zero.Core.Extensions; namespace zero.Core.Routing { @@ -14,13 +19,15 @@ namespace zero.Core.Routing protected IDocumentStore Raven { get; set; } protected ILogger Logger { get; set; } protected IEnumerable Providers { get; set; } + protected IApplicationContext Context { get; set; } - public Routes(IDocumentStore raven, ILogger logger, IEnumerable providers) + public Routes(IDocumentStore raven, ILogger logger, IEnumerable providers, IApplicationContext context) { Raven = raven; Logger = logger; Providers = providers; + Context = context; } @@ -55,11 +62,28 @@ namespace zero.Core.Routing /// public async Task ResolveUrl(string appId, string path) { + path = path.Length > 1 ? path.TrimEnd('/') : path; + using IAsyncDocumentSession session = Raven.OpenAsyncSession(); + string[] pathParts = path.Trim('/').Split('/'); + string[] parts = new string[pathParts.Length]; + + int min = parts.Length; + foreach (string pathPart in pathParts) + { + for (int i = 0; i < min; i++) + { + parts[i] += '/' + pathPart; + } + min -= 1; + } + IList routes = await session.Query() .Where(x => x.AppId == appId) - .Where(x => (!x.AllowSuffix && x.Url == path) || (x.AllowSuffix && path.StartsWith(x.Url))) + .Where(x => (!x.AllowSuffix && x.Url == path) || (x.AllowSuffix && x.Url.In(parts))) + .Include("References[].Id") + .Include("Dependencies") .ToListAsync(); if (routes.Count > 1) @@ -75,6 +99,16 @@ namespace zero.Core.Routing } + /// + public async Task ResolveUrl(HttpContext context) + { + IApplication app = await Context.ResolveFromRequest(context); + string path = context.Request.Path; + + return await ResolveUrl(app.Id, path); + } + + /// public async Task ResolveRoute(IRoute route) { @@ -83,20 +117,77 @@ namespace zero.Core.Routing } + /// + public async Task> RebuildAllRoutes() + { + int count = 0; + List all = new List(); + using IAsyncDocumentSession session = Raven.OpenAsyncSession(); + session.Advanced.MaxNumberOfRequestsPerSession = 1000; + + foreach (IRouteProvider provider in Providers) + { + // get all routes for this provider + IList routes = await provider.GetAllRoutes(session); + + // delete all registered routes in the database for this provider + await Raven.PurgeAsync($"where {nameof(IRoute.ProviderAlias)} = $alias", new Raven.Client.Parameters() + { + { "alias", provider.Alias } + }); + + // store new routes + using (BulkInsertOperation bulkInsert = Raven.BulkInsert()) + { + foreach (IRoute route in routes) + { + await bulkInsert.StoreAsync(route, route.Id); + count += 1; + } + } + + all.AddRange(routes); + } + + return all; + } + + + /// + public RouteProviderEndpoint GetEndpoint(IRoute route) + { + IRouteProvider routeProvider = FindProvider(route.ProviderAlias); + return routeProvider != null ? new RouteProviderEndpoint() + { + Controller = routeProvider.Controller, + Action = routeProvider.Action + } : null; + } + + /// /// Call the provider which can resolve the route /// async Task ResolveRouteInternal(IAsyncDocumentSession session, IRoute route) { - IRouteProvider routeProvider = Providers.FirstOrDefault(x => x.Alias == route.ProviderAlias); + IRouteProvider routeProvider = FindProvider(route.ProviderAlias); + return await routeProvider?.ResolveRoute(session, route); + } + + + /// + /// Find registered route provider for the specified alias + /// + IRouteProvider FindProvider(string alias) + { + IRouteProvider routeProvider = Providers.FirstOrDefault(x => x.Alias == alias); if (routeProvider == null) { - Logger.LogWarning("Could not locate URL provider {provider}", route.ProviderAlias); - return null; + Logger.LogWarning("Could not locate URL provider {provider}", alias); } - return await routeProvider.ResolveRoute(session, route); + return routeProvider; } } @@ -128,9 +219,24 @@ namespace zero.Core.Routing /// Task ResolveUrl(string appId, string path); + /// + /// Resolve an URL from an http context + /// + Task ResolveUrl(HttpContext context); + /// /// Resolve a route object by passing it to the specified provider /// Task ResolveRoute(IRoute route); + + /// + /// Purges all routes and rebuilds them by iterating over all registered providers + /// + Task> RebuildAllRoutes(); + + /// + /// Get endpoint the route maps to + /// + RouteProviderEndpoint GetEndpoint(IRoute route); } } diff --git a/zero.Core/Routing/legacy/IUrlProvider.cs b/zero.Core/Routing/legacy/IUrlProvider.cs deleted file mode 100644 index ea8674cc..00000000 --- a/zero.Core/Routing/legacy/IUrlProvider.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; - -namespace zero.Core.Routing -{ - public interface IUrlProvider where T : IResolvedRoute - { - string Alias { get; } - - Task Resolve(IRoute route); - - Task> Run(); - } -} diff --git a/zero.Core/Routing/legacy/PageUrlProvider-test.cs b/zero.Core/Routing/legacy/PageUrlProvider-test.cs deleted file mode 100644 index e4b7d061..00000000 --- a/zero.Core/Routing/legacy/PageUrlProvider-test.cs +++ /dev/null @@ -1,140 +0,0 @@ -using Raven.Client.Documents; -using Raven.Client.Documents.Commands.Batches; -using Raven.Client.Documents.Linq; -using Raven.Client.Documents.Operations; -using Raven.Client.Documents.Session; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using zero.Core.Api; -using zero.Core.Database.Indexes; -using zero.Core.Entities; -using zero.Core.Entities.Messages; -using zero.Core.Extensions; -using zero.Core.Messages; -using zero.Core.Utils; - -namespace zero.Core.Routing -{ - public class PageUrlProviderTest - { - public string Alias => "zero.pages"; - - public const string PAGE_COLLECTION = "page"; - - public const string PAGE_TYPE = "pageType"; - - protected IDocumentStore Raven { get; private set; } - - protected IMessageAggregator Messages { get; private set; } - - protected IPageUrlResolver PageUrlResolver { get; set; } - - protected IApplicationContext Context { get; private set; } - - - public PageUrlProviderTest(IDocumentStore raven, IMessageAggregator messages, IPageUrlResolver pageUrlResolver, IApplicationContext context) - { - Raven = raven; - Messages = messages; - PageUrlResolver = pageUrlResolver; - Context = context; - } - - - /// - public bool UrlChanged(IPage previous, IPage current) - { - return previous == null || !previous.Alias.Equals(current.Alias) || !previous.ParentId.Equals(current.ParentId); - } - - - /// - public Task GetDependencies(IPage model) - { - // TODO get all pages (and other routes) which have this page as a dependency and update them - throw new NotImplementedException(); - } - - - /// - public async Task UpdateAll() - { - throw new NotImplementedException(); - - //using IAsyncDocumentSession session = Raven.OpenAsyncSession(); - - //IList pages = await session.Query().ToListAsync(); - //IEnumerable> groupedPages = pages.GroupBy(x => x.AppId); - - //Dictionary map = new Dictionary(); - //HashSet commands = new HashSet(); - - //async Task traversePageChildren(IPage parent, IEnumerable parents, IEnumerable allPages) - //{ - // IEnumerable currentPages = allPages.Where(x => x.ParentId == parent?.Id); - - // foreach (IPage page in currentPages) - // { - // UrlRoute route = new UrlRoute() - // { - // Url = GetUrl(page, parents), - // Dependencies = parents.Select(x => x.Id).ToArray() - // }; - - // commands.Add(new PatchCommandData( - // id: page.Id, - // changeVector: null, - // patchIfMissing: null, - // patch: new PatchRequest() - // { - // Values = { { "route", route } }, - // Script = "this.Route = args.route" - // } - // )); - - // await traversePageChildren(page, parents.Union(new List() { page }), allPages); - // } - //}; - - //foreach (var group in groupedPages) - //{ - // await traversePageChildren(null, new List() { }, group); - //} - - //using (IAsyncDocumentSession commandSession = Raven.OpenAsyncSession()) - //{ - // commandSession.Advanced.Defer(commands.ToArray()); - // await commandSession.SaveChangesAsync(); - //} - - //return commands.Count; - } - - - /// - /// Create url for page - /// - protected string GetUrl(IPage page, IEnumerable parents) - { - StringBuilder stringBuilder = new StringBuilder(); - - stringBuilder.Append('/'); - - if (parents != null) - { - foreach (IPage parent in parents) - { - stringBuilder.Append(parent.Alias); - stringBuilder.Append('/'); - } - } - - stringBuilder.Append(page.Alias); - - return stringBuilder.ToString(); - } - } -} diff --git a/zero.Core/Routing/legacy/PageUrlProvider.cs b/zero.Core/Routing/legacy/PageUrlProvider.cs deleted file mode 100644 index 7b434aec..00000000 --- a/zero.Core/Routing/legacy/PageUrlProvider.cs +++ /dev/null @@ -1,129 +0,0 @@ -using Raven.Client.Documents; -using Raven.Client.Documents.Linq; -using Raven.Client.Documents.Session; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using zero.Core.Api; -using zero.Core.Entities; -using zero.Core.Messages; -using zero.Core.Utils; - -namespace zero.Core.Routing -{ - public class PageUrlProvider : IUrlProvider - { - public string Alias => "zero.pages"; - - public const string PAGE_COLLECTION = "page"; - - public const string PAGE_TYPE = "pageType"; - - protected IDocumentStore Raven { get; private set; } - - protected IMessageAggregator Messages { get; private set; } - - protected IPageUrlResolver PageUrlResolver { get; set; } - - - protected IApplicationContext Context { get; private set; } - - public PageUrlProvider(IDocumentStore raven, IMessageAggregator messages, IPageUrlResolver pageUrlResolver, IApplicationContext context) - { - Raven = raven; - Messages = messages; - PageUrlResolver = pageUrlResolver; - Context = context; - } - - - /// - public async Task Resolve(IRoute route) - { - PageRoute resolved = new PageRoute(route); - - List ids = new List(); - RouteReference reference = route.References.SingleOrDefault(x => x.Collection == PAGE_COLLECTION); - - ids.Add(reference.Id); - ids.AddRange(route.Dependencies); - - using IAsyncDocumentSession session = Raven.OpenAsyncSession(); - - Dictionary pages = await session.LoadAsync(ids); - - if (!pages.TryGetValue(reference.Id, out IPage page) || page.AppId != route.AppId) - { - return null; - } - - resolved.Page = page; - resolved.Parents = pages.Where(x => x.Key != reference.Id).Select(x => x.Value).ToList(); - - return resolved; - } - - - - - /// - public async Task> Run() - { - using IAsyncDocumentSession session = Raven.OpenAsyncSession(); - - List allRoutes = new List(); - IList pages = await session.Query().ToListAsync(); - IEnumerable> groupedPages = pages.GroupBy(x => x.AppId); - - foreach (var group in groupedPages) - { - IList routes = TraversePageChildren(null, new List() { }, group); - allRoutes.AddRange(routes); - } - - return allRoutes; - } - - - protected IRoute BuildRoute(IPage page, IEnumerable parents) - { - IRoute route = new Route() - { - Id = String.Concat("routes.", IdGenerator.Create()), - AppId = page.AppId, - Url = PageUrlResolver.GetUrl(page, parents), - ProviderAlias = Alias - }; - - route.Params.Add(PAGE_TYPE, page.PageTypeAlias); - route.References.Add(new RouteReference(page.Id, PAGE_COLLECTION)); - - if (parents != null) - { - foreach (IPage parent in parents) - { - route.Dependencies.Add(parent.Id); - } - } - - return route; - } - - - IList TraversePageChildren(IPage parent, IEnumerable parents, IEnumerable allPages) - { - List routes = new List(); - IEnumerable currentPages = allPages.Where(x => x.ParentId == parent?.Id); - - foreach (IPage page in currentPages) - { - IRoute route = BuildRoute(page, parents); - routes.Add(route); - routes.AddRange(TraversePageChildren(page, parents.Union(new List() { page }), allPages)); - } - - return routes; - } - } -} diff --git a/zero.Core/Routing/legacy/UrlResult.cs b/zero.Core/Routing/legacy/UrlResult.cs deleted file mode 100644 index d220f503..00000000 --- a/zero.Core/Routing/legacy/UrlResult.cs +++ /dev/null @@ -1,108 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; -using System.Text; -using zero.Core.Extensions; - -namespace zero.Core.Routing -{ - /// - /// Represents infos for a URL. - /// - [DataContract(Name = "urlInfo", Namespace = "")] - public class UrlInfo : IEquatable - { - - /// - /// Creates a instance representing a true URL. - /// - public static UrlInfo Url(string text, string culture = null) => new UrlInfo(text, true, culture); - - /// - /// Creates a instance representing a message. - /// - public static UrlInfo Message(string text, string culture = null) => new UrlInfo(text, false, culture); - - /// - /// Initializes a new instance of the class. - /// - public UrlInfo(string text, bool isUrl, string culture) - { - if (text == null) - { - throw new ArgumentException("Value cannot be null.", nameof(text)); - } - IsUrl = isUrl; - Text = text; - Culture = culture; - } - - /// - /// Gets the culture. - /// - [DataMember(Name = "culture")] - public string Culture { get; } - - /// - /// Gets a value indicating whether the URL is a true URL. - /// - /// Otherwise, it is a message. - [DataMember(Name = "isUrl")] - public bool IsUrl { get; } - - /// - /// Gets the text, which is either the URL, or a message. - /// - [DataMember(Name = "text")] - public string Text { get; } - - /// - /// Checks equality - /// - /// - /// - /// - /// Compare both culture and Text as invariant strings since URLs are not case sensitive, nor are culture names within Umbraco - /// - public bool Equals(UrlInfo other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return string.Equals(Culture, other.Culture, StringComparison.InvariantCultureIgnoreCase) && IsUrl == other.IsUrl && string.Equals(Text, other.Text, StringComparison.InvariantCultureIgnoreCase); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((UrlInfo)obj); - } - - public override int GetHashCode() - { - unchecked - { - var hashCode = (Culture != null ? StringComparer.InvariantCultureIgnoreCase.GetHashCode(Culture) : 0); - hashCode = (hashCode * 397) ^ IsUrl.GetHashCode(); - hashCode = (hashCode * 397) ^ (Text != null ? StringComparer.InvariantCultureIgnoreCase.GetHashCode(Text) : 0); - return hashCode; - } - } - - public static bool operator ==(UrlInfo left, UrlInfo right) - { - return Equals(left, right); - } - - public static bool operator !=(UrlInfo left, UrlInfo right) - { - return !Equals(left, right); - } - - public override string ToString() - { - return Text; - } - } -} \ No newline at end of file diff --git a/zero.Web/Controllers/DefaultRouteController.cs b/zero.Web/Controllers/DefaultRouteController.cs new file mode 100644 index 00000000..4c5c498b --- /dev/null +++ b/zero.Web/Controllers/DefaultRouteController.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json; + +namespace zero.Web.Controllers +{ + public class DefaultRouteController : Controller + { + public DefaultRouteController() + { + + } + + public IActionResult Index() + { + return Json(HttpContext.Request.RouteValues, new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.None }); + } + } +} diff --git a/zero.Web/Defaults/ZeroBackofficePlugin.cs b/zero.Web/Defaults/ZeroBackofficePlugin.cs index 369a8e06..26ba9ed6 100644 --- a/zero.Web/Defaults/ZeroBackofficePlugin.cs +++ b/zero.Web/Defaults/ZeroBackofficePlugin.cs @@ -8,6 +8,7 @@ using zero.Core.Options; using zero.Core.Plugins; using zero.Core.Routing; using zero.Core.Validation; +using zero.Web.Routing; using zero.Web.Sections; namespace zero.Web.Defaults @@ -89,10 +90,13 @@ namespace zero.Web.Defaults services.AddTransient(); services.AddTransient(); - services.AddTransient(); + services.AddScoped(); + services.AddScoped(); //services.AddTransient(); - services.AddTransient(); - services.AddTransient(); + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); } } } \ No newline at end of file diff --git a/zero.Web/Routing/ZeroRoutesTransformer.cs b/zero.Web/Routing/ZeroRoutesTransformer.cs new file mode 100644 index 00000000..97eab1e6 --- /dev/null +++ b/zero.Web/Routing/ZeroRoutesTransformer.cs @@ -0,0 +1,37 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Routing; +using System.Threading.Tasks; +using zero.Core.Routing; + +namespace zero.Web.Routing +{ + public class ZeroRoutesTransformer : DynamicRouteValueTransformer + { + IRoutes Routes; + + public ZeroRoutesTransformer(IRoutes routes) + { + Routes = routes; + } + + + public override async ValueTask TransformAsync(HttpContext httpContext, RouteValueDictionary values) + { + IResolvedRoute route = await Routes.ResolveUrl(httpContext); + + if (route == null) + { + return null; + } + + RouteProviderEndpoint endpoint = Routes.GetEndpoint(route.Route); + + values["route"] = route; + values["controller"] = endpoint.Controller; + values["action"] = endpoint.Action; + + return values; + } + } +} diff --git a/zero.Web/ZeroApplicationBuilderExtensions.cs b/zero.Web/ZeroApplicationBuilderExtensions.cs index 28db8224..2b70a1a4 100644 --- a/zero.Web/ZeroApplicationBuilderExtensions.cs +++ b/zero.Web/ZeroApplicationBuilderExtensions.cs @@ -11,6 +11,7 @@ using VueCliMiddleware; using zero.Core.Extensions; using zero.Core.Middlewares; using zero.Core.Options; +using zero.Web.Routing; namespace zero.Web { @@ -65,6 +66,14 @@ namespace zero.Web }); }); + app.UseWhen(ctx => !ctx.Request.Path.ToString().StartsWith(path), builder => + { + app.UseEndpoints(endpoints => + { + endpoints.MapDynamicControllerRoute("/{**url}"); + }); + }); + return app; }