Skip to content

Commit

Permalink
Merge pull request #229 from Lombiq/issue/TDEAL-8
Browse files Browse the repository at this point in the history
TDEAL-8: HttpRequestExtensions and other improvements
  • Loading branch information
barthamark authored Dec 26, 2023
2 parents 8db1b9b + 3299584 commit 38cfe73
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using Lombiq.HelpfulLibraries.Common.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace Microsoft.AspNetCore.Http;

public static class HttpRequestExtensions
{
/// <summary>
/// Returns the query string without the leading <c>?</c> and without the keys in <paramref name="keysToExclude"/>.
/// </summary>
public static string GetQueryWithout(this HttpRequest request, params string[] keysToExclude)
{
var query = HttpUtility.ParseQueryString(request.QueryString.Value ?? string.Empty);
return string.Join('&', query
.AllKeys
.Where(key => key != null && !keysToExclude.Exists(key.EqualsOrdinalIgnoreCase))
.Select(key => $"{HttpUtility.UrlEncode(key)}={HttpUtility.UrlEncode(query[key] ?? string.Empty)}"));
}

/// <summary>
/// Returns the current URL but appends a new query string entry.
/// </summary>
public static string GetLinkWithAdditionalQuery(this HttpRequest request, string queryString, string key, object value)
{
queryString ??= string.Empty;
if (queryString.StartsWith('?')) queryString = queryString[1..];

var pageQuery = string.IsNullOrEmpty(queryString)
? StringHelper.CreateInvariant($"{key}={value}")
: StringHelper.CreateInvariant($"&{key}={value}");
return $"{request.PathBase}{request.Path}?{queryString}{pageQuery}";
}

/// <summary>
/// Returns the current URL but appends a new query string entry.
/// </summary>
public static string GetLinkWithAdditionalQuery(this HttpRequest request, string key, object value) =>
request.GetLinkWithAdditionalQuery(request.QueryString.Value, key, value);

/// <summary>
/// Returns the current URL excluding any existing query string entry with the key <paramref name="key"/>, and with
/// a new <paramref name="key"/>-<paramref name="value"/> entry appended.
/// </summary>
public static string GetLinkWithDifferentQuery(this HttpRequest request, string key, object value) =>
request.GetLinkWithAdditionalQuery(request.GetQueryWithout(key), key, value);

/// <summary>
/// Returns the current URL but with the value of the query string entry of with the key <paramref name="key"/>
/// cycled to the next item in the <paramref name="values"/>. This can be useful for example to generate table
/// header links that cycle ascending-descending-unsorted sorting by that column.
/// </summary>
public static string GetLinkAndCycleQueryValue(this HttpRequest request, string key, params string[] values)
{
var query = HttpUtility.ParseQueryString(request.QueryString.Value ?? string.Empty);

var value = query[key] ?? string.Empty;
var index = ((IList<string>)values).IndexOf(value);
var newValue = values[(index + 1) % values.Length];

return request.GetLinkWithDifferentQuery(key, newValue);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;

namespace Lombiq.HelpfulLibraries.AspNetCore.Extensions;

public static class ServiceCollectionExtensions
{
/// <summary>
/// Configures <see cref="MvcOptions"/> to add the <typeparamref name="TFilter"/> to the list of filters.
/// </summary>
public static void AddAsyncResultFilter<TFilter>(this IServiceCollection services)
where TFilter : IAsyncResultFilter =>
services.Configure<MvcOptions>(options => options.Filters.Add(typeof(TFilter)));
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Lombiq.HelpfulLibraries.OrchardCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using OrchardCore.ContentManagement;
using System;
using System.Collections.Generic;
Expand Down Expand Up @@ -76,4 +77,39 @@ public static string ActionTask<TController>(
params (string Key, object Value)[] additionalArguments)
where TController : ControllerBase =>
httpContext.Action(taskActionExpression.StripResult(), additionalArguments);

/// <summary>
/// Returns the text of the MVC route value identified by <paramref name="name"/> (case-insensitive).
/// </summary>
public static string GetRouteValueString(this HttpContext httpContext, string name) =>
httpContext?.Request.RouteValues.GetMaybe(name)?.ToString();

/// <summary>
/// Returns a value indicating whether the current MVC route matches the provided <paramref name="area"/>, <paramref
/// name="controller"/> and <paramref name="action"/>.
/// </summary>
public static bool IsAction(this HttpContext httpContext, string area, string controller, string action) =>
httpContext?.Request.RouteValues is { } routeValues &&
routeValues.GetMaybe(nameof(area))?.ToString() == area &&
routeValues.GetMaybe(nameof(controller))?.ToString() == controller &&
routeValues.GetMaybe(nameof(action))?.ToString() == action;

/// <summary>
/// Returns a value indicating whether the current page is a content item display action.
/// </summary>
public static bool IsContentDisplay(this HttpContext httpContext) =>
httpContext.IsAction("OrchardCore.Contents", "Item", "Display");

/// <summary>
/// Gets the content item from the database by the ID in the <c>contentItemId</c> or <c>id</c> route values.
/// </summary>
public static Task<ContentItem> GetContentItemAsync(this HttpContext httpContext, string jsonPath = null)
{
var id = httpContext.GetRouteValueString(nameof(ContentItem.ContentItemId));
if (string.IsNullOrWhiteSpace(id)) id = httpContext.GetRouteValueString("id");
if (string.IsNullOrWhiteSpace(id)) return Task.FromResult<ContentItem>(null);

var contentManager = httpContext.RequestServices.GetRequiredService<IContentManager>();
return contentManager.GetAsync(id, jsonPath);
}
}
28 changes: 28 additions & 0 deletions Lombiq.HelpfulLibraries.OrchardCore/Contents/TaxonomyHelper.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using Newtonsoft.Json.Linq;
using OrchardCore.ContentManagement;
using OrchardCore.Taxonomies.Models;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Lombiq.HelpfulLibraries.OrchardCore.Contents;
Expand All @@ -20,4 +23,29 @@ await _contentHandleManager.GetContentItemIdAsync($"alias:{alias}") is { } conte
await _contentManager.GetAsync(contentItemId) is { } contentItem
? contentItem.As<TaxonomyPart>()?.Terms?.Find(term => term.ContentItemId == termId)
: null;

/// <summary>
/// Returns all child content items in a taxonomy tree.
/// </summary>
/// <param name="contentItem">The root of the tree to enumerate.</param>
/// <param name="includeSelf">
/// If <see langword="true"/> the <paramref name="contentItem"/> will be the first result so the collection is never
/// empty as long as <paramref name="contentItem"/> isn't <see langword="null"/>.
/// </param>
/// <returns>An unsorted list of all child items.</returns>
public static IList<ContentItem> GetAllChildren(ContentItem contentItem, bool includeSelf = false)
{
var results = new List<ContentItem>();
if (contentItem == null) return results;
if (includeSelf) results.Add(contentItem);

var partTerms = contentItem.As<TaxonomyPart>()?.Terms ?? Enumerable.Empty<ContentItem>();
var itemTerms = (contentItem.Content.Terms as JArray)?.ToObject<List<ContentItem>>() ?? Enumerable.Empty<ContentItem>();
foreach (var child in partTerms.Concat(itemTerms))
{
results.AddRange(GetAllChildren(child, includeSelf: true));
}

return results;
}
}
14 changes: 11 additions & 3 deletions Lombiq.HelpfulLibraries.OrchardCore/Mvc/WidgetFilterBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,11 @@ protected WidgetFilterBase(
/// <summary>
/// Returns the object used as the view-model and passed to the widget shape.
/// </summary>
protected abstract Task<TViewModel> GetViewModelAsync();
protected virtual Task<TViewModel> GetViewModelAsync() =>
throw new NotSupportedException($"Please override either overloads of \"{nameof(GetViewModelAsync)}\"!");

protected virtual Task<TViewModel> GetViewModelAsync(ResultExecutingContext context) =>
GetViewModelAsync();

public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
Expand All @@ -83,13 +87,17 @@ public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultE
if ((AdminOnly && !isAdmin) ||
(FrontEndOnly && isAdmin) ||
(_requiredPermission != null && !await _authorizationService.AuthorizeAsync(user, _requiredPermission)) ||
await GetViewModelAsync() is not { } viewModel)
await GetViewModelAsync(context) is not { } viewModel)
{
await next();
return;
}

await _layoutAccessor.AddShapeToZoneAsync(ZoneName, await _shapeFactory.CreateAsync(ViewName, viewModel));
await _layoutAccessor.AddShapeToZoneAsync(
ZoneName,
ViewName == null && viewModel is IShape shape
? shape
: await _shapeFactory.CreateAsync(ViewName, viewModel));

await next();
}
Expand Down

0 comments on commit 38cfe73

Please sign in to comment.