Skip to content

Commit

Permalink
Merge pull request #210 from Lombiq/issue/OSOE-657
Browse files Browse the repository at this point in the history
OSOE-657: Retrive the Admin prefix from `AdminOptions.AdminUrlPrefix` instead of hardcoding it
  • Loading branch information
Psichorex authored Jul 26, 2023
2 parents 1021a9e + 9a32ac5 commit f06eee2
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 39 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
using Lombiq.HelpfulLibraries.OrchardCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using OrchardCore.ContentManagement;
using OrchardCore.Environment.Extensions;
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
Expand Down Expand Up @@ -62,11 +60,10 @@ public static string Action<TController>(
params (string Key, object Value)[] additionalArguments)
where TController : ControllerBase
{
var provider = httpContext.RequestServices.GetService<ITypeFeatureProvider>();
var route = TypedRoute.CreateFromExpression(
actionExpression,
additionalArguments,
provider);
httpContext.RequestServices);
return route.ToString(httpContext);
}

Expand Down
57 changes: 34 additions & 23 deletions Lombiq.HelpfulLibraries.OrchardCore/Mvc/TypedRoute.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using OrchardCore.Admin;
using OrchardCore.Environment.Extensions;
Expand All @@ -27,13 +29,13 @@ public class TypedRoute
private readonly MethodInfo _action;
private readonly List<KeyValuePair<string, string>> _arguments;

private readonly Lazy<bool> _isAdminLazy;
private readonly Lazy<string> _routeLazy;
private readonly string _prefix = "/";
private readonly string _route;

public TypedRoute(
private TypedRoute(
MethodInfo action,
IEnumerable<KeyValuePair<string, string>> arguments,
ITypeFeatureProvider typeFeatureProvider = null)
IServiceProvider serviceProvider = null)
{
if (action?.DeclaringType is not { } controller)
{
Expand All @@ -53,6 +55,7 @@ public TypedRoute(
}
else
{
var typeFeatureProvider = serviceProvider?.GetService<ITypeFeatureProvider>();
_area = typeFeatureProvider?.GetFeatureForDependency(controller).Extension.Id ??
controller.Assembly.GetCustomAttribute<ModuleNameAttribute>()?.Name ??
controller.Assembly.GetCustomAttribute<ModuleMarkerAttribute>()?.Name ??
Expand All @@ -61,13 +64,15 @@ public TypedRoute(
"you sure this controller belongs to an Orchard Core module?");
}

_isAdminLazy = new Lazy<bool>(() =>
controller.GetCustomAttribute<AdminAttribute>() != null ||
action.GetCustomAttribute<AdminAttribute>() != null);
_routeLazy = new Lazy<string>(() =>
action.GetCustomAttribute<RouteAttribute>()?.Template is { } route && !string.IsNullOrWhiteSpace(route)
? GetRoute(route)
: $"{_area}/{controller.ControllerName()}/{action.GetCustomAttribute<ActionNameAttribute>()?.Name ?? action.Name}");
var isAdmin = controller.GetCustomAttribute<AdminAttribute>() != null || action.GetCustomAttribute<AdminAttribute>() != null;
if (isAdmin && action.GetCustomAttribute(typeof(RouteAttribute)) == null)
{
_prefix = $"/{(serviceProvider?.GetService<IOptions<AdminOptions>>()?.Value ?? new AdminOptions()).AdminUrlPrefix}/";
}

_route = action.GetCustomAttribute<RouteAttribute>()?.Template is { } route && !string.IsNullOrWhiteSpace(route)
? GetRoute(route)
: $"{_area}/{controller.ControllerName()}/{action.GetCustomAttribute<ActionNameAttribute>()?.Name ?? action.Name}";
}

/// <summary>
Expand All @@ -91,15 +96,11 @@ public string ToString(HttpContext httpContext)
/// </summary>
public override string ToString()
{
var isAdminWithoutRoute = _isAdminLazy.Value && _action.GetCustomAttribute(typeof(RouteAttribute)) == null;

var prefix = isAdminWithoutRoute ? "/Admin/" : "/";
var route = _routeLazy.Value;
var arguments = _arguments.Any()
? "?" + string.Join('&', _arguments.Select((key, value) => $"{key}={WebUtility.UrlEncode(value)}"))
: string.Empty;

return prefix + route + arguments;
return _prefix + _route + arguments;
}

/// <summary>
Expand Down Expand Up @@ -144,12 +145,12 @@ public static implicit operator RouteValueDictionary(TypedRoute route) =>
public static TypedRoute CreateFromExpression<TController>(
Expression<Action<TController>> actionExpression,
IEnumerable<(string Key, object Value)> additionalArguments,
ITypeFeatureProvider typeFeatureProvider = null)
IServiceProvider serviceProvider = null)
where TController : ControllerBase =>
CreateFromExpression(
actionExpression,
additionalArguments.Select((key, value) => new KeyValuePair<string, string>(key, value.ToString())),
typeFeatureProvider);
serviceProvider);

