diff --git a/zero.Core/Handlers/IContextResolverHandler.cs b/zero.Core/Handlers/IContextResolverHandler.cs
deleted file mode 100644
index b1cf12f5..00000000
--- a/zero.Core/Handlers/IContextResolverHandler.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using System.Threading.Tasks;
-
-namespace zero.Core.Handlers
-{
- public interface IContextResolverHandler : IHandler
- {
- Task AfterResolve(IZeroContext context);
- }
-}
diff --git a/zero.Core/Routing/NotFoundMatcherPolicy.cs b/zero.Core/Routing/NotFoundMatcherPolicy.cs
new file mode 100644
index 00000000..2a115be4
--- /dev/null
+++ b/zero.Core/Routing/NotFoundMatcherPolicy.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.AspNetCore.Routing;
+using Microsoft.AspNetCore.Routing.Matching;
+
+namespace zero.Core.Routing
+{
+ // see https://github.com/umbraco/Umbraco-CMS/blob/5bfab13dc5a268714aad2426a2b68ab5561a6407/src/Umbraco.Web.Website/Routing/NotFoundSelectorPolicy.cs
+
+ ///
+ /// Used to handle 404 routes that haven't been handled by the end user
+ ///
+ public class NotFoundSelectorPolicy : MatcherPolicy, IEndpointSelectorPolicy
+ {
+ //private readonly Lazy _notFound;
+ private readonly EndpointDataSource _endpointDataSource;
+
+ public NotFoundSelectorPolicy(EndpointDataSource endpointDataSource)
+ {
+ //_notFound = new Lazy(GetNotFoundEndpoint);
+ _endpointDataSource = endpointDataSource;
+ }
+
+ // return the endpoint for the RenderController.Index action.
+ //private Endpoint GetNotFoundEndpoint()
+ //{
+ // Endpoint e = _endpointDataSource.Endpoints.First(x =>
+ // {
+ // // return the endpoint for the RenderController.Index action.
+ // ControllerActionDescriptor descriptor = x.Metadata?.GetMetadata();
+ // return descriptor?.ControllerTypeInfo == typeof(RenderController)
+ // && descriptor?.ActionName == nameof(RenderController.Index);
+ // });
+ // return e;
+ //}
+
+ public override int Order => 0;
+
+ public bool AppliesToEndpoints(IReadOnlyList endpoints)
+ {
+ // Don't apply this filter to any endpoint group that is a controller route
+ // i.e. only dynamic routes.
+ foreach (Endpoint endpoint in endpoints)
+ {
+ ControllerAttribute controller = endpoint.Metadata?.GetMetadata();
+ if (controller != null)
+ {
+ return false;
+ }
+ }
+
+ // then ensure this is only applied if all endpoints are IDynamicEndpointMetadata
+ return endpoints.All(x => x.Metadata?.GetMetadata() != null);
+ }
+
+ public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates)
+ {
+ if (AllInvalid(candidates))
+ {
+ //UmbracoRouteValues umbracoRouteValues = httpContext.Features.Get();
+ //if (umbracoRouteValues?.PublishedRequest != null
+ // && !umbracoRouteValues.PublishedRequest.HasPublishedContent()
+ // && umbracoRouteValues.PublishedRequest.ResponseStatusCode == StatusCodes.Status404NotFound)
+ //{
+ // // not found/404
+ // httpContext.SetEndpoint(_notFound.Value);
+ //}
+ }
+
+ return Task.CompletedTask;
+ }
+
+ private static bool AllInvalid(CandidateSet candidates)
+ {
+ for (int i = 0; i < candidates.Count; i++)
+ {
+ if (candidates.IsValidCandidate(i))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+}
diff --git a/zero.Core/Routing/ZeroRoutesTransformer.cs b/zero.Core/Routing/ZeroRoutesTransformer.cs
index 0539ef07..45418373 100644
--- a/zero.Core/Routing/ZeroRoutesTransformer.cs
+++ b/zero.Core/Routing/ZeroRoutesTransformer.cs
@@ -19,15 +19,22 @@ namespace zero.Core.Routing
public override async ValueTask TransformAsync(HttpContext httpContext, RouteValueDictionary values)
{
- IResolvedRoute route = await Routes.ResolveUrl(httpContext);
+ IResolvedRoute route = httpContext.Features.Get();
+
+ if (route != null)
+ {
+ return null;
+ }
- (Zero as ZeroContext).SetRoute(route);
+ route = await Routes.ResolveUrl(httpContext);
if (route == null)
{
return null;
}
+ httpContext.Features.Set(route);
+
RouteProviderEndpoint endpoint = Routes.MapEndpoint(route);
if (endpoint == null)
@@ -35,7 +42,6 @@ namespace zero.Core.Routing
return null;
}
- values["zero.route"] = route;
values["controller"] = endpoint.Controller;
values["action"] = endpoint.Action;
return values;
diff --git a/zero.Core/Utils/PrimitiveTypeCollection.cs b/zero.Core/Utils/PrimitiveTypeCollection.cs
new file mode 100644
index 00000000..d1b0a6d8
--- /dev/null
+++ b/zero.Core/Utils/PrimitiveTypeCollection.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+
+namespace zero.Core.Utils
+{
+ public class PrimitiveTypeCollection : ConcurrentDictionary, IPrimitiveTypeCollection
+ {
+ ///
+ /// Initializes a new instance of .
+ ///
+ public PrimitiveTypeCollection() { }
+
+ ///
+ public TValue Get() => (TValue)this[typeof(TValue)];
+
+ ///
+ public void Set(TValue instance) => this[typeof(TValue)] = instance;
+
+ ///
+ public void Remove() => TryRemove(typeof(TValue), out _);
+ }
+
+
+ ///
+ /// Represents a simple collection based on type.
+ ///
+ public interface IPrimitiveTypeCollection : ICollection>
+ {
+ ///
+ /// Retrieves the requested value from the collection.
+ ///
+ /// The value key.
+ /// The requested value, or null if it is not present.
+ TValue Get();
+
+ ///
+ /// Sets the given value in the collection.
+ ///
+ /// The value key.
+ /// The value value.
+ void Set(TValue instance);
+
+ ///
+ /// Removes the given value from the collection.
+ ///
+ /// The value key.
+ /// The value value.
+ void Remove();
+ }
+}
diff --git a/zero.Core/ZeroContext.cs b/zero.Core/ZeroContext.cs
index df1ce8a1..75e676b8 100644
--- a/zero.Core/ZeroContext.cs
+++ b/zero.Core/ZeroContext.cs
@@ -12,6 +12,7 @@ using zero.Core.Extensions;
using zero.Core.Handlers;
using zero.Core.Options;
using zero.Core.Routing;
+using zero.Core.Utils;
namespace zero.Core
{
@@ -33,10 +34,10 @@ namespace zero.Core
public IZeroOptions Options { get; protected set; }
///
- public Route Route { get; private set; }
+ public Route Route => ResolvedRoute?.Route;
///
- public IResolvedRoute ResolvedRoute { get; private set; }
+ public IResolvedRoute ResolvedRoute => HttpContextAccessor?.HttpContext?.Features.Get();
///
public IZeroStore Store { get; private set; }
@@ -50,15 +51,17 @@ namespace zero.Core
protected IHandlerHolder Handler { get; private set; }
+ protected IHttpContextAccessor HttpContextAccessor { get; private set; }
+
+ protected IPrimitiveTypeCollection ValueCollection { get; private set; }
+
bool _resolved = false;
- ConcurrentDictionary _properties = new();
-
ConcurrentDictionary _sessions = new();
- public ZeroContext(IZeroOptions options, IApplicationResolver appResolver, ICultureResolver cultureResolver, ILogger logger, IZeroStore store, IHandlerHolder handler)
+ public ZeroContext(IZeroOptions options, IHttpContextAccessor httpContextAccessor, IApplicationResolver appResolver, ICultureResolver cultureResolver, ILogger logger, IZeroStore store, IHandlerHolder handler)
{
Options = options;
AppResolver = appResolver;
@@ -66,6 +69,8 @@ namespace zero.Core
Logger = logger;
Store = store;
Handler = handler;
+ ValueCollection = new PrimitiveTypeCollection();
+ HttpContextAccessor = httpContextAccessor;
}
@@ -109,53 +114,20 @@ namespace zero.Core
Store.ResolvedDatabase = Application.Database;
// set current culture
- await CultureResolver.Resolve(this);
-
- // resolve request route
- if (IsBackofficeRequest is false && context.Request.RouteValues.TryGetValue("zero.route", out object route))
- {
- ResolvedRoute = (IResolvedRoute)route;
- Route = ResolvedRoute.Route;
- }
-
- IContextResolverHandler handler = Handler.Get();
- if (handler != null)
- {
- await handler.AfterResolve(this);
- }
- }
-
-
- internal void SetRoute(IResolvedRoute route)
- {
- ResolvedRoute = route;
- Route = ResolvedRoute.Route;
+ await CultureResolver.Resolve(this);
}
///
- public T GetProperty(string key)
- {
- if (_properties.TryGetValue(key, out object value) && value is T)
- {
- return (T)value;
- }
- return default;
- }
+ public T Get() => ValueCollection.Get();
///
- public void SetProperty(string key, object value)
- {
- _properties[key] = value;
- }
+ public void Set(T value) => ValueCollection.Set(value);
///
- public void RemoveProperty(string key)
- {
- _properties.TryRemove(key, out _);
- }
+ public void Remove() => ValueCollection.Remove();
///
@@ -223,16 +195,16 @@ namespace zero.Core
///
/// Get a custom property from this scoped context
///
- T GetProperty(string key);
+ T Get();
///
/// Add a custom property to this scoped context
///
- void SetProperty(string key, object value);
+ void Set(T value);
///
/// Remove a custom property from this scoped context
///
- void RemoveProperty(string key);
+ void Remove();
}
}
diff --git a/zero.Web/Controllers/ZeroController.cs b/zero.Web/Controllers/ZeroController.cs
index bb5d5f09..01a96a99 100644
--- a/zero.Web/Controllers/ZeroController.cs
+++ b/zero.Web/Controllers/ZeroController.cs
@@ -1,43 +1,22 @@
using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.Mvc.Filters;
-using System;
-using zero.Core.Api;
-using zero.Core.Entities;
-using zero.Core.Routing;
using Microsoft.Extensions.DependencyInjection;
using zero.Core;
+using zero.Core.Entities;
+using zero.Core.Routing;
namespace zero.Web.Controllers
{
- public abstract class ZeroController : ZeroController
+ public abstract class ZeroController : ZeroController { }
+
+
+ public abstract class ZeroController : Controller where T : class, IResolvedRoute
{
-
- }
+ public virtual Application Application => Context?.Application;
-
- public abstract class ZeroController : Controller where T : IResolvedRoute
- {
- public Application Application { get; set; }
-
- public virtual T Route { get; set; }
+ T _route;
+ public virtual T Route => _route ?? (_route = HttpContext.Features.Get() as T);
IZeroContext _context;
public IZeroContext Context => _context ?? (_context = HttpContext?.RequestServices?.GetService());
-
-
- public override void OnActionExecuting(ActionExecutingContext filterContext)
- {
- Application = Context?.Application;
-
- if (filterContext.RouteData.Values.TryGetValue("zero.route", out object route))
- {
- if (!(route is T))
- {
- throw new InvalidCastException($"Could not cast IResolvedRoute to {typeof(T)}");
- }
- Route = (T)route;
- }
- base.OnActionExecuting(filterContext);
- }
}
}
diff --git a/zero.Web/Defaults/ZeroBackofficePlugin.cs b/zero.Web/Defaults/ZeroBackofficePlugin.cs
index ad15cb66..db851b17 100644
--- a/zero.Web/Defaults/ZeroBackofficePlugin.cs
+++ b/zero.Web/Defaults/ZeroBackofficePlugin.cs
@@ -1,6 +1,8 @@
using FluentValidation;
+using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
using zero.Core;
using zero.Core.Api;
using zero.Core.Collections;
@@ -67,7 +69,7 @@ namespace zero.Web.Defaults
services.AddTransient();
services.AddTransient();
services.AddTransient();
- services.AddTransient();
+ services.AddTransient();
services.AddTransient, ApplicationValidator>();
services.AddTransient, CountryValidator>();
@@ -114,6 +116,7 @@ namespace zero.Web.Defaults
services.AddScoped();
services.AddScoped();
+ services.TryAddEnumerable(ServiceDescriptor.Singleton());
services.AddScoped();
services.AddScoped();
diff --git a/zero.Web/ZeroEnpointRouteBuilderExtensions.cs b/zero.Web/ZeroEnpointRouteBuilderExtensions.cs
index eb7ddc23..13c09a0d 100644
--- a/zero.Web/ZeroEnpointRouteBuilderExtensions.cs
+++ b/zero.Web/ZeroEnpointRouteBuilderExtensions.cs
@@ -19,7 +19,7 @@ namespace zero.Web
public static IZeroEndpointRouteBuilder MapZeroRoutes(this IZeroEndpointRouteBuilder endpoints)
{
- endpoints.MapDynamicControllerRoute("{**url}", state: null, order: 10);
+ endpoints.MapDynamicControllerRoute("/{**url}", state: null, order: 10);
return endpoints;
}
}