Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OSOE-657: Retrive the Admin prefix from AdminOptions.AdminUrlPrefix instead of hardcoding it #210

Merged
merged 6 commits into from
Jul 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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