/// <summary>
/// Creates and returns a new <see cref="TypedRoute"/> using the provided <paramref name="action"/> expression,
Expand All @@ -159,8 +160,8 @@ public static TypedRoute CreateFromExpression<TController>(
/// <param name="additionalArguments">Additional arguments to add to the route and the key in the cache.</param>
public static TypedRoute CreateFromExpression<TController>(
Expression<Action<TController>> action,
IEnumerable<KeyValuePair<string, string>> additionalArguments,
ITypeFeatureProvider typeFeatureProvider = null)
IEnumerable<KeyValuePair<string, string>> additionalArguments = null,
IServiceProvider serviceProvider = null)
where TController : ControllerBase
{
Expression actionExpression = action;
Expand All @@ -178,21 +179,31 @@ public static TypedRoute CreateFromExpression<TController>(
methodParameters[index].Name,
ValueToString(Expression.Lambda(argument).Compile().DynamicInvoke())))
.Where(pair => pair.Value != null)
.Concat(additionalArguments)
.Concat(additionalArguments ?? Enumerable.Empty<KeyValuePair<string, string>>())
.ToList();

var key = string.Join(
separator: '|',
typeof(TController),
typeof(TController).FullName,
operation.Method,
string.Join(',', arguments.Select(pair => $"{pair.Key}={pair.Value}")));

if (serviceProvider?.GetService<IMemoryCache>() is { } cache)
{
return cache.GetOrCreate(
key,
_ => new TypedRoute(
operation.Method,
arguments,
serviceProvider));
}

return _cache.GetOrAdd(
key,
_ => new TypedRoute(
operation.Method,
arguments,
typeFeatureProvider));
serviceProvider));
}

private static string ValueToString(object value) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
using Lombiq.HelpfulLibraries.OrchardCore.Mvc;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using OrchardCore.Environment.Extensions;
using OrchardCore.Navigation;
using System;
using System.Linq.Expressions;
Expand Down Expand Up @@ -34,11 +32,10 @@ public static NavigationItemBuilder Action<TContext>(
params (string Key, object Value)[] additionalArguments)
where TContext : ControllerBase
{
var provider = httpContext.RequestServices.GetService<ITypeFeatureProvider>();
var route = TypedRoute.CreateFromExpression(
actionExpression,
additionalArguments,
provider);
httpContext.RequestServices);

return builder.Action(route);
}
Expand Down
44 changes: 36 additions & 8 deletions Lombiq.HelpfulLibraries.Tests/UnitTests/Models/TypedRouteTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using Lombiq.HelpfulLibraries.OrchardCore.Mvc;
using Lombiq.HelpfulLibraries.Tests.Controllers;
using Microsoft.Extensions.DependencyInjection;
using OrchardCore.Admin;
using OrchardCore.Environment.Extensions;
using OrchardCore.Environment.Extensions.Features;
using Shouldly;
Expand All @@ -20,21 +22,47 @@ public void TypedRouteShouldWorkCorrectly(
(string Name, object Value)[] additional,
string tenantName)
{
const string id = "Lombiq.HelpfulLibraries.Tests";
var route = TypedRoute.CreateFromExpression(
actionExpression,
additional,
CreateServiceProvider());
route.ToString(tenantName).ShouldBe(expected);
}

[Fact]
public void CustomizedAdminPrefixShouldBeUsed()
{
const string expected = "/CustomAdmin/Lombiq.HelpfulLibraries.Tests/RouteTest/Baz";

var route = TypedRoute.CreateFromExpression(
AsExpression(controller => controller.Baz()),
serviceProvider: CreateServiceProvider(services => services
.Configure<AdminOptions>(options => options.AdminUrlPrefix = " /CustomAdmin /")));
route.ToString(tenantName: string.Empty).ShouldBe(expected);
}

private static IServiceProvider CreateServiceProvider(Action<ServiceCollection> configure = null)
{
var services = new ServiceCollection();

const string feature = "Lombiq.HelpfulLibraries.Tests";
var typeFeatureProvider = new TypeFeatureProvider();
typeFeatureProvider.TryAdd(typeof(RouteTestController), new FeatureInfo(id, new ExtensionInfo(id)));
typeFeatureProvider.TryAdd(typeof(RouteTestController), new FeatureInfo(feature, new ExtensionInfo(feature)));
services.AddSingleton<ITypeFeatureProvider>(typeFeatureProvider);

var route = TypedRoute.CreateFromExpression(actionExpression, additional, typeFeatureProvider);
route.ToString(tenantName).ShouldBe(expected);
services.AddMemoryCache();

configure?.Invoke(services);

return services.BuildServiceProvider();
}

private static Expression<Action<RouteTestController>> AsExpression(
Expression<Action<RouteTestController>> expression) =>
expression;

public static IEnumerable<object[]> TypedRouteShouldWorkCorrectlyData()
{
static Expression<Action<RouteTestController>> AsExpression(
Expression<Action<RouteTestController>> expression) =>
expression;

var noMoreArguments = Array.Empty<(string Name, object Value)>();
var noTenant = string.Empty;
var someTenant = "SomeTenant";
Expand Down

0 comments on commit f06eee2

Please sign in to comment.