Obtener direcciones URL absolutas con ASP.NET Core

82

En MVC 5, tenía los siguientes métodos de extensión para generar URL absolutas, en lugar de relativas:

public static class UrlHelperExtensions
{
    public static string AbsoluteAction(
        this UrlHelper url,
        string actionName, 
        string controllerName, 
        object routeValues = null)
    {
        string scheme = url.RequestContext.HttpContext.Request.Url.Scheme;
        return url.Action(actionName, controllerName, routeValues, scheme);
    }

    public static string AbsoluteContent(
        this UrlHelper url,
        string contentPath)
    {
        return new Uri(url.RequestContext.HttpContext.Request.Url, url.Content(contentPath)).ToString();
    }

    public static string AbsoluteRouteUrl(
        this UrlHelper url,
        string routeName,
        object routeValues = null)
    {
        string scheme = url.RequestContext.HttpContext.Request.Url.Scheme;
        return url.RouteUrl(routeName, routeValues, scheme);
    }
}

¿Cuál sería el equivalente en ASP.NET Core?

  • UrlHelper.RequestContext ya no existe.
  • No puede hacerse con el HttpContextporque ya no hay una HttpContext.Currentpropiedad estática .

Por lo que puedo ver, ahora requeriría que los objetos HttpContexto HttpRequesttambién se pasen. Estoy en lo cierto? ¿Existe alguna forma de obtener la solicitud actual?

¿Estoy incluso en el camino correcto? ¿Debería el dominio ser ahora una variable de entorno, que se agrega simplemente a la URL relativa? ¿Sería este un mejor enfoque?

Muhammad Rehan Saeed
fuente
1
¿Obtener la URL absoluta, qué?
im1dermike
@ im1dermike eghttp://example.com/controller/action
Muhammad Rehan Saeed

Respuestas:

74

Después de RC2 y 1.0 , ya no necesita inyectarse una IHttpContextAccessorclase de extensión. Está disponible de inmediato en IUrlHelperel urlhelper.ActionContext.HttpContext.Request. Luego, crearía una clase de extensión siguiendo la misma idea, pero más simple ya que no habrá ninguna inyección involucrada.

public static string AbsoluteAction(
    this IUrlHelper url,
    string actionName, 
    string controllerName, 
    object routeValues = null)
{
    string scheme = url.ActionContext.HttpContext.Request.Scheme;
    return url.Action(actionName, controllerName, routeValues, scheme);
}

Dejando los detalles de cómo construirlo inyectando el accesor por si le resultan útiles a alguien. También podría estar interesado en la URL absoluta de la solicitud actual, en cuyo caso, eche un vistazo al final de la respuesta.


Puede modificar su clase de extensión para usar la IHttpContextAccessorinterfaz para obtener el HttpContext. Una vez que tenga el contexto, entonces se puede obtener la HttpRequestinstancia de HttpContext.Requesty utilizar sus propiedades Scheme, Host, Protocoletc, como en:

string scheme = HttpContextAccessor.HttpContext.Request.Scheme;

Por ejemplo, podría requerir que su clase esté configurada con un HttpContextAccessor:

public static class UrlHelperExtensions
{        
    private static IHttpContextAccessor HttpContextAccessor;
    public static void Configure(IHttpContextAccessor httpContextAccessor)
    {           
        HttpContextAccessor = httpContextAccessor;  
    }

    public static string AbsoluteAction(
        this IUrlHelper url,
        string actionName, 
        string controllerName, 
        object routeValues = null)
    {
        string scheme = HttpContextAccessor.HttpContext.Request.Scheme;
        return url.Action(actionName, controllerName, routeValues, scheme);
    }

    ....
}

Que es algo que puede hacer en su Startupclase (archivo Startup.cs):

public void Configure(IApplicationBuilder app)
{
    ...

    var httpContextAccessor = app.ApplicationServices.GetRequiredService<IHttpContextAccessor>();
    UrlHelperExtensions.Configure(httpContextAccessor);

    ...
}

Probablemente pueda encontrar diferentes formas de obtener el IHttpContextAccessoren su clase de extensión, pero si desea mantener sus métodos como métodos de extensión al final, deberá inyectarlo IHttpContextAccessoren su clase estática. (De lo contrario, necesitará IHttpContextcomo argumento en cada llamada)


Recién obteniendo la Uri absoluta de la solicitud actual

Si solo desea obtener el uri absoluto de la solicitud actual, puede usar los métodos de extensión GetDisplayUrlo GetEncodedUrlde la UriHelperclase. (Que es diferente del Ur L Helper)

