Pobieranie bezwzględnych adresów URL przy użyciu ASP.NET Core

82

W MVC 5 miałem następujące metody rozszerzeń do generowania bezwzględnych adresów URL zamiast względnych:

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);
    }
}

Jaki byłby odpowiednik w ASP.NET Core?

  • UrlHelper.RequestContext już nie istnieje.
  • Nie możesz go zdobyć, HttpContextponieważ nie ma już statycznej HttpContext.Currentwłaściwości.

O ile widzę, teraz wymagałbyś, aby obiekty HttpContextlub HttpRequestzostały również przekazane. Czy mam rację? Czy jest jakiś sposób, aby uzyskać bieżące żądanie?

Czy jestem w ogóle na dobrej drodze, czy domena powinna być teraz zmienną środowiskową, którą można po prostu dodać do względnego adresu URL? Czy byłoby to lepsze podejście?

Muhammad Rehan Saeed
źródło
1
Jak uzyskać bezwzględny adres URL?
im1dermike
@ im1dermike np.http://example.com/controller/action
Muhammad Rehan Saeed

Odpowiedzi:

74

Po RC2 i 1.0 nie musisz już wstrzykiwać sobie IHttpContextAccessorklasy rozszerzającej. Jest dostępna w IUrlHelperpoprzez urlhelper.ActionContext.HttpContext.Request. Następnie utworzyłbyś klasę rozszerzającą zgodnie z tym samym pomysłem, ale prostszą, ponieważ nie będzie wymagało wstrzyknięcia.

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);
}

Pozostawiając szczegóły, jak to zbudować, wstrzykując accesor na wypadek, gdyby były dla kogoś przydatne. Możesz być także zainteresowany bezwzględnym adresem URL bieżącego żądania, w takim przypadku spójrz na koniec odpowiedzi.


Możesz zmodyfikować swoją klasę rozszerzenia, aby używać IHttpContextAccessorinterfejsu do pobierania HttpContext. Gdy masz kontekst, a następnie można uzyskać HttpRequestinstancję od HttpContext.Requesti korzystać z jego właściwości Scheme, Host, Protocoletc jak w:

string scheme = HttpContextAccessor.HttpContext.Request.Scheme;

Na przykład możesz wymagać, aby Twoja klasa była skonfigurowana za pomocą 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);
    }

    ....
}

Co możesz zrobić na swojej Startupklasie (plik Startup.cs):

public void Configure(IApplicationBuilder app)
{
    ...

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

    ...
}

Prawdopodobnie mógłbyś wymyślić różne sposoby uzyskania IHttpContextAccessorklasy rozszerzającej, ale jeśli chcesz zachować swoje metody jako metody rozszerzające, ostatecznie będziesz musiał wstrzyknąć the IHttpContextAccessordo swojej klasy statycznej. (W przeciwnym razie będziesz potrzebować IHttpContextjako argumentu przy każdym połączeniu)


Pobieram bezwzględneUri bieżącego żądania

Jeśli chcesz tylko uzyskać bezwzględny identyfikator URI bieżącego żądania, możesz użyć metod rozszerzających GetDisplayUrllub GetEncodedUrlz UriHelperklasy. (Co różni się od Ur L Helper)

GetDisplayUrl . Zwraca połączone składniki adresu URL żądania w postaci całkowicie bez zmiany znaczenia (z wyjątkiem QueryString), nadającej się tylko do wyświetlania. Tego formatu nie należy używać w nagłówkach HTTP ani w innych operacjach HTTP.

GetEncodedUrl . Zwraca połączone składniki adresu URL żądania w postaci pełnej zmiany znaczenia, odpowiedniej do użycia w nagłówkach HTTP i innych operacjach HTTP.

Aby z nich skorzystać:

  • Uwzględnij przestrzeń nazw Microsoft.AspNet.Http.Extensions.
  • Pobierz HttpContextinstancję. Jest już dostępny w niektórych klasach (takich jak widoki brzytwy), ale w innych może być konieczne wstrzyknięcie, IHttpContextAccessorjak wyjaśniono powyżej.
  • Następnie użyj ich jak w this.Context.Request.GetDisplayUrl()

Alternatywą dla tych metod byłoby ręczne utworzenie absolutnego URI przy użyciu wartości w HttpContext.Requestobiekcie (podobnie do tego, co robi RequireHttpsAttribute ):

var absoluteUri = string.Concat(
                        request.Scheme,
                        "://",
                        request.Host.ToUriComponent(),
                        request.PathBase.ToUriComponent(),
                        request.Path.ToUriComponent(),
                        request.QueryString.ToUriComponent());
Daniel JG
źródło
Powinniśmy teraz używać IUrlHelper zamiast UrlHelper. Wszystkie obiekty są znacznie bardziej rozłączone w MVC 6. Myślę, że twoja opcja jest najlepsza.
Muhammad Rehan Saeed
Nie działa z RC1. Widok generuje błąd w czasie wykonywania z metodą rozszerzenia. Ponadto UriHelperlink nie działa.
Mrchief
2
@Mrchief Zaktualizowałem link (przestrzenie nazw zmieniły się dla RC2, więc wszystkie te linki do gałęzi dev są martwe ...). Jednak właśnie utworzyłem projekt RC1, dodałem @using Microsoft.AspNet.Http.Extensionsdo widoku Index.cshtml i mogłem używać tych rozszerzeń, jak w@Context.Request.GetDisplayUrl()
Daniel JG
44

W przypadku ASP.NET Core 1.0 i nowszych

/// <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);
    }
}

Wskazówka bonusowa

