diff --git a/zero.Core/Entities/ZeroEntity.cs b/zero.Core/Entities/ZeroEntity.cs
index effbf3c9..638262a7 100644
--- a/zero.Core/Entities/ZeroEntity.cs
+++ b/zero.Core/Entities/ZeroEntity.cs
@@ -5,7 +5,7 @@ using System.Diagnostics;
namespace zero.Core.Entities
{
[DebuggerDisplay("Id = {Id,nq}, Name = {Name}, Alias = {Alias}")]
- public class ZeroEntity : ZeroIdEntity, IZeroDbConventions
+ public class ZeroEntity : ZeroIdEntity, IZeroDbConventions, IZeroRouteEntity
{
///
/// Full name of the entity
@@ -83,4 +83,18 @@ namespace zero.Core.Entities
///
public string Id { get; set; }
}
+
+
+ public interface IZeroRouteEntity
+ {
+ ///
+ /// Id of the entity
+ ///
+ string Id { get; set; }
+
+ ///
+ /// Unique hash for this entity (primarily used for routing)
+ ///
+ string Hash { get; set; }
+ }
}
diff --git a/zero.Core/Extensions/RouteExtensions.cs b/zero.Core/Extensions/RouteExtensions.cs
new file mode 100644
index 00000000..1d04a79b
--- /dev/null
+++ b/zero.Core/Extensions/RouteExtensions.cs
@@ -0,0 +1,25 @@
+using zero.Core.Routing;
+
+namespace zero.Core.Extensions
+{
+ public static class RouteExtensions
+ {
+ public static Route DependsOn(this Route route, string id)
+ {
+ route.Dependencies.Add(id);
+ return route;
+ }
+
+ public static Route Param(this Route route, string key, object value)
+ {
+ route.Params[key] = value;
+ return route;
+ }
+
+ public static Route Reference(this Route route, string id, string collection)
+ {
+ route.References.Add(new(id, collection));
+ return route;
+ }
+ }
+}
diff --git a/zero.Core/Routing/IRouteProvider.cs b/zero.Core/Routing/IRouteProvider.cs
index c87d84d5..239469ac 100644
--- a/zero.Core/Routing/IRouteProvider.cs
+++ b/zero.Core/Routing/IRouteProvider.cs
@@ -57,6 +57,11 @@ namespace zero.Core.Routing
///
Task> GetAllRoutes(IAsyncDocumentSession session);
+ /////
+ ///// Updates all affected routes on demand after an entity has been changed
+ /////
+ //Task UpdateOnDemand(object model, object previous);
+
///
/// Generate unique route ID for a model
///
diff --git a/zero.Core/Routing/New/INewRouteProvider.cs b/zero.Core/Routing/New/INewRouteProvider.cs
new file mode 100644
index 00000000..2298358b
--- /dev/null
+++ b/zero.Core/Routing/New/INewRouteProvider.cs
@@ -0,0 +1,36 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using zero.Core.Entities;
+
+namespace zero.Core.Routing
+{
+ public interface INewRouteProvider where T : IZeroRouteEntity
+ {
+ string Alias { get; }
+
+ ///
+ /// Generate unique route ID for a model
+ ///
+ string Id(T model);
+
+ ///
+ /// Build URL for a model
+ ///
+ //string Url(T model);
+
+ ///
+ /// Create route entity from a model
+ ///
+ Task Create(T model);
+
+ ///
+ /// Get all models which should be provided and handled by this instance
+ ///
+ Task> All();
+
+ ///
+ /// Build a resolved route from a route response
+ ///
+ Task Resolve(RouteResponse response);
+ }
+}
diff --git a/zero.Core/Routing/New/NewAbtractRouteProvider.cs b/zero.Core/Routing/New/NewAbtractRouteProvider.cs
new file mode 100644
index 00000000..bac0e343
--- /dev/null
+++ b/zero.Core/Routing/New/NewAbtractRouteProvider.cs
@@ -0,0 +1,105 @@
+using Raven.Client.Documents;
+using Raven.Client.Documents.Session;
+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; }
+
+ public const string ID_PREFIX = "routes.";
+
+ public const char SLASH = '/';
+
+ protected IZeroDocumentSession Session { get; set; }
+
+ protected IZeroOptions Options { get; set; }
+
+
+
+ public NewAbtractRouteProvider(string alias, IZeroDocumentSession session, IZeroOptions options)
+ {
+ Session = session;
+ Alias = alias;
+ Options = options;
+ }
+
+
+ public virtual string Id(T model) => ID_PREFIX + model.Hash;
+
+
+ public abstract Task Create(T model);
+
+
+ public abstract Task> All();
+
+
+ public abstract Task Resolve(RouteResponse response);
+
+
+
+
+
+
+
+ protected async Task ResolvePage()
+ {
+ IEnumerable>> resolvers = Options.Routing.PageResolvers.GetAll(typeof(T));
+
+ foreach (Expression> resolver in resolvers.Reverse())
+ {
+ Page page = await Session.Query().FirstOrDefaultAsync(resolver);
+
+ if (page != null)
+ {
+ return page;
+ }
+ }
+
+ return null;
+ }
+
+
+ protected async Task> ResolvePages()
+ {
+ 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());
+ }
+
+ return pages;
+ }
+
+
+ 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 Session.LoadAsync(ID_PREFIX + page.Hash);
+ }
+
+
+ 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 Session.LoadAsync(ids)).Select(x => x.Value);
+ }
+ }
+}
diff --git a/zero.Core/Routing/New/NewTestRouteProvider.cs b/zero.Core/Routing/New/NewTestRouteProvider.cs
new file mode 100644
index 00000000..24e8edd6
--- /dev/null
+++ b/zero.Core/Routing/New/NewTestRouteProvider.cs
@@ -0,0 +1,51 @@
+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; }
+
+
+ public NewRouteProvider(IZeroDocumentSession session, IZeroOptions options) : base("new.test", session, options)
+ {
+ Options = options;
+ }
+
+
+ ///
+ public override async Task Create(Country model)
+ {
+ PageRoute ??= await ResolvePageRoute();
+
+ return new Route(Alias)
+ {
+ Id = Id(model),
+ Url = String.Join(SLASH, PageRoute.Url, "story", model.Alias)
+ }.DependsOn(model.Id);
+ }
+
+
+ ///
+ public override async Task Resolve(RouteResponse response)
+ {
+ PageRoute resolved = new(response.Route);
+ resolved.Page = await Session.LoadAsync(response.Route.Dependencies[0]);
+ return resolved.Page == null ? null : resolved;
+ }
+
+
+ ///
+ public override async Task> All()
+ {
+ return await Session.Query().ToListAsync();
+ }
+ }
+}
diff --git a/zero.Core/Routing/Page/PageUrlBuilder.cs b/zero.Core/Routing/Page/PageUrlBuilder.cs
index c30f6f01..c693f20d 100644
--- a/zero.Core/Routing/Page/PageUrlBuilder.cs
+++ b/zero.Core/Routing/Page/PageUrlBuilder.cs
@@ -106,7 +106,7 @@ namespace zero.Core.Routing
///
/// Get the part of the URL (by querying UrlAlias and Alias) for this page
///
- protected virtual string GetUrlPart(Page page)
+ public virtual string GetUrlPart(Page page)
{
string alias;
diff --git a/zero.Core/Routing/Route.cs b/zero.Core/Routing/Route.cs
index 6dac8050..c15e8c02 100644
--- a/zero.Core/Routing/Route.cs
+++ b/zero.Core/Routing/Route.cs
@@ -7,6 +7,13 @@ namespace zero.Core.Routing
[Collection("Routes")]
public class Route : ZeroIdEntity
{
+ public Route() { }
+
+ public Route(string providerAlias)
+ {
+ ProviderAlias = providerAlias;
+ }
+
///
/// Generated URL based on the URL provider
///
diff --git a/zero.Core/Routing/Routes.cs b/zero.Core/Routing/Routes.cs
index c00740d0..363c3147 100644
--- a/zero.Core/Routing/Routes.cs
+++ b/zero.Core/Routing/Routes.cs
@@ -216,30 +216,6 @@ namespace zero.Core.Routing
}
- /////
- //public async Task ResolveRoute(Route route)
- //{
- // using IAsyncDocumentSession session = Store.OpenAsyncSession();
- // return await ResolveRouteInternal(session, null); // TODO
- //}
-
-
- ///
- public NotFoundRoute NotFound(HttpContext context)
- {
- if (!Options.SetupCompleted || context.IsBackofficeRequest(Options.BackofficePath) || Options.Routing.NotFoundEndpoint == null)
- {
- return null;
- }
-
- return new NotFoundRoute(context)
- {
- Controller = Options.Routing.NotFoundEndpoint.Controller,
- Action = Options.Routing.NotFoundEndpoint.Action
- };
- }
-
-
///
public async Task RebuildAllRoutes()
{
@@ -253,16 +229,19 @@ namespace zero.Core.Routing
using IAsyncDocumentSession session = Store.OpenAsyncSession(app.Database);
session.Advanced.MaxNumberOfRequestsPerSession = 1000;
+ // delete all routes
+ await Store.PurgeAsync(app.Database);
+
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 Store.PurgeAsync(app.Database, $"where {nameof(Route.ProviderAlias)} = $alias", new Raven.Client.Parameters()
- {
- { "alias", provider.Alias }
- });
+ //await Store.PurgeAsync(app.Database, $"where {nameof(Route.ProviderAlias)} = $alias", new Raven.Client.Parameters()
+ //{
+ // { "alias", provider.Alias }
+ //});
// store new routes
using (BulkInsertOperation bulkInsert = Store.BulkInsert(app.Database))
@@ -355,16 +334,6 @@ namespace zero.Core.Routing
///
Task ResolveUrl(HttpContext context);
- ///
- /// Resolve a route object by passing it to the specified provider
- ///
- //Task ResolveRoute(Route route);
-
- ///
- /// Returns the endpoint which maps 404 requests
- ///
- NotFoundRoute NotFound(HttpContext context);
-
///
/// Purges all routes and rebuilds them by iterating over all registered providers
///