GetDisplayUrl . Devuelve los componentes combinados de la URL de la solicitud en un formato sin escape (excepto para QueryString) adecuado solo para visualización. Este formato no debe usarse en encabezados HTTP u otras operaciones HTTP.

GetEncodedUrl . Devuelve los componentes combinados de la URL de la solicitud en un formato de escape completo adecuado para su uso en encabezados HTTP y otras operaciones HTTP.

Para utilizarlos:

  • Incluya el espacio de nombres Microsoft.AspNet.Http.Extensions.
  • Obtén la HttpContextinstancia. Ya está disponible en algunas clases (como las vistas de maquinilla de afeitar), pero en otras es posible que deba inyectar un IHttpContextAccessorcomo se explicó anteriormente.
  • Entonces utilícelos como en this.Context.Request.GetDisplayUrl()

Una alternativa a esos métodos sería crear manualmente el uri absoluto utilizando los valores en el HttpContext.Requestobjeto (similar a lo que hace RequireHttpsAttribute ):

var absoluteUri = string.Concat(
                        request.Scheme,
                        "://",
                        request.Host.ToUriComponent(),
                        request.PathBase.ToUriComponent(),
                        request.Path.ToUriComponent(),
                        request.QueryString.ToUriComponent());
Daniel JG
fuente
Ahora deberíamos usar IUrlHelper, en lugar de UrlHelper. Todos los objetos están mucho más desconectados en MVC 6. Creo que tu opción es la mejor.
Muhammad Rehan Saeed
No funciona con RC1. La vista produce un error de tiempo de ejecución con el método de extensión. Además, el UriHelperenlace está muerto.
Mrchief
2
@Mrchief He actualizado el enlace (los espacios de nombres han cambiado para RC2, por lo que todos esos enlaces a la rama de desarrollo están muertos ...). Sin embargo, acabo de crear un proyecto RC1, agregado @using Microsoft.AspNet.Http.Extensionsa la vista Index.cshtml y pude usar esas extensiones como en@Context.Request.GetDisplayUrl()
Daniel JG
44

Para ASP.NET Core 1.0 en adelante

/// <summary>
/// <see cref="IUrlHelper"/> extension methods.
/// </summary>
public static class UrlHelperExtensions
{
    /// <summary>
    /// Generates a fully qualified URL to an action method by using the specified action name, controller name and
    /// route values.
    /// </summary>
    /// <param name="url">The URL helper.</param>
    /// <param name="actionName">The name of the action method.</param>
    /// <param name="controllerName">The name of the controller.</param>
    /// <param name="routeValues">The route values.</param>
    /// <returns>The absolute URL.</returns>
    public static string AbsoluteAction(
        this IUrlHelper url,
        string actionName,
        string controllerName,
        object routeValues = null)
    {
        return url.Action(actionName, controllerName, routeValues, url.ActionContext.HttpContext.Request.Scheme);
    }

    /// <summary>
    /// Generates a fully qualified URL to the specified content by using the specified content path. Converts a
    /// virtual (relative) path to an application absolute path.
    /// </summary>
    /// <param name="url">The URL helper.</param>
    /// <param name="contentPath">The content path.</param>
    /// <returns>The absolute URL.</returns>
    public static string AbsoluteContent(
        this IUrlHelper url,
        string contentPath)
    {
        HttpRequest request = url.ActionContext.HttpContext.Request;
        return new Uri(new Uri(request.Scheme + "://" + request.Host.Value), url.Content(contentPath)).ToString();
    }

    /// <summary>
    /// Generates a fully qualified URL to the specified route by using the route name and route values.
    /// </summary>
    /// <param name="url">The URL helper.</param>
    /// <param name="routeName">Name of the route.</param>
    /// <param name="routeValues">The route values.</param>
    /// <returns>The absolute URL.</returns>
    public static string AbsoluteRouteUrl(
        this IUrlHelper url,
        string routeName,
        object routeValues = null)
    {
        return url.RouteUrl(routeName, routeValues, url.ActionContext.HttpContext.Request.Scheme);
    }
}

Consejo de bonificación

No puede registrar directamente un IUrlHelperen el contenedor DI. La resolución de una instancia de IUrlHelperrequiere que utilice IUrlHelperFactoryy IActionContextAccessor. Sin embargo, puede hacer lo siguiente como atajo:

services
    .AddSingleton<IActionContextAccessor, ActionContextAccessor>()
    .AddScoped<IUrlHelper>(x => x
        .GetRequiredService<IUrlHelperFactory>()
        .GetUrlHelper(x.GetRequiredService<IActionContextAccessor>().ActionContext));

