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);
|
||||
|
||||
if (actionContext.Action == null)
|
||||
{
|
||||
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;
|
||||
}
|
||||
NormalizeRouteValuesForAction(actionContext.Action, actionContext.Controller, valuesDictionary, AmbientValues);
|
||||
|
||||
var virtualPathData = GetVirtualPathData(routeName: null, values: valuesDictionary);
|
||||
return GenerateUrl(actionContext.Protocol, actionContext.Host, virtualPathData, actionContext.Fragment);
|
||||
|
|
|
|||
|
|
@ -3,8 +3,12 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Core;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.AspNetCore.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
|
||||
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 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))
|
||||
{
|
||||
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;
|
||||
}
|
||||
UrlHelperBase.NormalizeRouteValuesForPage(urlHelper.ActionContext, pageName, pageHandler, routeValues, ambientValues);
|
||||
|
||||
return urlHelper.RouteUrl(
|
||||
routeName: null,
|
||||
|
|
@ -478,24 +454,5 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
host: host,
|
||||
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="result">The <see cref="JsonResult"/>.</param>
|
||||
/// <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)
|
||||
{
|
||||
|
|
@ -128,9 +128,11 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal
|
|||
var jsonSerializer = JsonSerializer.Create(serializerSettings);
|
||||
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.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
|
|
@ -14,6 +15,7 @@ using Microsoft.Extensions.Logging;
|
|||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Moq;
|
||||
using Newtonsoft.Json;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -217,6 +219,66 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal
|
|||
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)
|
||||
{
|
||||
return new JsonResultExecutor(
|
||||
|
|
|
|||
Loading…
Reference in New Issue