use HttpContext features for routing

This commit is contained in:
2021-09-30 10:53:01 +02:00
parent cc8e5e7c90
commit 53a87c2f7b
8 changed files with 181 additions and 89 deletions
@@ -1,9 +0,0 @@
using System.Threading.Tasks;
namespace zero.Core.Handlers
{
public interface IContextResolverHandler : IHandler
{
Task AfterResolve(IZeroContext context);
}
}
@@ -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
/// <summary>
/// Used to handle 404 routes that haven't been handled by the end user
/// </summary>
public class NotFoundSelectorPolicy : MatcherPolicy, IEndpointSelectorPolicy
{
//private readonly Lazy<Endpoint> _notFound;
private readonly EndpointDataSource _endpointDataSource;
public NotFoundSelectorPolicy(EndpointDataSource endpointDataSource)
{
//_notFound = new Lazy<Endpoint>(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<ControllerActionDescriptor>();
// return descriptor?.ControllerTypeInfo == typeof(RenderController)
// && descriptor?.ActionName == nameof(RenderController.Index);
// });
// return e;
//}
public override int Order => 0;
public bool AppliesToEndpoints(IReadOnlyList<Endpoint> 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<ControllerAttribute>();
if (controller != null)
{
return false;
}
}
// then ensure this is only applied if all endpoints are IDynamicEndpointMetadata
return endpoints.All(x => x.Metadata?.GetMetadata<IDynamicEndpointMetadata>() != null);
}
public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates)
{
if (AllInvalid(candidates))
{
//UmbracoRouteValues umbracoRouteValues = httpContext.Features.Get<UmbracoRouteValues>();
//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;
}
}
}
+9 -3
View File
@@ -19,15 +19,22 @@ namespace zero.Core.Routing
public override async ValueTask<RouteValueDictionary> TransformAsync(HttpContext httpContext, RouteValueDictionary values)
{
IResolvedRoute route = await Routes.ResolveUrl(httpContext);
IResolvedRoute route = httpContext.Features.Get<IResolvedRoute>();
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;
@@ -0,0 +1,51 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
namespace zero.Core.Utils
{
public class PrimitiveTypeCollection : ConcurrentDictionary<Type, object>, IPrimitiveTypeCollection
{
/// <summary>
/// Initializes a new instance of <see cref="PrimitiveTypeCollection"/>.
/// </summary>
public PrimitiveTypeCollection() { }
/// <inheritdoc />
public TValue Get<TValue>() => (TValue)this[typeof(TValue)];
/// <inheritdoc />
public void Set<TValue>(TValue instance) => this[typeof(TValue)] = instance;
/// <inheritdoc />
public void Remove<TValue>() => TryRemove(typeof(TValue), out _);
}
/// <summary>
/// Represents a simple collection based on type.
/// </summary>
public interface IPrimitiveTypeCollection : ICollection<KeyValuePair<Type, object>>
{
/// <summary>
/// Retrieves the requested value from the collection.
/// </summary>
/// <typeparam name="TValue">The value key.</typeparam>
/// <returns>The requested value, or null if it is not present.</returns>
TValue Get<TValue>();
/// <summary>
/// Sets the given value in the collection.
/// </summary>
/// <typeparam name="TValue">The value key.</typeparam>
/// <param name="instance">The value value.</param>
void Set<TValue>(TValue instance);
/// <summary>
/// Removes the given value from the collection.
/// </summary>
/// <typeparam name="TValue">The value key.</typeparam>
/// <param name="instance">The value value.</param>
void Remove<TValue>();
}
}
+17 -45
View File
@@ -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; }
/// <inheritdoc />
public Route Route { get; private set; }
public Route Route => ResolvedRoute?.Route;
/// <inheritdoc />
public IResolvedRoute ResolvedRoute { get; private set; }
public IResolvedRoute ResolvedRoute => HttpContextAccessor?.HttpContext?.Features.Get<IResolvedRoute>();
/// <inheritdoc />
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<string, object> _properties = new();
ConcurrentDictionary<string, IAsyncDocumentSession> _sessions = new();
public ZeroContext(IZeroOptions options, IApplicationResolver appResolver, ICultureResolver cultureResolver, ILogger<ZeroContext> logger, IZeroStore store, IHandlerHolder handler)
public ZeroContext(IZeroOptions options, IHttpContextAccessor httpContextAccessor, IApplicationResolver appResolver, ICultureResolver cultureResolver, ILogger<ZeroContext> 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<IContextResolverHandler>();
if (handler != null)
{
await handler.AfterResolve(this);
}
}
internal void SetRoute(IResolvedRoute route)
{
ResolvedRoute = route;
Route = ResolvedRoute.Route;
await CultureResolver.Resolve(this);
}
/// <inheritdoc />
public T GetProperty<T>(string key)
{
if (_properties.TryGetValue(key, out object value) && value is T)
{
return (T)value;
}
return default;
}
public T Get<T>() => ValueCollection.Get<T>();
/// <inheritdoc />
public void SetProperty(string key, object value)
{
_properties[key] = value;
}
public void Set<T>(T value) => ValueCollection.Set(value);
/// <inheritdoc />
public void RemoveProperty(string key)
{
_properties.TryRemove(key, out _);
}
public void Remove<T>() => ValueCollection.Remove<T>();
/// <inheritdoc />
@@ -223,16 +195,16 @@ namespace zero.Core
/// <summary>
/// Get a custom property from this scoped context
/// </summary>
T GetProperty<T>(string key);
T Get<T>();
/// <summary>
/// Add a custom property to this scoped context
/// </summary>
void SetProperty(string key, object value);
void Set<T>(T value);
/// <summary>
/// Remove a custom property from this scoped context
/// </summary>
void RemoveProperty(string key);
void Remove<T>();
}
}
+9 -30
View File
@@ -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<IResolvedRoute>
public abstract class ZeroController : ZeroController<DefaultResolvedRoute> { }
public abstract class ZeroController<T> : Controller where T : class, IResolvedRoute
{
}
public virtual Application Application => Context?.Application;
public abstract class ZeroController<T> : 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<IResolvedRoute>() as T);
IZeroContext _context;
public IZeroContext Context => _context ?? (_context = HttpContext?.RequestServices?.GetService<IZeroContext>());
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);
}
}
}
+4 -1
View File
@@ -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<Media, Media>();
services.AddTransient<MediaFolder, MediaFolder>();
services.AddTransient<Preview, Preview>();
services.AddTransient<Route, Route>();
services.AddTransient<Core.Routing.Route, Core.Routing.Route>();
services.AddTransient<IValidator<Application>, ApplicationValidator>();
services.AddTransient<IValidator<Country>, CountryValidator>();
@@ -114,6 +116,7 @@ namespace zero.Web.Defaults
services.AddScoped<ILinkProvider, RawLinkProvider>();
services.AddScoped<ZeroRoutesTransformer>();
services.TryAddEnumerable(ServiceDescriptor.Singleton<MatcherPolicy, NotFoundSelectorPolicy>());
services.AddScoped<IMailProvider, MailProvider>();
services.AddScoped<IMailDispatcher, FileMailDispatcher>();
@@ -19,7 +19,7 @@ namespace zero.Web
public static IZeroEndpointRouteBuilder MapZeroRoutes(this IZeroEndpointRouteBuilder endpoints)
{
endpoints.MapDynamicControllerRoute<ZeroRoutesTransformer>("{**url}", state: null, order: 10);
endpoints.MapDynamicControllerRoute<ZeroRoutesTransformer>("/{**url}", state: null, order: 10);
return endpoints;
}
}