Backlog de ASP.NET Core

ACTUALIZACIÓN : esto no hará ASP.NET Core 5

Hay indicaciones de que podrá utilizar LinkGeneratorpara crear URL absolutas sin la necesidad de proporcionar un HttpContext(Este fue el mayor inconveniente LinkGeneratory por qué, IUrlHelperaunque es más complejo de configurar, usar la solución a continuación fue más fácil de usar) Consulte "Facilite la configuración un host / esquema para URL absolutas con LinkGenerator " .

Muhammad Rehan Saeed
fuente
1
¿Haría eso lo que necesito también? Ver stackoverflow.com/q/37928214/153923
jp2code
4
Esto está bien, pero me parece una exageración, demasiado código para algo simple. ¿Podríamos quedarnos constring url = string.Concat(this.Request.Scheme, "://", this.Request.Host, this.Request.Path, this.Request.QueryString);
Junior Mayhé
19

No es necesario crear un método de extensión para esto

@Url.Action("Action", "Controller", values: null);

  • Action - Nombre de la acción
  • Controller - Nombre del controlador
  • values - Objeto que contiene valores de ruta: también conocidos como parámetros GET

También hay muchas otras sobrecargasUrl.Action que puede utilizar para generar enlaces.

Kelly Elton
fuente
1
¡Gracias! Esto era exactamente lo que necesitaba, pero no quiero entender qué es this.Context.Request.Scheme. ¿Eso solo obtiene las partes de protocolo y dominio de la URL?
Lukas
this.Context.Request.Schemadevuelve el protocolo que se utilizó para la solicitud. Será httpo https. Aquí están los documentos, pero realmente no explica qué significa Schema.
Kelly Elton
14

Si simplemente desea un Uri para un método que tiene una anotación de ruta, lo siguiente funcionó para mí.

Pasos

Obtener URL relativa

Tomando nota del nombre de ruta de la acción de destino, obtenga la URL relativa utilizando la propiedad de URL del controlador de la siguiente manera:

var routeUrl = Url.RouteUrl("*Route Name Here*", new { *Route parameters here* });

Crea una URL absoluta

var absUrl = string.Format("{0}://{1}{2}", Request.Scheme,
            Request.Host, routeUrl);

Crea una nueva Uri

var uri = new Uri(absUrl, UriKind.Absolute)

Ejemplo

[Produces("application/json")]
[Route("api/Children")]
public class ChildrenController : Controller
{
    private readonly ApplicationDbContext _context;

    public ChildrenController(ApplicationDbContext context)
    {
        _context = context;
    }

    // GET: api/Children
    [HttpGet]
    public IEnumerable<Child> GetChild()
    {
        return _context.Child;
    }

    [HttpGet("uris")]
    public IEnumerable<Uri> GetChildUris()
    {
        return from c in _context.Child
               select
                   new Uri(
                       $"{Request.Scheme}://{Request.Host}{Url.RouteUrl("GetChildRoute", new { id = c.ChildId })}",
                       UriKind.Absolute);
    }


    // GET: api/Children/5
    [HttpGet("{id}", Name = "GetChildRoute")]
    public IActionResult GetChild([FromRoute] int id)
    {
        if (!ModelState.IsValid)
        {
            return HttpBadRequest(ModelState);
        }

        Child child = _context.Child.Single(m => m.ChildId == id);

        if (child == null)
        {
            return HttpNotFound();
        }

        return Ok(child);
    }
}
Jon
fuente
9

Esta es una variación de la respuesta de Muhammad Rehan Saeed , con la clase unida parasitariamente a la clase MVC del núcleo .net existente del mismo nombre, de modo que todo simplemente funciona.

namespace Microsoft.AspNetCore.Mvc
{
    /// <summary>
    /// <see cref="IUrlHelper"/> extension methods.
    /// </summary>
    public static partial class UrlHelperExtensions
    {
        /// <summary>
        /// Generates a fully qualified URL to an action method by using the specified action name, controller name and
        /// route values.
        /// </summary>
        /// <param name="url">The URL helper.</param>
        /// <param name="actionName">The name of the action method.</param>
        /// <param name="controllerName">The name of the controller.</param>
        /// <param name="routeValues">The route values.</param>
        /// <returns>The absolute URL.</returns>
        public static string AbsoluteAction(
            this IUrlHelper url,
            string actionName,
            string controllerName,
            object routeValues = null)
        {
            return url.Action(actionName, controllerName, routeValues, url.ActionContext.HttpContext.Request.Scheme);
        }

