Merge pull request #8498 from dotnet-maestro-bot/merge/release/2.2-to-master
[automated] Merge branch 'release/2.2' => 'master'
This commit is contained in:
commit
f2612f4cc0
|
|
@ -0,0 +1,281 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for using <see cref="LinkGenerator"/> to generate links to MVC controllers.
|
||||||
|
/// </summary>
|
||||||
|
public static class ControllerLinkGeneratorExtensions
|
||||||
|
{
|
||||||
|
private static readonly LinkGenerationTemplateOptions _templateOptions = new LinkGenerationTemplateOptions()
|
||||||
|
{
|
||||||
|
UseAmbientValues = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a URI with an absolute path based on the provided values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="generator">The <see cref="LinkGenerator"/>.</param>
|
||||||
|
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
|
||||||
|
/// <param name="action">
|
||||||
|
/// The action name. Used to resolve endpoints. Optional. If <c>null</c> is provided, the current action route value
|
||||||
|
/// will be used.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="controller">
|
||||||
|
/// The controller name. Used to resolve endpoints. Optional. If <c>null</c> is provided, the current controller route value
|
||||||
|
/// will be used.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="values">The route values. Optional. Used to resolve endpoints and expand parameters in the route template.</param>
|
||||||
|
/// <param name="pathBase">
|
||||||
|
/// An optional URI path base. Prepended to the path in the resulting URI. If not provided, the value of <see cref="HttpRequest.PathBase"/> will be used.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="fragment">A URI fragment. Optional. Appended to the resulting URI.</param>
|
||||||
|
/// <param name="options">
|
||||||
|
/// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
|
||||||
|
/// names from <c>RouteOptions</c>.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>A URI with an absolute path, or <c>null</c> if a URI cannot be created.</returns>
|
||||||
|
public static string GetPathByAction(
|
||||||
|
this LinkGenerator generator,
|
||||||
|
HttpContext httpContext,
|
||||||
|
string action = default,
|
||||||
|
string controller = default,
|
||||||
|
object values = default,
|
||||||
|
PathString? pathBase = default,
|
||||||
|
FragmentString fragment = default,
|
||||||
|
LinkOptions options = default)
|
||||||
|
{
|
||||||
|
if (generator == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(generator));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (httpContext == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(httpContext));
|
||||||
|
}
|
||||||
|
|
||||||
|
var address = CreateAddress(httpContext, action, controller, values);
|
||||||
|
return generator.GetPathByAddress<RouteValuesAddress>(
|
||||||
|
httpContext,
|
||||||
|
address,
|
||||||
|
address.ExplicitValues,
|
||||||
|
address.AmbientValues,
|
||||||
|
pathBase,
|
||||||
|
fragment,
|
||||||
|
options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a URI with an absolute path based on the provided values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="generator">The <see cref="LinkGenerator"/>.</param>
|
||||||
|
/// <param name="action">The action name. Used to resolve endpoints.</param>
|
||||||
|
/// <param name="controller">The controller name. Used to resolve endpoints.</param>
|
||||||
|
/// <param name="values">The route values. Optional. Used to resolve endpoints and expand parameters in the route template.</param>
|
||||||
|
/// <param name="pathBase">An optional URI path base. Prepended to the path in the resulting URI.</param>
|
||||||
|
/// <param name="fragment">A URI fragment. Optional. Appended to the resulting URI.</param>
|
||||||
|
/// <param name="options">
|
||||||
|
/// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
|
||||||
|
/// names from <c>RouteOptions</c>.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>A URI with an absolute path, or <c>null</c> if a URI cannot be created.</returns>
|
||||||
|
public static string GetPathByAction(
|
||||||
|
this LinkGenerator generator,
|
||||||
|
string action,
|
||||||
|
string controller,
|
||||||
|
object values = default,
|
||||||
|
PathString pathBase = default,
|
||||||
|
FragmentString fragment = default,
|
||||||
|
LinkOptions options = default)
|
||||||
|
{
|
||||||
|
if (generator == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(generator));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(action));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (controller == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(controller));
|
||||||
|
}
|
||||||
|
|
||||||
|
var address = CreateAddress(httpContext: null, action, controller, values);
|
||||||
|
return generator.GetPathByAddress<RouteValuesAddress>(address, address.ExplicitValues, pathBase, fragment, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates an absolute URI based on the provided values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="generator">The <see cref="LinkGenerator"/>.</param>
|
||||||
|
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
|
||||||
|
/// <param name="action">
|
||||||
|
/// The action name. Used to resolve endpoints. Optional. If <c>null</c> is provided, the current action route value
|
||||||
|
/// will be used.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="controller">
|
||||||
|
/// The controller name. Used to resolve endpoints. Optional. If <c>null</c> is provided, the current controller route value
|
||||||
|
/// will be used.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="values">The route values. Optional. Used to resolve endpoints and expand parameters in the route template.</param>
|
||||||
|
/// <param name="scheme">
|
||||||
|
/// The URI scheme, applied to the resulting URI. Optional. If not provided, the value of <see cref="HttpRequest.Scheme"/> will be used.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="host">
|
||||||
|
/// The URI host/authority, applied to the resulting URI. Optional. If not provided, the value <see cref="HttpRequest.Host"/> will be used.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="pathBase">
|
||||||
|
/// An optional URI path base. Prepended to the path in the resulting URI. If not provided, the value of <see cref="HttpRequest.PathBase"/> will be used.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="fragment">A URI fragment. Optional. Appended to the resulting URI.</param>
|
||||||
|
/// <param name="options">
|
||||||
|
/// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
|
||||||
|
/// names from <c>RouteOptions</c>.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>A absolute URI, or <c>null</c> if a URI cannot be created.</returns>
|
||||||
|
public static string GetUriByAction(
|
||||||
|
this LinkGenerator generator,
|
||||||
|
HttpContext httpContext,
|
||||||
|
string action = default,
|
||||||
|
string controller = default,
|
||||||
|
object values = default,
|
||||||
|
string scheme = default,
|
||||||
|
HostString? host = default,
|
||||||
|
PathString? pathBase = default,
|
||||||
|
FragmentString fragment = default,
|
||||||
|
LinkOptions options = default)
|
||||||
|
{
|
||||||
|
if (generator == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(generator));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (httpContext == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(httpContext));
|
||||||
|
}
|
||||||
|
|
||||||
|
var address = CreateAddress(httpContext, action, controller, values);
|
||||||
|
return generator.GetUriByAddress<RouteValuesAddress>(
|
||||||
|
httpContext,
|
||||||
|
address,
|
||||||
|
address.ExplicitValues,
|
||||||
|
address.AmbientValues,
|
||||||
|
scheme,
|
||||||
|
host,
|
||||||
|
pathBase,
|
||||||
|
fragment,
|
||||||
|
options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates an absolute URI based on the provided values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="generator">The <see cref="LinkGenerator"/>.</param>
|
||||||
|
/// <param name="action">The action name. Used to resolve endpoints.</param>
|
||||||
|
/// <param name="controller">The controller name. Used to resolve endpoints.</param>
|
||||||
|
/// <param name="values">The route values. May be null. Used to resolve endpoints and expand parameters in the route template.</param>
|
||||||
|
/// <param name="scheme">The URI scheme, applied to the resulting URI.</param>
|
||||||
|
/// <param name="host">The URI host/authority, applied to the resulting URI.</param>
|
||||||
|
/// <param name="pathBase">An optional URI path base. Prepended to the path in the resulting URI.</param>
|
||||||
|
/// <param name="fragment">A URI fragment. Optional. Appended to the resulting URI.</param>
|
||||||
|
/// <param name="options">
|
||||||
|
/// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
|
||||||
|
/// names from <c>RouteOptions</c>.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>A absolute URI, or <c>null</c> if a URI cannot be created.</returns>
|
||||||
|
public static string GetUriByAction(
|
||||||
|
this LinkGenerator generator,
|
||||||
|
string action,
|
||||||
|
string controller,
|
||||||
|
object values,
|
||||||
|
string scheme,
|
||||||
|
HostString host,
|
||||||
|
PathString pathBase = default,
|
||||||
|
FragmentString fragment = default,
|
||||||
|
LinkOptions options = default)
|
||||||
|
{
|
||||||
|
if (generator == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(generator));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(action));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (controller == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(controller));
|
||||||
|
}
|
||||||
|
|
||||||
|
var address = CreateAddress(httpContext: null, action, controller, values);
|
||||||
|
return generator.GetUriByAddress<RouteValuesAddress>(address, address.ExplicitValues, scheme, host, pathBase, fragment, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a <see cref="LinkGenerationTemplate"/> based on the provided <paramref name="action"/>, <paramref name="controller"/>, and <paramref name="values"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="generator">The <see cref="LinkGenerator"/>.</param>
|
||||||
|
/// <param name="action">The action name. Used to resolve endpoints.</param>
|
||||||
|
/// <param name="controller">The controller name. Used to resolve endpoints.</param>
|
||||||
|
/// <param name="values">The route values. Optional. Used to resolve endpoints and expand parameters in the route template.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// A <see cref="LinkGenerationTemplate"/> if one or more endpoints matching the address can be found, otherwise <c>null</c>.
|
||||||
|
/// </returns>
|
||||||
|
public static LinkGenerationTemplate GetTemplateByAction(
|
||||||
|
this LinkGenerator generator,
|
||||||
|
string action,
|
||||||
|
string controller,
|
||||||
|
object values = default)
|
||||||
|
{
|
||||||
|
if (generator == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(generator));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(action));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (controller == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(controller));
|
||||||
|
}
|
||||||
|
|
||||||
|
var address = CreateAddress(httpContext: null, action, controller, values);
|
||||||
|
return generator.GetTemplateByAddress<RouteValuesAddress>(address, _templateOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RouteValuesAddress CreateAddress(HttpContext httpContext, string action, string controller, object values)
|
||||||
|
{
|
||||||
|
var explicitValues = new RouteValueDictionary(values);
|
||||||
|
var ambientValues = GetAmbientValues(httpContext);
|
||||||
|
|
||||||
|
UrlHelperBase.NormalizeRouteValuesForAction(action, controller, explicitValues, ambientValues);
|
||||||
|
|
||||||
|
return new RouteValuesAddress()
|
||||||
|
{
|
||||||
|
AmbientValues = ambientValues,
|
||||||
|
ExplicitValues = explicitValues
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RouteValueDictionary GetAmbientValues(HttpContext httpContext)
|
||||||
|
{
|
||||||
|
return httpContext?.Features.Get<IRouteValuesFeature>()?.RouteValues;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,268 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for using <see cref="LinkGenerator"/> to generate links to Razor Pages.
|
||||||
|
/// </summary>
|
||||||
|
public static class PageLinkGeneratorExtensions
|
||||||
|
{
|
||||||
|
private static readonly LinkGenerationTemplateOptions _templateOptions = new LinkGenerationTemplateOptions()
|
||||||
|
{
|
||||||
|
UseAmbientValues = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a URI with an absolute path based on the provided values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="generator">The <see cref="LinkGenerator"/>.</param>
|
||||||
|
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
|
||||||
|
/// <param name="page">
|
||||||
|
/// The page name. Used to resolve endpoints. Optional. If <c>null</c> is provided, the current page route value
|
||||||
|
/// will be used.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="handler">
|
||||||
|
/// The page handler name. Used to resolve endpoints. Optional.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="values">The route values. Optional. Used to resolve endpoints and expand parameters in the route template.</param>
|
||||||
|
/// <param name="pathBase">
|
||||||
|
/// An optional URI path base. Prepended to the path in the resulting URI. If not provided, the value of <see cref="HttpRequest.PathBase"/> will be used.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="fragment">A URI fragment. Optional. Appended to the resulting URI.</param>
|
||||||
|
/// <param name="options">
|
||||||
|
/// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
|
||||||
|
/// names from <c>RouteOptions</c>.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>A URI with an absolute path, or <c>null</c> if a URI cannot be created.</returns>
|
||||||
|
public static string GetPathByPage(
|
||||||
|
this LinkGenerator generator,
|
||||||
|
HttpContext httpContext,
|
||||||
|
string page = default,
|
||||||
|
string handler = default,
|
||||||
|
object values = default,
|
||||||
|
PathString? pathBase = default,
|
||||||
|
FragmentString fragment = default,
|
||||||
|
LinkOptions options = default)
|
||||||
|
{
|
||||||
|
if (generator == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(generator));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (httpContext == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(httpContext));
|
||||||
|
}
|
||||||
|
|
||||||
|
var address = CreateAddress(httpContext, page, handler, values);
|
||||||
|
return generator.GetPathByAddress<RouteValuesAddress>(
|
||||||
|
httpContext,
|
||||||
|
address,
|
||||||
|
address.ExplicitValues,
|
||||||
|
address.AmbientValues,
|
||||||
|
pathBase,
|
||||||
|
fragment,
|
||||||
|
options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a URI with an absolute path based on the provided values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="generator">The <see cref="LinkGenerator"/>.</param>
|
||||||
|
/// <param name="page">
|
||||||
|
/// The page name. Used to resolve endpoints.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="handler">
|
||||||
|
/// The page handler name. Used to resolve endpoints. Optional.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="values">The route values. Optional. Used to resolve endpoints and expand parameters in the route template.</param>
|
||||||
|
/// <param name="pathBase">An optional URI path base. Prepended to the path in the resulting URI.</param>
|
||||||
|
/// <param name="fragment">A URI fragment. Optional. Appended to the resulting URI.</param>
|
||||||
|
/// <param name="options">
|
||||||
|
/// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
|
||||||
|
/// names from <c>RouteOptions</c>.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>A URI with an absolute path, or <c>null</c> if a URI cannot be created.</returns>
|
||||||
|
public static string GetPathByPage(
|
||||||
|
this LinkGenerator generator,
|
||||||
|
string page,
|
||||||
|
string handler = default,
|
||||||
|
object values = default,
|
||||||
|
PathString pathBase = default,
|
||||||
|
FragmentString fragment = default,
|
||||||
|
LinkOptions options = default)
|
||||||
|
{
|
||||||
|
if (generator == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(generator));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (page == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(page));
|
||||||
|
}
|
||||||
|
|
||||||
|
var address = CreateAddress(httpContext: null, page, handler, values);
|
||||||
|
return generator.GetPathByAddress<RouteValuesAddress>(address, address.ExplicitValues, pathBase, fragment, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates an absolute URI based on the provided values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="generator">The <see cref="LinkGenerator"/>.</param>
|
||||||
|
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
|
||||||
|
/// <param name="page">
|
||||||
|
/// The page name. Used to resolve endpoints. Optional. If <c>null</c> is provided, the current page route value
|
||||||
|
/// will be used.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="handler">
|
||||||
|
/// The page handler name. Used to resolve endpoints. Optional.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="values">The route values. Optional. Used to resolve endpoints and expand parameters in the route template.</param>
|
||||||
|
/// <param name="scheme">
|
||||||
|
/// The URI scheme, applied to the resulting URI. Optional. If not provided, the value of <see cref="HttpRequest.Scheme"/> will be used.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="host">
|
||||||
|
/// The URI host/authority, applied to the resulting URI. Optional. If not provided, the value <see cref="HttpRequest.Host"/> will be used.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="pathBase">
|
||||||
|
/// An optional URI path base. Prepended to the path in the resulting URI. If not provided, the value of <see cref="HttpRequest.PathBase"/> will be used.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="fragment">A URI fragment. Optional. Appended to the resulting URI.</param>
|
||||||
|
/// <param name="options">
|
||||||
|
/// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
|
||||||
|
/// names from <c>RouteOptions</c>.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>A absolute URI, or <c>null</c> if a URI cannot be created.</returns>
|
||||||
|
public static string GetUriByPage(
|
||||||
|
this LinkGenerator generator,
|
||||||
|
HttpContext httpContext,
|
||||||
|
string page = default,
|
||||||
|
string handler = default,
|
||||||
|
object values = default,
|
||||||
|
string scheme = default,
|
||||||
|
HostString? host = default,
|
||||||
|
PathString? pathBase = default,
|
||||||
|
FragmentString fragment = default,
|
||||||
|
LinkOptions options = default)
|
||||||
|
{
|
||||||
|
if (generator == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(generator));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (httpContext == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(httpContext));
|
||||||
|
}
|
||||||
|
|
||||||
|
var address = CreateAddress(httpContext, page, handler, values);
|
||||||
|
return generator.GetUriByAddress<RouteValuesAddress>(
|
||||||
|
httpContext,
|
||||||
|
address,
|
||||||
|
address.ExplicitValues,
|
||||||
|
address.AmbientValues,
|
||||||
|
scheme,
|
||||||
|
host,
|
||||||
|
pathBase,
|
||||||
|
fragment,
|
||||||
|
options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates an absolute URI based on the provided values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="generator">The <see cref="LinkGenerator"/>.</param>
|
||||||
|
/// <param name="page">The page name. Used to resolve endpoints.</param>
|
||||||
|
/// <param name="handler">The page handler name. May be null.</param>
|
||||||
|
/// <param name="values">The route values. May be null. Used to resolve endpoints and expand parameters in the route template.</param>
|
||||||
|
/// <param name="scheme">The URI scheme, applied to the resulting URI.</param>
|
||||||
|
/// <param name="host">The URI host/authority, applied to the resulting URI.</param>
|
||||||
|
/// <param name="pathBase">An optional URI path base. Prepended to the path in the resulting URI.</param>
|
||||||
|
/// <param name="fragment">A URI fragment. Optional. Appended to the resulting URI.</param>
|
||||||
|
/// <param name="options">
|
||||||
|
/// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
|
||||||
|
/// names from <c>RouteOptions</c>.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>A absolute URI, or <c>null</c> if a URI cannot be created.</returns>
|
||||||
|
public static string GetUriByPage(
|
||||||
|
this LinkGenerator generator,
|
||||||
|
string page,
|
||||||
|
string handler,
|
||||||
|
object values,
|
||||||
|
string scheme,
|
||||||
|
HostString host,
|
||||||
|
PathString pathBase = default,
|
||||||
|
FragmentString fragment = default,
|
||||||
|
LinkOptions options = default)
|
||||||
|
{
|
||||||
|
if (generator == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(generator));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (page == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(page));
|
||||||
|
}
|
||||||
|
|
||||||
|
var address = CreateAddress(httpContext: null, page, handler, values);
|
||||||
|
return generator.GetUriByAddress<RouteValuesAddress>(address, address.ExplicitValues, scheme, host, pathBase, fragment, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a <see cref="LinkGenerationTemplate"/> based on the provided <paramref name="page"/>, <paramref name="handler"/>, and <paramref name="values"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="generator">The <see cref="LinkGenerator"/>.</param>
|
||||||
|
/// <param name="page">The page name. Used to resolve endpoints.</param>
|
||||||
|
/// <param name="handler">The page handler name. Optional.</param>
|
||||||
|
/// <param name="values">The route values. Optional. Used to resolve endpoints and expand parameters in the route template.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// A <see cref="LinkGenerationTemplate"/> if one or more endpoints matching the address can be found, otherwise <c>null</c>.
|
||||||
|
/// </returns>
|
||||||
|
public static LinkGenerationTemplate GetTemplateByPage(
|
||||||
|
this LinkGenerator generator,
|
||||||
|
string page,
|
||||||
|
string handler = default,
|
||||||
|
object values = default)
|
||||||
|
{
|
||||||
|
if (generator == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(generator));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (page == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(page));
|
||||||
|
}
|
||||||
|
|
||||||
|
var address = CreateAddress(httpContext: null, page, handler, values);
|
||||||
|
return generator.GetTemplateByAddress<RouteValuesAddress>(address, _templateOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RouteValuesAddress CreateAddress(HttpContext httpContext, string page, string handler, object values)
|
||||||
|
{
|
||||||
|
var explicitValues = new RouteValueDictionary(values);
|
||||||
|
var ambientValues = GetAmbientValues(httpContext);
|
||||||
|
|
||||||
|
UrlHelperBase.NormalizeRouteValuesForPage(context: null, page, handler, explicitValues, ambientValues);
|
||||||
|
|
||||||
|
return new RouteValuesAddress()
|
||||||
|
{
|
||||||
|
AmbientValues = ambientValues,
|
||||||
|
ExplicitValues = explicitValues
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RouteValueDictionary GetAmbientValues(HttpContext httpContext)
|
||||||
|
{
|
||||||
|
return httpContext?.Features.Get<IRouteValuesFeature>()?.RouteValues;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -44,31 +44,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
|
|
||||||
var valuesDictionary = GetValuesDictionary(actionContext.Values);
|
var valuesDictionary = GetValuesDictionary(actionContext.Values);
|
||||||
|
|
||||||
if (actionContext.Action == null)
|
NormalizeRouteValuesForAction(actionContext.Action, actionContext.Controller, valuesDictionary, AmbientValues);
|
||||||
{
|
|
||||||
if (!valuesDictionary.ContainsKey("action") &&
|
|
||||||
AmbientValues.TryGetValue("action", out var action))
|
|
||||||
{
|
|
||||||
valuesDictionary["action"] = action;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
valuesDictionary["action"] = actionContext.Action;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (actionContext.Controller == null)
|
|
||||||
{
|
|
||||||
if (!valuesDictionary.ContainsKey("controller") &&
|
|
||||||
AmbientValues.TryGetValue("controller", out var controller))
|
|
||||||
{
|
|
||||||
valuesDictionary["controller"] = controller;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
valuesDictionary["controller"] = actionContext.Controller;
|
|
||||||
}
|
|
||||||
|
|
||||||
var virtualPathData = GetVirtualPathData(routeName: null, values: valuesDictionary);
|
var virtualPathData = GetVirtualPathData(routeName: null, values: valuesDictionary);
|
||||||
return GenerateUrl(actionContext.Protocol, actionContext.Host, virtualPathData, actionContext.Fragment);
|
return GenerateUrl(actionContext.Protocol, actionContext.Host, virtualPathData, actionContext.Fragment);
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,12 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Core;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Routing
|
namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
|
|
@ -263,6 +267,109 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static void NormalizeRouteValuesForAction(
|
||||||
|
string action,
|
||||||
|
string controller,
|
||||||
|
RouteValueDictionary values,
|
||||||
|
RouteValueDictionary ambientValues)
|
||||||
|
{
|
||||||
|
object obj = null;
|
||||||
|
if (action == null)
|
||||||
|
{
|
||||||
|
if (!values.ContainsKey("action") &&
|
||||||
|
(ambientValues?.TryGetValue("action", out obj) ?? false))
|
||||||
|
{
|
||||||
|
values["action"] = obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
values["action"] = action;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (controller == null)
|
||||||
|
{
|
||||||
|
if (!values.ContainsKey("controller") &&
|
||||||
|
(ambientValues?.TryGetValue("controller", out obj) ?? false))
|
||||||
|
{
|
||||||
|
values["controller"] = obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
values["controller"] = controller;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void NormalizeRouteValuesForPage(
|
||||||
|
ActionContext context,
|
||||||
|
string page,
|
||||||
|
string handler,
|
||||||
|
RouteValueDictionary values,
|
||||||
|
RouteValueDictionary ambientValues)
|
||||||
|
{
|
||||||
|
object value = null;
|
||||||
|
if (string.IsNullOrEmpty(page))
|
||||||
|
{
|
||||||
|
if (!values.ContainsKey("page") &&
|
||||||
|
(ambientValues?.TryGetValue("page", out value) ?? false))
|
||||||
|
{
|
||||||
|
values["page"] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
values["page"] = CalculatePageName(context, ambientValues, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(handler))
|
||||||
|
{
|
||||||
|
if (!values.ContainsKey("handler") &&
|
||||||
|
(ambientValues?.ContainsKey("handler") ?? false))
|
||||||
|
{
|
||||||
|
// Clear out form action unless it's explicitly specified in the routeValues.
|
||||||
|
values["handler"] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
values["handler"] = handler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object CalculatePageName(ActionContext context, RouteValueDictionary ambientValues, string pageName)
|
||||||
|
{
|
||||||
|
Debug.Assert(pageName.Length > 0);
|
||||||
|
// Paths not qualified with a leading slash are treated as relative to the current page.
|
||||||
|
if (pageName[0] != '/')
|
||||||
|
{
|
||||||
|
// OK now we should get the best 'normalized' version of the page route value that we can.
|
||||||
|
string currentPagePath;
|
||||||
|
if (context != null)
|
||||||
|
{
|
||||||
|
currentPagePath = NormalizedRouteValue.GetNormalizedRouteValue(context, "page");
|
||||||
|
}
|
||||||
|
else if (ambientValues != null)
|
||||||
|
{
|
||||||
|
currentPagePath = ambientValues["page"]?.ToString();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
currentPagePath = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(currentPagePath))
|
||||||
|
{
|
||||||
|
// Disallow the use sibling page routing, a Razor page specific feature, from a non-page action.
|
||||||
|
throw new InvalidOperationException(Resources.FormatUrlHelper_RelativePagePathIsNotSupported(pageName));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ViewEnginePath.CombinePath(currentPagePath, pageName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pageName;
|
||||||
|
}
|
||||||
|
|
||||||
// for unit testing
|
// for unit testing
|
||||||
internal static void AppendPathAndFragment(StringBuilder builder, PathString pathBase, string virtualPath, string fragment)
|
internal static void AppendPathAndFragment(StringBuilder builder, PathString pathBase, string virtualPath, string fragment)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -444,32 +444,8 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
|
|
||||||
var routeValues = new RouteValueDictionary(values);
|
var routeValues = new RouteValueDictionary(values);
|
||||||
var ambientValues = urlHelper.ActionContext.RouteData.Values;
|
var ambientValues = urlHelper.ActionContext.RouteData.Values;
|
||||||
if (string.IsNullOrEmpty(pageName))
|
|
||||||
{
|
|
||||||
if (!routeValues.ContainsKey("page") &&
|
|
||||||
ambientValues.TryGetValue("page", out var value))
|
|
||||||
{
|
|
||||||
routeValues["page"] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
routeValues["page"] = CalculatePageName(urlHelper.ActionContext, pageName);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(pageHandler))
|
UrlHelperBase.NormalizeRouteValuesForPage(urlHelper.ActionContext, pageName, pageHandler, routeValues, ambientValues);
|
||||||
{
|
|
||||||
if (!routeValues.ContainsKey("handler") &&
|
|
||||||
ambientValues.TryGetValue("handler", out var handler))
|
|
||||||
{
|
|
||||||
// Clear out form action unless it's explicitly specified in the routeValues.
|
|
||||||
routeValues["handler"] = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
routeValues["handler"] = pageHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
return urlHelper.RouteUrl(
|
return urlHelper.RouteUrl(
|
||||||
routeName: null,
|
routeName: null,
|
||||||
|
|
@ -478,24 +454,5 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
host: host,
|
host: host,
|
||||||
fragment: fragment);
|
fragment: fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static object CalculatePageName(ActionContext actionContext, string pageName)
|
|
||||||
{
|
|
||||||
Debug.Assert(pageName.Length > 0);
|
|
||||||
// Paths not qualified with a leading slash are treated as relative to the current page.
|
|
||||||
if (pageName[0] != '/')
|
|
||||||
{
|
|
||||||
var currentPagePath = NormalizedRouteValue.GetNormalizedRouteValue(actionContext, "page");
|
|
||||||
if (string.IsNullOrEmpty(currentPagePath))
|
|
||||||
{
|
|
||||||
// Disallow the use sibling page routing, a Razor page specific feature, from a non-page action.
|
|
||||||
throw new InvalidOperationException(Resources.FormatUrlHelper_RelativePagePathIsNotSupported(pageName));
|
|
||||||
}
|
|
||||||
|
|
||||||
return ViewEnginePath.CombinePath(currentPagePath, pageName);
|
|
||||||
}
|
|
||||||
|
|
||||||
return pageName;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal
|
||||||
/// <param name="context">The <see cref="ActionContext"/>.</param>
|
/// <param name="context">The <see cref="ActionContext"/>.</param>
|
||||||
/// <param name="result">The <see cref="JsonResult"/>.</param>
|
/// <param name="result">The <see cref="JsonResult"/>.</param>
|
||||||
/// <returns>A <see cref="Task"/> which will complete when writing has completed.</returns>
|
/// <returns>A <see cref="Task"/> which will complete when writing has completed.</returns>
|
||||||
public virtual Task ExecuteAsync(ActionContext context, JsonResult result)
|
public virtual async Task ExecuteAsync(ActionContext context, JsonResult result)
|
||||||
{
|
{
|
||||||
if (context == null)
|
if (context == null)
|
||||||
{
|
{
|
||||||
|
|
@ -128,9 +128,11 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal
|
||||||
var jsonSerializer = JsonSerializer.Create(serializerSettings);
|
var jsonSerializer = JsonSerializer.Create(serializerSettings);
|
||||||
jsonSerializer.Serialize(jsonWriter, result.Value);
|
jsonSerializer.Serialize(jsonWriter, result.Value);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
// Perf: call FlushAsync to call WriteAsync on the stream with any content left in the TextWriter's
|
||||||
|
// buffers. This is better than just letting dispose handle it (which would result in a synchronous write).
|
||||||
|
await writer.FlushAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,245 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
|
using Microsoft.Extensions.ObjectPool;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing
|
||||||
|
{
|
||||||
|
public class ControllerLinkGeneratorExtensionsTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void GetPathByAction_WithHttpContext_PromotesAmbientValues()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoint1 = CreateEndpoint(
|
||||||
|
"Home/Index/{id}",
|
||||||
|
defaults: new { controller = "Home", action = "Index", },
|
||||||
|
metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) });
|
||||||
|
var endpoint2 = CreateEndpoint(
|
||||||
|
"Home/Index/{id?}",
|
||||||
|
defaults: new { controller = "Home", action = "Index", },
|
||||||
|
metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) });
|
||||||
|
|
||||||
|
var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
|
||||||
|
|
||||||
|
var httpContext = CreateHttpContext(new { controller = "Home", });
|
||||||
|
httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var path = linkGenerator.GetPathByAction(
|
||||||
|
httpContext,
|
||||||
|
action: "Index",
|
||||||
|
values: new RouteValueDictionary(new { query = "some?query" }),
|
||||||
|
fragment: new FragmentString("#Fragment?"),
|
||||||
|
options: new LinkOptions() { AppendTrailingSlash = true, });
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("/Foo/Bar%3Fencodeme%3F/Home/Index/?query=some%3Fquery#Fragment?", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetPathByAction_WithoutHttpContext_WithPathBaseAndFragment()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoint1 = CreateEndpoint(
|
||||||
|
"Home/Index/{id}",
|
||||||
|
defaults: new { controller = "Home", action = "Index", },
|
||||||
|
metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) });
|
||||||
|
var endpoint2 = CreateEndpoint(
|
||||||
|
"Home/Index/{id?}",
|
||||||
|
defaults: new { controller = "Home", action = "Index", },
|
||||||
|
metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) });
|
||||||
|
|
||||||
|
var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var path = linkGenerator.GetPathByAction(
|
||||||
|
action: "Index",
|
||||||
|
controller: "Home",
|
||||||
|
values: new RouteValueDictionary(new { query = "some?query" }),
|
||||||
|
new PathString("/Foo/Bar?encodeme?"),
|
||||||
|
new FragmentString("#Fragment?"),
|
||||||
|
new LinkOptions() { AppendTrailingSlash = true, });
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("/Foo/Bar%3Fencodeme%3F/Home/Index/?query=some%3Fquery#Fragment?", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetPathByAction_WithHttpContext_WithPathBaseAndFragment()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoint1 = CreateEndpoint(
|
||||||
|
"Home/Index/{id}",
|
||||||
|
defaults: new { controller = "Home", action = "Index", },
|
||||||
|
metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) });
|
||||||
|
var endpoint2 = CreateEndpoint(
|
||||||
|
"Home/Index/{id?}",
|
||||||
|
defaults: new { controller = "Home", action = "Index", },
|
||||||
|
metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) });
|
||||||
|
|
||||||
|
var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
|
||||||
|
|
||||||
|
var httpContext = CreateHttpContext();
|
||||||
|
httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var path = linkGenerator.GetPathByAction(
|
||||||
|
httpContext,
|
||||||
|
action: "Index",
|
||||||
|
controller: "Home",
|
||||||
|
values: new RouteValueDictionary(new { query = "some?query" }),
|
||||||
|
fragment: new FragmentString("#Fragment?"),
|
||||||
|
options: new LinkOptions() { AppendTrailingSlash = true, });
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("/Foo/Bar%3Fencodeme%3F/Home/Index/?query=some%3Fquery#Fragment?", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetUriByAction_WithoutHttpContext_WithPathBaseAndFragment()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoint1 = CreateEndpoint(
|
||||||
|
"Home/Index/{id}",
|
||||||
|
defaults: new { controller = "Home", action = "Index", },
|
||||||
|
metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) });
|
||||||
|
var endpoint2 = CreateEndpoint(
|
||||||
|
"Home/Index/{id?}",
|
||||||
|
defaults: new { controller = "Home", action = "Index", },
|
||||||
|
metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) });
|
||||||
|
|
||||||
|
var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var path = linkGenerator.GetUriByAction(
|
||||||
|
action: "Index",
|
||||||
|
controller: "Home",
|
||||||
|
values: new RouteValueDictionary(new { query = "some?query" }),
|
||||||
|
"http",
|
||||||
|
new HostString("example.com"),
|
||||||
|
new PathString("/Foo/Bar?encodeme?"),
|
||||||
|
new FragmentString("#Fragment?"),
|
||||||
|
new LinkOptions() { AppendTrailingSlash = true, });
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("http://example.com/Foo/Bar%3Fencodeme%3F/Home/Index/?query=some%3Fquery#Fragment?", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetUriByAction_WithHttpContext_WithPathBaseAndFragment()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoint1 = CreateEndpoint(
|
||||||
|
"Home/Index/{id}",
|
||||||
|
defaults: new { controller = "Home", action = "Index", },
|
||||||
|
metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) });
|
||||||
|
var endpoint2 = CreateEndpoint(
|
||||||
|
"Home/Index/{id?}",
|
||||||
|
defaults: new { controller = "Home", action = "Index", },
|
||||||
|
metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) });
|
||||||
|
|
||||||
|
var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
|
||||||
|
|
||||||
|
var httpContext = CreateHttpContext(new { controller = "Home", action = "Index", });
|
||||||
|
httpContext.Request.Scheme = "http";
|
||||||
|
httpContext.Request.Host = new HostString("example.com");
|
||||||
|
httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var uri = linkGenerator.GetUriByAction(
|
||||||
|
httpContext,
|
||||||
|
values: new RouteValueDictionary(new { query = "some?query" }),
|
||||||
|
fragment: new FragmentString("#Fragment?"),
|
||||||
|
options: new LinkOptions() { AppendTrailingSlash = true, });
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("http://example.com/Foo/Bar%3Fencodeme%3F/Home/Index/?query=some%3Fquery#Fragment?", uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetTemplateByAction_CreatesTemplate()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoint1 = CreateEndpoint(
|
||||||
|
"Home/Index/{id}",
|
||||||
|
metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) });
|
||||||
|
var endpoint2 = CreateEndpoint(
|
||||||
|
"Home/Index/{id?}",
|
||||||
|
metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) });
|
||||||
|
|
||||||
|
var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var template = linkGenerator.GetTemplateByAction(action: "Index", controller: "Home");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(template);
|
||||||
|
Assert.Equal("/Home/Index/17", template.GetPath(new { id = 17 }));
|
||||||
|
}
|
||||||
|
|
||||||
|
private RouteEndpoint CreateEndpoint(
|
||||||
|
string template,
|
||||||
|
object defaults = null,
|
||||||
|
object requiredValues = null,
|
||||||
|
int order = 0,
|
||||||
|
object[] metadata = null)
|
||||||
|
{
|
||||||
|
return new RouteEndpoint(
|
||||||
|
(httpContext) => Task.CompletedTask,
|
||||||
|
RoutePatternFactory.Parse(template, defaults, parameterPolicies: null),
|
||||||
|
order,
|
||||||
|
new EndpointMetadataCollection(metadata ?? Array.Empty<object>()),
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IServiceProvider CreateServices(IEnumerable<Endpoint> endpoints)
|
||||||
|
{
|
||||||
|
if (endpoints == null)
|
||||||
|
{
|
||||||
|
endpoints = Enumerable.Empty<Endpoint>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
services.AddOptions();
|
||||||
|
services.AddLogging();
|
||||||
|
services.AddRouting();
|
||||||
|
services
|
||||||
|
.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>()
|
||||||
|
.AddSingleton<UrlEncoder>(UrlEncoder.Default);
|
||||||
|
services.TryAddEnumerable(ServiceDescriptor.Singleton<EndpointDataSource>(new DefaultEndpointDataSource(endpoints)));
|
||||||
|
return services.BuildServiceProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
private LinkGenerator CreateLinkGenerator(params Endpoint[] endpoints)
|
||||||
|
{
|
||||||
|
var services = CreateServices(endpoints);
|
||||||
|
return services.GetRequiredService<LinkGenerator>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpContext CreateHttpContext(object ambientValues = null)
|
||||||
|
{
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
|
||||||
|
var feature = new EndpointFeature
|
||||||
|
{
|
||||||
|
RouteValues = new RouteValueDictionary(ambientValues)
|
||||||
|
};
|
||||||
|
|
||||||
|
httpContext.Features.Set<IEndpointFeature>(feature);
|
||||||
|
httpContext.Features.Set<IRouteValuesFeature>(feature);
|
||||||
|
return httpContext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,245 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
|
using Microsoft.Extensions.ObjectPool;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing
|
||||||
|
{
|
||||||
|
public class PageLinkGeneratorExtensionsTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void GetPathByPage_WithHttpContext_PromotesAmbientValues()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoint1 = CreateEndpoint(
|
||||||
|
"About/{id}",
|
||||||
|
defaults: new { page = "/About", },
|
||||||
|
metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/About", })) });
|
||||||
|
var endpoint2 = CreateEndpoint(
|
||||||
|
"Admin/ManageUsers/{handler?}",
|
||||||
|
defaults: new { page = "/Admin/ManageUsers", },
|
||||||
|
metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/Admin/ManageUsers", })) });
|
||||||
|
|
||||||
|
var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
|
||||||
|
|
||||||
|
var httpContext = CreateHttpContext(new { page = "/About", id = 17, });
|
||||||
|
httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var path = linkGenerator.GetPathByPage(
|
||||||
|
httpContext,
|
||||||
|
values: new RouteValueDictionary(new { id = 18, query = "some?query" }),
|
||||||
|
fragment: new FragmentString("#Fragment?"),
|
||||||
|
options: new LinkOptions() { AppendTrailingSlash = true, });
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("/Foo/Bar%3Fencodeme%3F/About/18/?query=some%3Fquery#Fragment?", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetPathByPage_WithoutHttpContext_WithPathBaseAndFragment()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoint1 = CreateEndpoint(
|
||||||
|
"About/{id}",
|
||||||
|
defaults: new { page = "/About", },
|
||||||
|
metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/About", })) });
|
||||||
|
var endpoint2 = CreateEndpoint(
|
||||||
|
"Admin/ManageUsers/{handler?}",
|
||||||
|
defaults: new { page = "/Admin/ManageUsers", },
|
||||||
|
metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/Admin/ManageUsers", })) });
|
||||||
|
|
||||||
|
var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var path = linkGenerator.GetPathByPage(
|
||||||
|
page: "/Admin/ManageUsers",
|
||||||
|
handler: "Delete",
|
||||||
|
values: new RouteValueDictionary(new { user = "jamesnk", query = "some?query" }),
|
||||||
|
new PathString("/Foo/Bar?encodeme?"),
|
||||||
|
new FragmentString("#Fragment?"),
|
||||||
|
new LinkOptions() { AppendTrailingSlash = true, });
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("/Foo/Bar%3Fencodeme%3F/Admin/ManageUsers/Delete/?user=jamesnk&query=some%3Fquery#Fragment?", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetPathByPage_WithHttpContext_WithPathBaseAndFragment()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoint1 = CreateEndpoint(
|
||||||
|
"About/{id}",
|
||||||
|
defaults: new { page = "/About", },
|
||||||
|
metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/About", })) });
|
||||||
|
var endpoint2 = CreateEndpoint(
|
||||||
|
"Admin/ManageUsers",
|
||||||
|
defaults: new { page = "/Admin/ManageUsers", },
|
||||||
|
metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/Admin/ManageUsers", })) });
|
||||||
|
|
||||||
|
var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
|
||||||
|
|
||||||
|
var httpContext = CreateHttpContext(new { page = "/Admin/ManageUsers", handler = "DeleteUser", });
|
||||||
|
httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var path = linkGenerator.GetPathByPage(
|
||||||
|
httpContext,
|
||||||
|
page: "/About",
|
||||||
|
values: new RouteValueDictionary(new { id = 19, query = "some?query" }),
|
||||||
|
fragment: new FragmentString("#Fragment?"),
|
||||||
|
options: new LinkOptions() { AppendTrailingSlash = true, });
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("/Foo/Bar%3Fencodeme%3F/About/19/?query=some%3Fquery#Fragment?", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetUriByPage_WithoutHttpContext_WithPathBaseAndFragment()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoint1 = CreateEndpoint(
|
||||||
|
"About/{id}",
|
||||||
|
defaults: new { page = "/About", },
|
||||||
|
metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/About", })) });
|
||||||
|
var endpoint2 = CreateEndpoint(
|
||||||
|
"Admin/ManageUsers",
|
||||||
|
defaults: new { page = "/Admin/ManageUsers", },
|
||||||
|
metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/Admin/ManageUsers", })) });
|
||||||
|
|
||||||
|
var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var path = linkGenerator.GetUriByPage(
|
||||||
|
page: "/About",
|
||||||
|
handler: null,
|
||||||
|
values: new RouteValueDictionary(new { id = 19, query = "some?query" }),
|
||||||
|
"http",
|
||||||
|
new HostString("example.com"),
|
||||||
|
new PathString("/Foo/Bar?encodeme?"),
|
||||||
|
new FragmentString("#Fragment?"),
|
||||||
|
new LinkOptions() { AppendTrailingSlash = true, });
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("http://example.com/Foo/Bar%3Fencodeme%3F/About/19/?query=some%3Fquery#Fragment?", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetUriByPage_WithHttpContext_WithPathBaseAndFragment()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoint1 = CreateEndpoint(
|
||||||
|
"About/{id}",
|
||||||
|
defaults: new { page = "/About", },
|
||||||
|
metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/About", })) });
|
||||||
|
var endpoint2 = CreateEndpoint(
|
||||||
|
"Admin/ManageUsers",
|
||||||
|
defaults: new { page = "/Admin/ManageUsers", },
|
||||||
|
metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/Admin/ManageUsers", })) });
|
||||||
|
|
||||||
|
var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
|
||||||
|
|
||||||
|
var httpContext = CreateHttpContext(new { page = "/Admin/ManageUsers", });
|
||||||
|
httpContext.Request.Scheme = "http";
|
||||||
|
httpContext.Request.Host = new HostString("example.com");
|
||||||
|
httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var uri = linkGenerator.GetUriByPage(
|
||||||
|
httpContext,
|
||||||
|
values: new RouteValueDictionary(new { query = "some?query" }),
|
||||||
|
fragment: new FragmentString("#Fragment?"),
|
||||||
|
options: new LinkOptions() { AppendTrailingSlash = true, });
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("http://example.com/Foo/Bar%3Fencodeme%3F/Admin/ManageUsers/?query=some%3Fquery#Fragment?", uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetTemplateByAction_CreatesTemplate()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var endpoint1 = CreateEndpoint(
|
||||||
|
"About/{id}",
|
||||||
|
defaults: new { page = "/About", },
|
||||||
|
metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/About", })) });
|
||||||
|
var endpoint2 = CreateEndpoint(
|
||||||
|
"Admin/ManageUsers",
|
||||||
|
defaults: new { page = "/Admin/ManageUsers", },
|
||||||
|
metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/Admin/ManageUsers", })) });
|
||||||
|
|
||||||
|
var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var template = linkGenerator.GetTemplateByPage(page: "/About");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(template);
|
||||||
|
Assert.Equal("/About/17", template.GetPath(new { id = 17 }));
|
||||||
|
}
|
||||||
|
|
||||||
|
private RouteEndpoint CreateEndpoint(
|
||||||
|
string template,
|
||||||
|
object defaults = null,
|
||||||
|
object requiredValues = null,
|
||||||
|
int order = 0,
|
||||||
|
object[] metadata = null)
|
||||||
|
{
|
||||||
|
return new RouteEndpoint(
|
||||||
|
(httpContext) => Task.CompletedTask,
|
||||||
|
RoutePatternFactory.Parse(template, defaults, parameterPolicies: null),
|
||||||
|
order,
|
||||||
|
new EndpointMetadataCollection(metadata ?? Array.Empty<object>()),
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IServiceProvider CreateServices(IEnumerable<Endpoint> endpoints)
|
||||||
|
{
|
||||||
|
if (endpoints == null)
|
||||||
|
{
|
||||||
|
endpoints = Enumerable.Empty<Endpoint>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
services.AddOptions();
|
||||||
|
services.AddLogging();
|
||||||
|
services.AddRouting();
|
||||||
|
services
|
||||||
|
.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>()
|
||||||
|
.AddSingleton<UrlEncoder>(UrlEncoder.Default);
|
||||||
|
services.TryAddEnumerable(ServiceDescriptor.Singleton<EndpointDataSource>(new DefaultEndpointDataSource(endpoints)));
|
||||||
|
return services.BuildServiceProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
private LinkGenerator CreateLinkGenerator(params Endpoint[] endpoints)
|
||||||
|
{
|
||||||
|
var services = CreateServices(endpoints);
|
||||||
|
return services.GetRequiredService<LinkGenerator>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpContext CreateHttpContext(object ambientValues = null)
|
||||||
|
{
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
|
||||||
|
var feature = new EndpointFeature
|
||||||
|
{
|
||||||
|
RouteValues = new RouteValueDictionary(ambientValues)
|
||||||
|
};
|
||||||
|
|
||||||
|
httpContext.Features.Set<IEndpointFeature>(feature);
|
||||||
|
httpContext.Features.Set<IRouteValuesFeature>(feature);
|
||||||
|
return httpContext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
|
|
@ -14,6 +15,7 @@ using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
|
using Moq;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -217,6 +219,66 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal
|
||||||
Assert.Equal(expected, logger.MostRecentMessage);
|
Assert.Equal(expected, logger.MostRecentMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExecuteAsync_WritesToTheResponseStream_WhenContentIsLargerThanBuffer()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var writeLength = 2 * TestHttpResponseStreamWriterFactory.DefaultBufferSize + 4;
|
||||||
|
var text = new string('a', writeLength);
|
||||||
|
var expectedWriteCallCount = Math.Ceiling((double)writeLength / TestHttpResponseStreamWriterFactory.DefaultBufferSize);
|
||||||
|
|
||||||
|
var stream = new Mock<Stream>();
|
||||||
|
stream.SetupGet(s => s.CanWrite).Returns(true);
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.Response.Body = stream.Object;
|
||||||
|
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
|
||||||
|
|
||||||
|
var result = new JsonResult(text);
|
||||||
|
var executor = CreateExecutor();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await executor.ExecuteAsync(actionContext, result);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
// HttpResponseStreamWriter buffers content up to the buffer size (16k). When writes exceed the buffer size, it'll perform a synchronous
|
||||||
|
// write to the response stream.
|
||||||
|
stream.Verify(s => s.Write(It.IsAny<byte[]>(), It.IsAny<int>(), TestHttpResponseStreamWriterFactory.DefaultBufferSize), Times.Exactly(2));
|
||||||
|
|
||||||
|
// Remainder buffered content is written asynchronously as part of the FlushAsync.
|
||||||
|
stream.Verify(s => s.WriteAsync(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()), Times.Once());
|
||||||
|
|
||||||
|
// Dispose does not call Flush
|
||||||
|
stream.Verify(s => s.Flush(), Times.Never());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(5)]
|
||||||
|
[InlineData(TestHttpResponseStreamWriterFactory.DefaultBufferSize - 30)]
|
||||||
|
public async Task ExecuteAsync_DoesNotWriteSynchronouslyToTheResponseBody_WhenContentIsSmallerThanBufferSize(int writeLength)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var text = new string('a', writeLength);
|
||||||
|
|
||||||
|
var stream = new Mock<Stream>();
|
||||||
|
stream.SetupGet(s => s.CanWrite).Returns(true);
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.Response.Body = stream.Object;
|
||||||
|
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
|
||||||
|
|
||||||
|
var result = new JsonResult(text);
|
||||||
|
var executor = CreateExecutor();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await executor.ExecuteAsync(actionContext, result);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
// HttpResponseStreamWriter buffers content up to the buffer size (16k) and will asynchronously write content to the response as part
|
||||||
|
// of the FlushAsync call if the content written to it is smaller than the buffer size.
|
||||||
|
// This test verifies that no synchronous writes are performed in this scenario.
|
||||||
|
stream.Verify(s => s.Flush(), Times.Never());
|
||||||
|
stream.Verify(s => s.Write(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>()), Times.Never());
|
||||||
|
}
|
||||||
|
|
||||||
private static JsonResultExecutor CreateExecutor(ILogger<JsonResultExecutor> logger = null)
|
private static JsonResultExecutor CreateExecutor(ILogger<JsonResultExecutor> logger = null)
|
||||||
{
|
{
|
||||||
return new JsonResultExecutor(
|
return new JsonResultExecutor(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue