use HttpContext features for routing
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user