Files
mixtape/zero.Core/Routing/RouteResolver.cs
T

166 lines
4.6 KiB
C#

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Raven.Client.Documents;
using Raven.Client.Documents.Linq;
using Raven.Client.Exceptions.Documents.Indexes;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using zero.Core.Database;
using zero.Core.Database.Indexes;
using zero.Core.Entities;
using zero.Core.Extensions;
using zero.Core.Options;
namespace zero.Core.Routing
{
public class RouteResolver : Routes, IRouteResolver
{
protected IApplicationResolver AppResolver { get; set; }
protected IZeroOptions Options { get; set; }
public RouteResolver(IZeroContext context, IZeroStore store, ILogger<Routes> logger, IEnumerable<IRouteProvider> providers, IApplicationResolver appResolver) : base(context, store, logger, providers)
{
AppResolver = appResolver;
Options = context.Options;
}
/// <inheritdoc />
public async Task<IRouteModel> ResolveUrl(HttpContext context, Application application, string path)
{
IZeroDocumentSession session = Store.Session();
path = path.Length > 1 ? path.TrimEnd(PATH_SEPERATOR) : path;
string[] pathParts = path.Trim(PATH_SEPERATOR).Split(PATH_SEPERATOR);
string[] parts = new string[pathParts.Length];
int min = parts.Length;
foreach (string pathPart in pathParts)
{
for (int i = 0; i < min; i++)
{
parts[i] += PATH_SEPERATOR + pathPart;
}
min -= 1;
}
IList<Route> routes = null;
try
{
routes = await session.Query<Route, Routes_ForResolver>()
.Where(x => (!x.AllowSuffix && x.Url == path) || (x.AllowSuffix && x.Url.In(parts)))
.Include("References[].Id")
.Include("Dependencies")
.ToListAsync();
routes = routes.Where(x => !x.Param<bool>(PageRouteProvider.PAGE_IS_FOLDER)).ToList();
}
catch (IndexDoesNotExistException ex)
{
Logger.LogError(ex, "Indexes have not been created yet");
return null;
}
Route route = routes.FirstOrDefault();
// try to get the best matching path when multiple routes are found
// our assumption is that the best path is those with the longest path parts separated by a slash
// if we still get multiple routes with equal part counts then we prefer those which do not allow a suffix
if (routes.Count > 1)
{
int maxPathParts = routes.Max(x => x.Url.Count(u => u == PATH_SEPERATOR));
IEnumerable<Route> longestRoutes = routes.Where(x => maxPathParts == x.Url.Count(u => u == PATH_SEPERATOR)).OrderBy(x => x.AllowSuffix);
if (longestRoutes.Count() > 1)
{
Logger.LogWarning("Multiple routes {routes} were found for {path}", longestRoutes.Select(x => x.Id), path);
}
route = longestRoutes.FirstOrDefault();
}
if (route == null)
{
return null;
}
return await ResolveRouteInternal(new RouteResponse()
{
Route = route,
App = application,
HttpContext = context,
Path = path
});
}
/// <inheritdoc />
public async Task<IRouteModel> ResolveUrl(HttpContext context)
{
if (!Options.SetupCompleted || context.IsBackofficeRequest(Options.BackofficePath))
{
return null;
}
Application app = await AppResolver.ResolveFromRequest(context);
string path = context.Request.Path;
if (app == null)
{
return null;
}
return await ResolveUrl(context, app, path);
}
/// <inheritdoc />
public RouteEndpoint MapEndpoint(IRouteModel route)
{
if (TryGetProvider(route.Route.ProviderAlias, out IRouteProvider provider))
{
return provider.Map(GetContext(), route);
}
return null;
}
/// <summary>
/// Call the provider which can resolve the route
/// </summary>
async Task<IRouteModel> ResolveRouteInternal(RouteResponse response)
{
if (TryGetProvider(response.Route.ProviderAlias, out IRouteProvider provider))
{
return await provider.Model(GetContext(), response);
}
return null;
}
}
public interface IRouteResolver
{
/// <summary>
/// Resolve an URL from the specified app and the path
/// </summary>
Task<IRouteModel> ResolveUrl(HttpContext context, Application application, string path);
/// <summary>
/// Resolve an URL from an http context
/// </summary>
Task<IRouteModel> ResolveUrl(HttpContext context);
/// <summary>
/// Get endpoint the route maps to
/// </summary>
RouteEndpoint MapEndpoint(IRouteModel route);
}
}