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; } }