From 55617e35252dcfb989c8e64cfe71271655fbce40 Mon Sep 17 00:00:00 2001 From: Tobias Klika Date: Thu, 4 Nov 2021 15:51:54 +0100 Subject: [PATCH] start new routing --- .../Collections/Tests/CountryCollection.cs | 45 ++++++ zero.Core/Extensions/QueryableExtensions.cs | 2 - .../Routing/{ => Links}/ILinkProvider.cs | 0 zero.Core/Routing/{ => Links}/Links.cs | 0 .../Routing/{ => Links}/RawLinkProvider.cs | 0 .../Routing/{ => Links}/RequestUrlResolver.cs | 0 zero.Core/Routing/New/INewRouteProvider.cs | 106 ++++++++++--- zero.Core/Routing/New/IRouteModel.cs | 12 ++ .../Routing/New/NewAbtractRouteProvider.cs | 144 +++++++++--------- zero.Core/Routing/New/NewTestRouteProvider.cs | 100 ++++++------ zero.Core/Routing/New/TestRouteProvider.cs | 80 ++++++++++ zero.Core/Routing/New/ZeroRoutingContext.cs | 37 +++++ zero.Core/Routing/NotFoundRoute.cs | 21 --- zero.Core/Routing/Page/BasePageRoute.cs | 2 +- zero.Core/Routing/Page/IPageUrlBuilder.cs | 5 - zero.Core/Routing/Page/PageUrlBuilder.cs | 41 +---- zero.Core/zero.Core.csproj | 1 + 17 files changed, 386 insertions(+), 210 deletions(-) create mode 100644 zero.Core/Collections/Tests/CountryCollection.cs rename zero.Core/Routing/{ => Links}/ILinkProvider.cs (100%) rename zero.Core/Routing/{ => Links}/Links.cs (100%) rename zero.Core/Routing/{ => Links}/RawLinkProvider.cs (100%) rename zero.Core/Routing/{ => Links}/RequestUrlResolver.cs (100%) create mode 100644 zero.Core/Routing/New/IRouteModel.cs create mode 100644 zero.Core/Routing/New/TestRouteProvider.cs create mode 100644 zero.Core/Routing/New/ZeroRoutingContext.cs delete mode 100644 zero.Core/Routing/NotFoundRoute.cs diff --git a/zero.Core/Collections/Tests/CountryCollection.cs b/zero.Core/Collections/Tests/CountryCollection.cs new file mode 100644 index 00000000..0d7efefc --- /dev/null +++ b/zero.Core/Collections/Tests/CountryCollection.cs @@ -0,0 +1,45 @@ +using FluentValidation.Results; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using zero.Core.Entities; + +namespace zero.Core.Collections.Tests +{ + public class CountryCollection : EntityCollection + { + /// + public override async Task Validate(ICollectionContext ctx, Country model) + { + return await new CountryValidator(ctx.Store).ValidateAsync(model); + } + } + + + public class CountryDto : ZeroEntity + { + public bool IsPreferred { get; set; } + public string Code { get; set; } + } + + + public class EntityCollection where TEntity : ZeroEntity where TDto : ZeroEntity + { + /// + public virtual Task Validate(ICollectionContext ctx, TDto model) + { + return Task.FromResult(new ValidationResult()); + } + + + public virtual Task> Save(ICollectionContext ctx, TDto model) + { + throw new NotImplementedException(); + } + } + + + public class EntityCollection : EntityCollection where T : ZeroEntity { } +} diff --git a/zero.Core/Extensions/QueryableExtensions.cs b/zero.Core/Extensions/QueryableExtensions.cs index b5b3f750..547cfc01 100644 --- a/zero.Core/Extensions/QueryableExtensions.cs +++ b/zero.Core/Extensions/QueryableExtensions.cs @@ -3,10 +3,8 @@ using Raven.Client.Documents.Linq; using Raven.Client.Documents.Queries; using Raven.Client.Documents.Session; using System; -using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using zero.Core.Entities; namespace zero.Core.Extensions { diff --git a/zero.Core/Routing/ILinkProvider.cs b/zero.Core/Routing/Links/ILinkProvider.cs similarity index 100% rename from zero.Core/Routing/ILinkProvider.cs rename to zero.Core/Routing/Links/ILinkProvider.cs diff --git a/zero.Core/Routing/Links.cs b/zero.Core/Routing/Links/Links.cs similarity index 100% rename from zero.Core/Routing/Links.cs rename to zero.Core/Routing/Links/Links.cs diff --git a/zero.Core/Routing/RawLinkProvider.cs b/zero.Core/Routing/Links/RawLinkProvider.cs similarity index 100% rename from zero.Core/Routing/RawLinkProvider.cs rename to zero.Core/Routing/Links/RawLinkProvider.cs diff --git a/zero.Core/Routing/RequestUrlResolver.cs b/zero.Core/Routing/Links/RequestUrlResolver.cs similarity index 100% rename from zero.Core/Routing/RequestUrlResolver.cs rename to zero.Core/Routing/Links/RequestUrlResolver.cs diff --git a/zero.Core/Routing/New/INewRouteProvider.cs b/zero.Core/Routing/New/INewRouteProvider.cs index dfaba28d..5532bb1a 100644 --- a/zero.Core/Routing/New/INewRouteProvider.cs +++ b/zero.Core/Routing/New/INewRouteProvider.cs @@ -1,46 +1,114 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using zero.Core.Entities; namespace zero.Core.Routing { - public interface INewRouteProvider where T : IZeroRouteEntity + public abstract class ZeroRouteProvider : ZeroRouteProvider, INewRouteProvider where T : IZeroRouteEntity { - string Alias { get; } + public ZeroRouteProvider(string alias) : base(alias) { } - /// - /// Warmup is only called once. - /// - Task Warmup(); + /// + public override bool CanHandle(Type type) => typeof(T).IsAssignableFrom(type); - /// - /// Generate unique route ID for a model - /// - string Id(T model); + /// + public abstract Task Create(RoutingContext context, T model); + /// + public sealed override Task Create(RoutingContext context, IZeroRouteEntity model) => Create(context, (T)model); + + /// + public virtual string Key(T model) => model.Hash; + + /// + public sealed override string Key(IZeroRouteEntity model) => Key((T)model); + + /// + public virtual Task Model(RoutingContext context, Route route, T entity) => Task.FromResult((IRouteModel)new RouteModel() { Route = route }); + + /// + public sealed override Task Model(RoutingContext context, Route route, IZeroRouteEntity entity) => Model(context, route, (T)entity); + } + + + public abstract class ZeroRouteProvider : INewRouteProvider + { + public ZeroRouteProvider(string alias) + { + Alias = alias; + } + + public virtual string Alias { get; protected set; } + + /// + public virtual bool CanHandle(Type type) => false; + + /// + public abstract Task Create(RoutingContext context, IZeroRouteEntity model); + + /// + public virtual string Key(IZeroRouteEntity model) => model.Hash; + + /// + public virtual IAsyncEnumerable Seed(RoutingContext context) => AsyncEnumerable.Empty(); + + /// + public virtual Task Model(RoutingContext context, Route route, IZeroRouteEntity entity) => Task.FromResult((IRouteModel)new RouteModel() { Route = route }); + } + + + public interface INewRouteProvider : INewRouteProvider where T : IZeroRouteEntity + { /// - /// Build URL for a model + /// Generate unique route key for a model /// - string Url(T model); + string Key(T model); /// /// Create route entity from a model /// - Route Create(T model); + Task Create(RoutingContext context, T model); /// - /// + /// Converts a route to a model which is passed to the endpoint /// - //bool HasChanged(T newModel, T oldModel); + Task Model(RoutingContext context, Route route, T entity); + } + + + public interface INewRouteProvider + { + /// + /// Alias of this route provider + /// + string Alias { get; } + + /// + /// Whether this provider can handle a certain entity type + /// + bool CanHandle(Type type); + + /// + /// Generate unique route key for a model + /// + string Key(IZeroRouteEntity model); + + /// + /// Create route entity from a model + /// + Task Create(RoutingContext context, IZeroRouteEntity model); /// /// Get all models which should be provided and handled by this instance /// - Task> All(); + IAsyncEnumerable Seed(RoutingContext context); /// - /// Build a resolved route from a route response + /// Converts a route to a model which is passed to the endpoint /// - Task Resolve(RouteResponse response); + Task Model(RoutingContext context, Route route, IZeroRouteEntity entity); } + } diff --git a/zero.Core/Routing/New/IRouteModel.cs b/zero.Core/Routing/New/IRouteModel.cs new file mode 100644 index 00000000..5a5fad1d --- /dev/null +++ b/zero.Core/Routing/New/IRouteModel.cs @@ -0,0 +1,12 @@ +namespace zero.Core.Routing +{ + public interface IRouteModel + { + Route Route { get; set; } + } + + public class RouteModel : IRouteModel + { + public Route Route { get; set; } + } +} diff --git a/zero.Core/Routing/New/NewAbtractRouteProvider.cs b/zero.Core/Routing/New/NewAbtractRouteProvider.cs index 4e82f419..bff9bc36 100644 --- a/zero.Core/Routing/New/NewAbtractRouteProvider.cs +++ b/zero.Core/Routing/New/NewAbtractRouteProvider.cs @@ -1,53 +1,53 @@ -using Raven.Client.Documents; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Threading.Tasks; -using zero.Core.Database; -using zero.Core.Entities; -using zero.Core.Options; +//using Raven.Client.Documents; +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Linq.Expressions; +//using System.Threading.Tasks; +//using zero.Core.Database; +//using zero.Core.Entities; +//using zero.Core.Options; -namespace zero.Core.Routing -{ - public abstract class NewAbtractRouteProvider : INewRouteProvider where T : IZeroRouteEntity - { - public virtual string Alias { get; protected set; } +//namespace zero.Core.Routing +//{ +// public abstract class NewAbtractRouteProvider : INewRouteProvider where T : IZeroRouteEntity +// { +// public virtual string Alias { get; protected set; } - public const string ID_PREFIX = "routes."; +// public const string ID_PREFIX = "routes."; - public const char SLASH = '/'; +// public const char SLASH = '/'; - protected IZeroStore Store { get; set; } +// protected IZeroStore Store { get; set; } - protected IZeroOptions Options { get; set; } +// protected IZeroOptions Options { get; set; } - public NewAbtractRouteProvider(string alias, IZeroStore store, IZeroOptions options) - { - Store = store; - Alias = alias; - Options = options; - } +// public NewAbtractRouteProvider(string alias, IZeroStore store, IZeroOptions options) +// { +// Store = store; +// Alias = alias; +// Options = options; +// } - public virtual Task Warmup() => Task.CompletedTask; +// public virtual Task Warmup() => Task.CompletedTask; - public virtual string Id(T model) => ID_PREFIX + model.Hash; +// public virtual string Id(T model) => ID_PREFIX + model.Hash; - public abstract string Url(T model); +// public abstract string Url(T model); - public abstract Route Create(T model); +// public abstract Route Create(T model); - public abstract Task> All(); +// public abstract Task> All(); - public abstract Task Resolve(RouteResponse response); +// public abstract Task Resolve(RouteResponse response); @@ -55,58 +55,58 @@ namespace zero.Core.Routing - protected async Task ResolvePage() - { - IZeroDocumentSession session = Store.Session(); - IEnumerable>> resolvers = Options.Routing.PageResolvers.GetAll(typeof(T)); +// protected async Task ResolvePage() +// { +// IZeroDocumentSession session = Store.Session(); +// IEnumerable>> resolvers = Options.Routing.PageResolvers.GetAll(typeof(T)); - foreach (Expression> resolver in resolvers.Reverse()) - { - Page page = await session.Query().FirstOrDefaultAsync(resolver); +// foreach (Expression> resolver in resolvers.Reverse()) +// { +// Page page = await session.Query().FirstOrDefaultAsync(resolver); - if (page != null) - { - return page; - } - } +// if (page != null) +// { +// return page; +// } +// } - return null; - } +// return null; +// } - protected async Task> ResolvePages() - { - IZeroDocumentSession session = Store.Session(); - List pages = new(); - IEnumerable>> resolvers = Options.Routing.PageResolvers.GetAll(typeof(T)); +// protected async Task> ResolvePages() +// { +// IZeroDocumentSession session = Store.Session(); +// List pages = new(); +// IEnumerable>> resolvers = Options.Routing.PageResolvers.GetAll(typeof(T)); - foreach (Expression> resolver in resolvers.Reverse()) - { - pages.AddRange(await session.Query().Where(resolver).ToListAsync()); - } +// foreach (Expression> resolver in resolvers.Reverse()) +// { +// pages.AddRange(await session.Query().Where(resolver).ToListAsync()); +// } - return pages; - } +// return pages; +// } - protected async Task ResolvePageRoute() - { - Page page = await ResolvePage(); +// protected async Task ResolvePageRoute() +// { +// Page page = await ResolvePage(); - // WARNING: we are assuming that the route id is built from the page hash but this could be altered with PageRouteProvier.GetRouteId. - // we cannot use a dependency on this provider here as we are working from the abstract route provider which is the base of the PageRouteProvider itself, - // and therefore a circular dependency. - return page == null ? null : await Store.Session().LoadAsync(ID_PREFIX + page.Hash); - } +// // WARNING: we are assuming that the route id is built from the page hash but this could be altered with PageRouteProvier.GetRouteId. +// // we cannot use a dependency on this provider here as we are working from the abstract route provider which is the base of the PageRouteProvider itself, +// // and therefore a circular dependency. +// return page == null ? null : await Store.Session().LoadAsync(ID_PREFIX + page.Hash); +// } - protected async Task> ResolvePageRoutes() - { - List routes = new(); - IEnumerable pages = await ResolvePages(); +// protected async Task> ResolvePageRoutes() +// { +// List routes = new(); +// IEnumerable pages = await ResolvePages(); - string[] ids = pages.Select(x => ID_PREFIX + x.Hash).ToArray(); - return ids.Length < 1 ? routes : (await Store.Session().LoadAsync(ids)).Select(x => x.Value); - } - } -} +// string[] ids = pages.Select(x => ID_PREFIX + x.Hash).ToArray(); +// return ids.Length < 1 ? routes : (await Store.Session().LoadAsync(ids)).Select(x => x.Value); +// } +// } +//} diff --git a/zero.Core/Routing/New/NewTestRouteProvider.cs b/zero.Core/Routing/New/NewTestRouteProvider.cs index 370aa879..b47843ab 100644 --- a/zero.Core/Routing/New/NewTestRouteProvider.cs +++ b/zero.Core/Routing/New/NewTestRouteProvider.cs @@ -1,63 +1,63 @@ -using Raven.Client.Documents; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using zero.Core.Database; -using zero.Core.Entities; -using zero.Core.Extensions; -using zero.Core.Options; +//using Raven.Client.Documents; +//using System; +//using System.Collections.Generic; +//using System.Threading.Tasks; +//using zero.Core.Database; +//using zero.Core.Entities; +//using zero.Core.Extensions; +//using zero.Core.Options; -namespace zero.Core.Routing -{ - public class NewRouteProvider : NewAbtractRouteProvider - { - protected Route PageRoute { get; set; } +//namespace zero.Core.Routing +//{ +// public class NewRouteProvider : NewAbtractRouteProvider +// { +// protected Route PageRoute { get; set; } - public NewRouteProvider(IZeroStore store, IZeroOptions options) : base("new.test", store, options) - { - Options = options; - } +// public NewRouteProvider(IZeroStore store, IZeroOptions options) : base("new.test", store, options) +// { +// Options = options; +// } - /// - public override async Task Warmup() - { - PageRoute ??= await ResolvePageRoute(); - } +// /// +// public override async Task Warmup() +// { +// PageRoute ??= await ResolvePageRoute(); +// } - /// - public override string Url(Country model) - { - return String.Join(SLASH, PageRoute.Url, "story", model.Alias); - } +// /// +// public override string Url(Country model) +// { +// return String.Join(SLASH, PageRoute.Url, "story", model.Alias); +// } - /// - public override Route Create(Country model) - { - return new Route(Alias) - { - Id = Id(model), - Url = Url(model), - }.DependsOn(model.Id); - } +// /// +// public override Route Create(Country model) +// { +// return new Route(Alias) +// { +// Id = Id(model), +// Url = Url(model), +// }.DependsOn(model.Id); +// } - /// - public override async Task Resolve(RouteResponse response) - { - PageRoute resolved = new(response.Route); - resolved.Page = await Store.Session().LoadAsync(response.Route.Dependencies[0]); - return resolved.Page == null ? null : resolved; - } +// /// +// public override async Task Resolve(RouteResponse response) +// { +// PageRoute resolved = new(response.Route); +// resolved.Page = await Store.Session().LoadAsync(response.Route.Dependencies[0]); +// return resolved.Page == null ? null : resolved; +// } - /// - public override async Task> All() - { - return await Store.Session().Query().ToListAsync(); - } - } -} +// /// +// public override async Task> All() +// { +// return await Store.Session().Query().ToListAsync(); +// } +// } +//} diff --git a/zero.Core/Routing/New/TestRouteProvider.cs b/zero.Core/Routing/New/TestRouteProvider.cs new file mode 100644 index 00000000..eb9714d4 --- /dev/null +++ b/zero.Core/Routing/New/TestRouteProvider.cs @@ -0,0 +1,80 @@ +using Raven.Client.Documents; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using zero.Core.Database.Indexes; +using zero.Core.Entities; + +namespace zero.Core.Routing +{ + public class TestPageRouteProvider : ZeroRouteProvider + { + public static string PAGE_TYPE_PARAM = "pageType"; + + protected IPageUrlBuilder UrlBuilder { get; set; } + + + public TestPageRouteProvider(IPageUrlBuilder urlBuilder) : base("zero.pages") + { + UrlBuilder = urlBuilder; + } + + + /// + public override async Task Create(RoutingContext context, Page model) + { + if (model is PageFolder) + { + return null; + } + + IEnumerable parents = await GetParents(context, model); + + Route route = new(); + + route.Url = UrlBuilder.GetUrl(model, parents); + route.Dependencies.Add(model.Id); + route.Dependencies.AddRange(parents.Select(x => x.Id)); + + route.Params.Add(PAGE_TYPE_PARAM, model.PageTypeAlias); + + return route; + } + + + /// + public override async Task Model(RoutingContext context, Route route, Page entity) + { + PageRoute resolved = new(route); + resolved.Page = entity; + resolved.Parents = await GetParents(context, entity); + + return resolved; + } + + + /// + public override async IAsyncEnumerable Seed(RoutingContext context) + { + var stream = await context.Session.Advanced.StreamAsync(context.Session.Query()); + while (await stream.MoveNextAsync()) + { + yield return await Create(context, stream.Current.Document); + } + } + + + /// + /// Get parents for a page + /// + protected virtual async Task> GetParents(RoutingContext context, Page model) + { + Pages_ByHierarchy.Result result = await context.Session.Query() + .ProjectInto() + .Include(x => x.Path.Select(p => p.Id)) + .FirstOrDefaultAsync(x => x.Id == model.Id); + + return (await context.Session.LoadAsync(result.Path.Select(x => x.Id))).Select(x => x.Value).ToList(); + } + } +} diff --git a/zero.Core/Routing/New/ZeroRoutingContext.cs b/zero.Core/Routing/New/ZeroRoutingContext.cs new file mode 100644 index 00000000..a02f41a1 --- /dev/null +++ b/zero.Core/Routing/New/ZeroRoutingContext.cs @@ -0,0 +1,37 @@ +using zero.Core.Database; +using zero.Core.Options; + +namespace zero.Core.Routing +{ + public class RoutingContext : IRoutingContext + { + public IZeroStore Store { get; private set; } + + public IZeroContext Context { get; private set; } + + public IZeroOptions Options { get; private set; } + + public IZeroDocumentSession Session { get; private set; } + + + public RoutingContext(IZeroStore store, IZeroContext context, IZeroOptions options, IZeroDocumentSession session) + { + Store = store; + Context = context; + Options = options; + Session = session; + } + } + + + public interface IRoutingContext + { + IZeroStore Store { get; } + + IZeroContext Context { get; } + + IZeroOptions Options { get; } + + IZeroDocumentSession Session { get; } + } +} diff --git a/zero.Core/Routing/NotFoundRoute.cs b/zero.Core/Routing/NotFoundRoute.cs deleted file mode 100644 index 337a900f..00000000 --- a/zero.Core/Routing/NotFoundRoute.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Microsoft.AspNetCore.Http; - -namespace zero.Core.Routing -{ - public class NotFoundRoute : IResolvedRoute - { - public NotFoundRoute(HttpContext context) - { - Route = new Route() - { - Url = context.Request.Path.ToString() - }; - } - - public Route Route { get; set; } - - public string Controller { get; set; } - - public string Action { get; set; } - } -} diff --git a/zero.Core/Routing/Page/BasePageRoute.cs b/zero.Core/Routing/Page/BasePageRoute.cs index fc4d28fa..0ff9c629 100644 --- a/zero.Core/Routing/Page/BasePageRoute.cs +++ b/zero.Core/Routing/Page/BasePageRoute.cs @@ -3,7 +3,7 @@ using zero.Core.Entities; namespace zero.Core.Routing { - public abstract class BasePageRoute : IResolvedRoute + public abstract class BasePageRoute : IResolvedRoute, IRouteModel { public BasePageRoute() { } diff --git a/zero.Core/Routing/Page/IPageUrlBuilder.cs b/zero.Core/Routing/Page/IPageUrlBuilder.cs index 8c9a573e..bb6302b4 100644 --- a/zero.Core/Routing/Page/IPageUrlBuilder.cs +++ b/zero.Core/Routing/Page/IPageUrlBuilder.cs @@ -10,11 +10,6 @@ namespace zero.Core.Routing { public interface IPageUrlBuilder { - /// - /// Get URL for a page - /// - Task GetUrl(Page page); - /// /// Get URL for a page /// diff --git a/zero.Core/Routing/Page/PageUrlBuilder.cs b/zero.Core/Routing/Page/PageUrlBuilder.cs index 70066e5d..6a5a6f4d 100644 --- a/zero.Core/Routing/Page/PageUrlBuilder.cs +++ b/zero.Core/Routing/Page/PageUrlBuilder.cs @@ -1,12 +1,8 @@ -using Raven.Client.Documents; -using System; +using System; using System.Collections.Generic; -using System.Linq; using System.Text; -using System.Threading.Tasks; using zero.Core.Api; using zero.Core.Database; -using zero.Core.Database.Indexes; using zero.Core.Entities; using zero.Core.Extensions; using zero.Core.Options; @@ -31,41 +27,6 @@ namespace zero.Core.Routing } - /// - public async Task GetUrl(Page page) - { - IZeroDocumentSession session = Store.Session(); - - Pages_ByHierarchy.Result result = await session.Query() - .ProjectInto() - .Include(x => x.Path.Select(p => p.Id)) - .FirstOrDefaultAsync(x => x.Id == page.Id); - - IList parents = (await session.LoadAsync(result.Path.Select(x => x.Id))).Select(x => x.Value).ToList(); - - StringBuilder stringBuilder = new StringBuilder(); - - if (parents != null) - { - foreach (Page parent in parents) - { - stringBuilder.Append(GetUrlPart(parent)); - stringBuilder.Append(PATH_SEPARATOR); - } - } - - stringBuilder.Append(GetUrlPart(page)); - stringBuilder.Append(PATH_SEPARATOR); - - if (!TRAILING_SLASH) - { - stringBuilder.Remove(stringBuilder.Length - 1, 1); - } - - return stringBuilder.ToString(); - } - - /// public string GetUrl(Page page, IEnumerable parents) { diff --git a/zero.Core/zero.Core.csproj b/zero.Core/zero.Core.csproj index a9a92207..9e6b68cd 100644 --- a/zero.Core/zero.Core.csproj +++ b/zero.Core/zero.Core.csproj @@ -33,6 +33,7 @@ +