diff --git a/zero.Core/Routing/Configuration/RoutingEndpointOptions.cs b/zero.Core/Routing/Configuration/RoutingEndpointOptions.cs index 5ffc342e..e6ce1da7 100644 --- a/zero.Core/Routing/Configuration/RoutingEndpointOptions.cs +++ b/zero.Core/Routing/Configuration/RoutingEndpointOptions.cs @@ -7,11 +7,11 @@ public class RoutingEndpointOptions class ResolverMap { public Type Type { get; set; } - public Func Impl { get; set; } + public Func Impl { get; set; } } - public void Add(Func resolver) where T : class, IRouteModel + public void Add(Func resolver) where T : class, IRouteModel { Resolvers.Add(new() { @@ -20,7 +20,7 @@ public class RoutingEndpointOptions }); } - public void Replace(Func resolver) where T : class, IRouteModel + public void Replace(Func resolver) where T : class, IRouteModel { Remove(); Add(resolver); @@ -32,17 +32,17 @@ public class RoutingEndpointOptions Resolvers.RemoveWhere(x => x.Type == type); } - public Func Get() where T : IRouteModel => Get(typeof(T)); + public Func Get() where T : IRouteModel => Get(typeof(T)); - public IEnumerable> GetAll() where T : IRouteModel => GetAll(typeof(T)); + public IEnumerable> GetAll() where T : IRouteModel => GetAll(typeof(T)); - public Func Get(Type type) + public Func Get(Type type) { ResolverMap map = Resolvers.LastOrDefault(x => x.Type == type); return map?.Impl; } - public IEnumerable> GetAll(Type type) + public IEnumerable> GetAll(Type type) { IEnumerable maps = Resolvers.Where(x => x.Type == type); return maps.Select(map => map.Impl).Where(x => x != null); diff --git a/zero.Core/Routing/Configuration/RoutingOptions.cs b/zero.Core/Routing/Configuration/RoutingOptions.cs index 96114d6c..22589514 100644 --- a/zero.Core/Routing/Configuration/RoutingOptions.cs +++ b/zero.Core/Routing/Configuration/RoutingOptions.cs @@ -5,7 +5,7 @@ public class RoutingOptions public RoutingOptions() { PageRouteIdBuilder = new PageRouteIdBuilder(); - DefaultEndpoint = new("ZeroFrontend", "Index"); + DefaultEndpoint = new ControllerRouteEndpoint("ZeroFrontend", "Index"); EndpointResolvers = new(); PageResolvers = new(); //ErrorReexecutionPath = "/error"; @@ -16,10 +16,10 @@ public class RoutingOptions public IPageRouteIdBuilder PageRouteIdBuilder { get; set; } - public RouteEndpoint NotFoundEndpoint { get; set; } + public IRouteEndpoint NotFoundEndpoint { get; set; } - public RouteEndpoint DefaultEndpoint { get; set; } + public IRouteEndpoint DefaultEndpoint { get; set; } /// /// diff --git a/zero.Core/Routing/Entities/RouteEndpoint.cs b/zero.Core/Routing/Entities/RouteEndpoint.cs index c58dc22a..acf2cf2b 100644 --- a/zero.Core/Routing/Entities/RouteEndpoint.cs +++ b/zero.Core/Routing/Entities/RouteEndpoint.cs @@ -1,23 +1,47 @@ -namespace zero.Routing; +using Microsoft.AspNetCore.Routing; -public class RouteEndpoint +namespace zero.Routing; + +public class ControllerRouteEndpoint : IRouteEndpoint { - public RouteEndpoint(string controller, string action = "Index") + public string Controller { get; set; } + + public string Action { get; set; } + + public ControllerRouteEndpoint(string controller, string action = "Index") { - Type = RouteEndpointType.Controller; Controller = controller; Action = action; } - public RouteEndpointType Type { get; set; } - - public string Controller { get; set; } - - public string Action { get; set; } + public virtual void Apply(RouteValueDictionary values) + { + values["controller"] = Controller; + values["action"] = Action; + } } -public enum RouteEndpointType +public class PageRouteEndpoint : IRouteEndpoint { - Controller = 0 + public string Page { get; set; } + + public PageRouteEndpoint(string page) + { + Page = page; + } + + public virtual void Apply(RouteValueDictionary values) + { + values["page"] = Page; + } } + + +public interface IRouteEndpoint +{ + /// + /// Append values to the RouteValueDictionary + /// + void Apply(RouteValueDictionary values); +} \ No newline at end of file diff --git a/zero.Core/Routing/RouteResolver.cs b/zero.Core/Routing/RouteResolver.cs index 5e045dca..8ae3479e 100644 --- a/zero.Core/Routing/RouteResolver.cs +++ b/zero.Core/Routing/RouteResolver.cs @@ -22,7 +22,7 @@ public class RouteResolver : Routes, IRouteResolver /// public async Task ResolveUrl(HttpContext context, Application application, string path) { - IZeroDocumentSession session = Store.Session(); + IZeroDocumentSession session = Store.Session(application.Database); path = path.Length > 1 ? path.TrimEnd(PATH_SEPERATOR) : path; @@ -98,7 +98,7 @@ public class RouteResolver : Routes, IRouteResolver return null; } - Application app = await AppResolver.ResolveFromRequest(context); + Application app = await AppResolver.Resolve(context, Context.BackofficeUser); string path = context.Request.Path; if (app == null) @@ -111,7 +111,7 @@ public class RouteResolver : Routes, IRouteResolver /// - public RouteEndpoint MapEndpoint(IRouteModel route) + public IRouteEndpoint MapEndpoint(IRouteModel route) { if (TryGetProvider(route.Route.ProviderAlias, out IRouteProvider provider)) { @@ -152,5 +152,5 @@ public interface IRouteResolver /// /// Get endpoint the route maps to /// - RouteEndpoint MapEndpoint(IRouteModel route); + IRouteEndpoint MapEndpoint(IRouteModel route); } \ No newline at end of file diff --git a/zero.Core/Routing/ZeroEnpointRouteBuilderExtensions.cs b/zero.Core/Routing/ZeroEnpointRouteBuilderExtensions.cs index 76cc63a9..b02560e7 100644 --- a/zero.Core/Routing/ZeroEnpointRouteBuilderExtensions.cs +++ b/zero.Core/Routing/ZeroEnpointRouteBuilderExtensions.cs @@ -23,7 +23,8 @@ public static class ZeroEndpointRouteBuilderExtensions public static IZeroEndpointRouteBuilder MapZeroRoutes(this IZeroEndpointRouteBuilder endpoints) { - endpoints.MapDynamicControllerRoute("/{**url}", state: null, order: 10); + endpoints.MapDynamicControllerRoute("/{**url}", state: ZeroRoutesTransformerType.Controller, order: 10); + endpoints.MapDynamicPageRoute("/{**url}", state: ZeroRoutesTransformerType.Page, order: 11); return endpoints; } } diff --git a/zero.Core/Routing/ZeroRouteProvider.cs b/zero.Core/Routing/ZeroRouteProvider.cs index b3892db6..fdb5e600 100644 --- a/zero.Core/Routing/ZeroRouteProvider.cs +++ b/zero.Core/Routing/ZeroRouteProvider.cs @@ -107,15 +107,15 @@ public abstract class ZeroRouteProvider : IRouteProvider public virtual string Id(ISupportsRouting model) => "routes." + Alias + "." + Key(model); /// - public virtual RouteEndpoint Map(RoutingContext context, IRouteModel route) + public virtual IRouteEndpoint Map(RoutingContext context, IRouteModel route) { RoutingOptions options = context.Context.Options.For(); - IEnumerable> resolvers = options.EndpointResolvers.GetAll(route.GetType()); + IEnumerable> resolvers = options.EndpointResolvers.GetAll(route.GetType()); - foreach (Func resolver in resolvers.Reverse()) + foreach (Func resolver in resolvers.Reverse()) { - RouteEndpoint endpoint = resolver(route); + IRouteEndpoint endpoint = resolver(route); if (endpoint != null) { @@ -232,7 +232,7 @@ public interface IRouteProvider /// /// Map a route model to an endpoint /// - RouteEndpoint Map(RoutingContext context, IRouteModel route); + IRouteEndpoint Map(RoutingContext context, IRouteModel route); /// /// Find a persisted route for an entity diff --git a/zero.Core/Routing/ZeroRoutesTransformer.cs b/zero.Core/Routing/ZeroRoutesTransformer.cs index f0567c24..bf6a6333 100644 --- a/zero.Core/Routing/ZeroRoutesTransformer.cs +++ b/zero.Core/Routing/ZeroRoutesTransformer.cs @@ -1,28 +1,53 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Logging; namespace zero.Routing; public class ZeroRoutesTransformer : DynamicRouteValueTransformer { readonly IRouteResolver RouteResolver; + readonly ILogger Logger; - public ZeroRoutesTransformer(IRouteResolver routeResolver) + public ZeroRoutesTransformer(IRouteResolver routeResolver, ILogger logger) { RouteResolver = routeResolver; - } + Logger = logger; + } public override async ValueTask TransformAsync(HttpContext httpContext, RouteValueDictionary values) { - IRouteModel route = httpContext.Features.Get(); - - if (route != null) - { - return null; - } + if (State == null || State is not ZeroRoutesTransformerType) + { + throw new ArgumentNullException(nameof(State), "Pass a ZeroRoutesTransformerType instance to the state parameter in the dynamic route transformer"); + } + ZeroRoutesTransformerType type = (ZeroRoutesTransformerType)State; + + IRouteEndpoint endpoint = httpContext.Features.Get(); + IRouteModel route = httpContext.Features.Get(); + + // check if a resolved endpoint routes to a Razor Page. + // if the Page transformer can't find an endpoint it exits as the Controller transformer already ran before. + if (type == ZeroRoutesTransformerType.Page) + { + if (endpoint is PageRouteEndpoint) + { + return Apply(httpContext, values, endpoint, type); + } + + return null; + } + + // a route was already set, therefore we prevent another execution + if (route != null) + { + return null; + } + + // resolve route from URL route = await RouteResolver.ResolveUrl(httpContext); if (route == null) @@ -30,17 +55,52 @@ public class ZeroRoutesTransformer : DynamicRouteValueTransformer return null; } - httpContext.Features.Set(route); + httpContext.Features.Set(route); - RouteEndpoint endpoint = RouteResolver.MapEndpoint(route); + // map the route to an endpoint + // an endpoint can be a controller action or a Razor page. + // in case it is a Razor page we exit here and let the transformer run again within the page context (MapDynamicPageRoute). + endpoint = RouteResolver.MapEndpoint(route); if (endpoint == null) { return null; } - values["controller"] = endpoint.Controller; - values["action"] = endpoint.Action; + httpContext.Features.Set(endpoint); + + if (endpoint is PageRouteEndpoint) + { + return null; + } + + return Apply(httpContext, values, endpoint, type); + } + + + protected virtual RouteValueDictionary Apply(HttpContext httpContext, RouteValueDictionary values, IRouteEndpoint endpoint, ZeroRoutesTransformerType type) + { + endpoint.Apply(values); + + HashSet logValues = new(); + + foreach ((string key, object value) in values) + { + if (key != "url" && value != null && (value is not string || ((string)value).HasValue())) + { + logValues.Add(key + ": " + value); + } + } + + Logger.LogDebug("Routed {url} to [{values}] (transformer: {type})", httpContext.Request.Path, logValues, type); + return values; } } + + +public enum ZeroRoutesTransformerType +{ + Controller = 0, + Page = 1 +} \ No newline at end of file diff --git a/zero.Core/Routing/ZeroRoutingModule.cs b/zero.Core/Routing/ZeroRoutingModule.cs index 8f43f3fd..e324162b 100644 --- a/zero.Core/Routing/ZeroRoutingModule.cs +++ b/zero.Core/Routing/ZeroRoutingModule.cs @@ -18,7 +18,7 @@ internal class ZeroRoutingModule : ZeroModule services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddTransient(); services.TryAddEnumerable(ServiceDescriptor.Singleton()); services.AddScoped(); diff --git a/zero.Core/ZeroApplicationBuilder.cs b/zero.Core/ZeroApplicationBuilder.cs index 83a285ce..13cbc150 100644 --- a/zero.Core/ZeroApplicationBuilder.cs +++ b/zero.Core/ZeroApplicationBuilder.cs @@ -14,8 +14,8 @@ public class ZeroApplicationBuilder : IZeroApplicationBuilder App = app; App.UseStaticFiles(); //App.UseExceptionHandler("/zero/api/error"); - App.UseRouting(); App.UseMiddleware(); + App.UseRouting(); App.UseAuthentication(); App.UseAuthorization(); diff --git a/zero.Core/ZeroBuilder.cs b/zero.Core/ZeroBuilder.cs index dab3d662..b85c4e37 100644 --- a/zero.Core/ZeroBuilder.cs +++ b/zero.Core/ZeroBuilder.cs @@ -31,6 +31,7 @@ public class ZeroBuilder setupAction?.Invoke(StartupOptions); services.AddControllers(); + services.AddRazorPages(); // adds and discovers additional and built-in assemblies new AssemblyDiscovery(Mvc).Execute(StartupOptions.AssemblyDiscoveryRules);