Nie możesz bezpośrednio zarejestrować IUrlHelperw kontenerze DI. Rozwiązanie wystąpienia IUrlHelperwymaga użycia IUrlHelperFactoryi IActionContextAccessor. Możesz jednak wykonać następujące czynności jako skrót:

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

ASP.NET Core Backlog

AKTUALIZACJA : To nie spowoduje, że ASP.NET Core 5

Istnieją przesłanki wskazujące na to, że będziesz w stanie LinkGeneratorutworzyć bezwzględne adresy URL bez konieczności podawania adresu HttpContext(To była największa wada LinkGeneratori dlaczego, IUrlHelperchociaż bardziej skomplikowana konfiguracja przy użyciu poniższego rozwiązania była łatwiejsza w użyciu) Zobacz „Ułatw konfigurację host / schemat dla bezwzględnych adresów URL z LinkGenerator ” .

Muhammad Rehan Saeed
źródło
1
Czy to również zrobiłoby to, czego potrzebuję? Zobacz stackoverflow.com/q/37928214/153923
jp2code
4
To jest w porządku, ale wydaje mi się to przesada, zbyt dużo kodu na coś prostego. Czy moglibyśmy się po prostu trzymaćstring url = string.Concat(this.Request.Scheme, "://", this.Request.Host, this.Request.Path, this.Request.QueryString);
Junior Mayhé
19

Nie musisz w tym celu tworzyć metody rozszerzenia

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

  • Action - nazwa działania
  • Controller - Nazwa kontrolera
  • values - Obiekt zawierający wartości tras: aka parametry GET

Istnieje również wiele innych przeciążeń,Url.Action których możesz użyć do generowania linków.

Kelly Elton
źródło
1
Dzięki! To było dokładnie to, czego potrzebowałem, ale nie mogę zrozumieć, co to jest this.Context.Request.Scheme. Czy to tylko pobiera protokół i części domeny adresu URL?
Lukas
this.Context.Request.Schemazwraca protokół, który został użyty dla żądania. To będzie httplub https. Oto dokumenty, ale tak naprawdę nie wyjaśnia, co oznacza Schemat.
Kelly Elton
14

Jeśli po prostu potrzebujesz Uri dla metody, która ma adnotację trasy, poniższe zadziałały.

Kroki

Pobierz względny adres URL

Zwracając uwagę na nazwę trasy akcji docelowej, pobierz względny adres URL za pomocą właściwości adresu URL kontrolera w następujący sposób:

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

Utwórz bezwzględny adres URL

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

Utwórz nowy Uri

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

Przykład

[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
źródło
9

Jest to odmiana odpowiedzi Muhammada Rehana Saeeda , w której klasa zostaje pasożytniczo dołączona do istniejącej klasy MVC .net core o tej samej nazwie, dzięki czemu wszystko po prostu działa.

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);
        }
    }
}
Josiah
źródło
5

Właśnie odkryłem, że możesz to zrobić za pomocą tego połączenia:

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

Pozwoli to zachować schemat, hosta, port, wszystko.

pavlindrom
źródło
3

W nowym projekcie ASP.Net 5 MVC w akcji kontrolera nadal możesz wykonać this.Contexti this.Context.Requestwygląda na to, że w żądaniu nie ma już właściwości Url, ale właściwości podrzędne (schemat, host itp.) Znajdują się bezpośrednio w obiekcie żądania.

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

        return View();
    }

Raczej chcesz użyć this.Context lub wstrzyknąć właściwość to kolejna konwersacja. Wstrzyknięcie zależności w ASP.NET vNext

ToddB
źródło
3

Jeśli chcesz tylko przekonwertować ścieżkę względną z opcjonalnymi parametrami, utworzyłem metodę rozszerzenia dla 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);
}

Następnie można wywołać metodę z usługi / widoku przy użyciu wstrzykniętego IHttpContextAccessor

var callbackUrl = _httpContextAccessor.AbsoluteUrl("/Identity/Account/ConfirmEmail", new { userId = applicationUser.Id, code });
RadarBug
źródło
2

Możesz uzyskać adres URL w ten sposób:

Request.Headers["Referer"]

Wyjaśnienie

Request.UrlRefererRzuci System.UriFormatExceptionjeśli Referer HTTP nagłówek jest niepoprawny (co może się zdarzyć, ponieważ zwykle nie jest pod kontrolą).

Jeśli chodzi o używanie Request.ServerVariables, według MSDN :

Kolekcja Request.ServerVariables

Kolekcja ServerVariables pobiera wartości z góry określonych zmiennych środowiskowych i informacje nagłówka żądania.

Właściwość Request.Headers

Pobiera kolekcję nagłówków HTTP.

Chyba nie rozumiem, dlaczego wolisz Request.ServerVariables over Request.Headers, ponieważRequest.ServerVariables zawiera wszystkie zmienne środowiskowe, a także nagłówki, gdzie Request.Headers to znacznie krótsza lista, która zawiera tylko nagłówki.

Dlatego najlepszym rozwiązaniem jest użycie Request.Headerskolekcji do bezpośredniego odczytania wartości. Zwróć jednak uwagę na ostrzeżenia Microsoftu dotyczące kodowania wartości w formacie HTML, jeśli zamierzasz wyświetlić ją w formularzu.

Ricardo Rodriguez
źródło
Referer nie jest wiarygodny, przeglądarki nie są do tego zmuszane. Innymi słowy, użytkownicy mogą skonfigurować swoje przeglądarki tak, aby nie wysyłały odsyłaczy, np. Ze względów bezpieczeństwa.
mikiqex