        /// <summary>
        /// Generates a fully qualified URL to the specified content by using the specified content path. Converts a
        /// virtual (relative) path to an application absolute path.
        /// </summary>
        /// <param name="url">The URL helper.</param>
        /// <param name="contentPath">The content path.</param>
        /// <returns>The absolute URL.</returns>
        public static string AbsoluteContent(
            this IUrlHelper url,
            string contentPath)
        {
            HttpRequest request = url.ActionContext.HttpContext.Request;
            return new Uri(new Uri(request.Scheme + "://" + request.Host.Value), url.Content(contentPath)).ToString();
        }

        /// <summary>
        /// Generates a fully qualified URL to the specified route by using the route name and route values.
        /// </summary>
        /// <param name="url">The URL helper.</param>
        /// <param name="routeName">Name of the route.</param>
        /// <param name="routeValues">The route values.</param>
        /// <returns>The absolute URL.</returns>
        public static string AbsoluteRouteUrl(
            this IUrlHelper url,
            string routeName,
            object routeValues = null)
        {
            return url.RouteUrl(routeName, routeValues, url.ActionContext.HttpContext.Request.Scheme);
        }
    }
}
Josías
fuente
5

Acabo de descubrir que puedes hacerlo con esta llamada:

Url.Action(new UrlActionContext
{
    Protocol = Request.Scheme,
    Host = Request.Host.Value,
    Action = "Action"
})

Esto mantendrá el esquema, el host, el puerto, todo.

pavlindrom
fuente
3

En un nuevo proyecto ASP.Net 5 MVC en una acción de controlador que aún puede hacer this.Contexty this.Context.Requestparece que en la Solicitud ya no hay una propiedad Url pero las propiedades secundarias (esquema, host, etc.) están todas en el objeto de solicitud directamente.

 public IActionResult About()
    {
        ViewBag.Message = "Your application description page.";
        var schema = this.Context.Request.Scheme;

        return View();
    }

Más bien o no quieres usar esto. El contexto o inyectar la propiedad es otra conversación. Inyección de dependencia en ASP.NET vNext

ToddB
fuente
3

Si solo desea convertir una ruta relativa con parámetros opcionales, creé un método de extensión para IHttpContextAccessor

public static string AbsoluteUrl(this IHttpContextAccessor httpContextAccessor, string relativeUrl, object parameters = null)
{
    var request = httpContextAccessor.HttpContext.Request;

    var url = new Uri(new Uri($"{request.Scheme}://{request.Host.Value}"), relativeUrl).ToString();

    if (parameters != null)
    {
        url = Microsoft.AspNetCore.WebUtilities.QueryHelpers.AddQueryString(url, ToDictionary(parameters));
    }

    return url;
}


private static Dictionary<string, string> ToDictionary(object obj)
{
    var json = JsonConvert.SerializeObject(obj);
    return JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
}

Luego puede llamar al método desde su servicio / vista usando el IHttpContextAccessor inyectado

var callbackUrl = _httpContextAccessor.AbsoluteUrl("/Identity/Account/ConfirmEmail", new { userId = applicationUser.Id, code });
RadarBug
fuente
2

Puede obtener la URL de esta manera:

Request.Headers["Referer"]

Explicación

El Request.UrlRefererva a lanzar una System.UriFormatExceptionsi está mal formado el árbitro cabecera HTTP (que puede suceder ya que no es por lo general bajo su control).

En cuanto al uso Request.ServerVariables, por MSDN :

Request.ServerVariables Collection

La colección ServerVariables recupera los valores de variables de entorno predeterminadas y solicita información de encabezado.

Propiedad Request.Headers

Obtiene una colección de encabezados HTTP.

Supongo que no entiendo por qué prefiere el Request.ServerVariablesfinal Request.Headers, ya que Request.ServerVariablescontiene todas las variables de entorno, así como los encabezados, donde Request.Headers es una lista mucho más corta que solo contiene los encabezados.

Entonces, la mejor solución es usar la Request.Headerscolección para leer el valor directamente. Sin embargo, preste atención a las advertencias de Microsoft sobre la codificación HTML del valor si lo va a mostrar en un formulario.

Ricardo Rodríguez
fuente
El referente no es confiable, los navegadores no están obligados a enviarlo. En otras palabras, los usuarios pueden configurar sus navegadores para que no envíen referencias, por ejemplo, como medida de seguridad.
mikiqex