Obter o endereço IP do host remoto

136

No ASP.NET, há uma System.Web.HttpRequestclasse que contém ServerVariablespropriedades que podem nos fornecer o endereço IP do REMOTE_ADDRvalor da propriedade.

No entanto, não foi possível encontrar uma maneira semelhante de obter o endereço IP do host remoto da API da Web do ASP.NET.

Como posso obter o endereço IP do host remoto que está fazendo a solicitação?

paulius_l
fonte

Respostas:

189

É possível fazer isso, mas não muito detectável - você precisa usar a bolsa de propriedades da solicitação recebida, e a propriedade que você precisa acessar depende de estar usando a API da Web no IIS (hospedado na web) ou auto-hospedado. O código abaixo mostra como isso pode ser feito.

private string GetClientIp(HttpRequestMessage request)
{
    if (request.Properties.ContainsKey("MS_HttpContext"))
    {
        return ((HttpContextWrapper)request.Properties["MS_HttpContext"]).Request.UserHostAddress;
    }

    if (request.Properties.ContainsKey(RemoteEndpointMessageProperty.Name))
    {
        RemoteEndpointMessageProperty prop;
        prop = (RemoteEndpointMessageProperty)request.Properties[RemoteEndpointMessageProperty.Name];
        return prop.Address;
    }

    return null;
}
carlosfigueira
fonte
4
Obrigado, eu estava procurando por isso também. Pequena melhoria = classe de extensão: gist.github.com/2653453
MikeJansen
28
A WebAPI é geralmente muito limpa. É uma pena que códigos como este sejam necessários para algo trivial como um IP.
Toad
2
A RemoteEndpointMessagePropertyclasse está no System.ServiceModel.Channelsnamespace System.ServiceModel.dllassembly? Não é uma montagem pertencente ao WCF?
Slauma
4
@ Sluma, sim, eles são. A API da Web do ASP.NET (atualmente) é implementada em dois "sabores": auto-hospedado e hospedado na web. A versão hospedada na Web é implementada no ASP.NET, enquanto a versão hospedada no topo de um ouvinte do WCF. Observe que a plataforma (API da Web do ASP.NET) é independente de hospedagem, portanto, é possível que alguém implemente uma hospedagem diferente no futuro e o host mostre essa propriedade (ponto de extremidade remoto) de maneira diferente.
Carlosfigueira
3
Infelizmente, isso não funciona se você se auto-hospedar usando Owin (como é recomendado para a API da Web 2). Precisa de outro se não ...
Nikolai Samteladze
74

Esta solução também abrange a API da Web auto-hospedada usando Owin. Parcialmente daqui .

Você pode criar um método privado ApiControllerque retornará o endereço IP remoto, independentemente de como você hospeda sua API da Web:

 private const string HttpContext = "MS_HttpContext";
 private const string RemoteEndpointMessage =
     "System.ServiceModel.Channels.RemoteEndpointMessageProperty";
 private const string OwinContext = "MS_OwinContext";

 private string GetClientIp(HttpRequestMessage request)
 {
       // Web-hosting
       if (request.Properties.ContainsKey(HttpContext ))
       {
            HttpContextWrapper ctx = 
                (HttpContextWrapper)request.Properties[HttpContext];
            if (ctx != null)
            {
                return ctx.Request.UserHostAddress;
            }
       }

       // Self-hosting
       if (request.Properties.ContainsKey(RemoteEndpointMessage))
       {
            RemoteEndpointMessageProperty remoteEndpoint =
                (RemoteEndpointMessageProperty)request.Properties[RemoteEndpointMessage];
            if (remoteEndpoint != null)
            {
                return remoteEndpoint.Address;
            }
        }

       // Self-hosting using Owin
       if (request.Properties.ContainsKey(OwinContext))
       {
           OwinContext owinContext = (OwinContext)request.Properties[OwinContext];
           if (owinContext != null)
           {
               return owinContext.Request.RemoteIpAddress;
           }
       }

        return null;
 }

Referências necessárias:

  • HttpContextWrapper - System.Web.dll
  • RemoteEndpointMessageProperty - System.ServiceModel.dll
  • OwinContext - Microsoft.Owin.dll (você já o terá se usar o pacote Owin)

Um pequeno problema com esta solução é que você precisa carregar bibliotecas para todos os 3 casos em que estará usando apenas uma delas durante o tempo de execução. Como sugerido aqui , isso pode ser superado usando dynamicvariáveis. Você também pode escrever o GetClientIpAddressmétodo como uma extensão para HttpRequestMethod.

using System.Net.Http;

public static class HttpRequestMessageExtensions
{
    private const string HttpContext = "MS_HttpContext";
    private const string RemoteEndpointMessage =
        "System.ServiceModel.Channels.RemoteEndpointMessageProperty";
    private const string OwinContext = "MS_OwinContext";

    public static string GetClientIpAddress(this HttpRequestMessage request)
    {
       // Web-hosting. Needs reference to System.Web.dll
       if (request.Properties.ContainsKey(HttpContext))
       {
           dynamic ctx = request.Properties[HttpContext];
           if (ctx != null)
           {
               return ctx.Request.UserHostAddress;
           }
       }

       // Self-hosting. Needs reference to System.ServiceModel.dll. 
       if (request.Properties.ContainsKey(RemoteEndpointMessage))
       {
            dynamic remoteEndpoint = request.Properties[RemoteEndpointMessage];
            if (remoteEndpoint != null)
            {
                return remoteEndpoint.Address;
            }
        }

       // Self-hosting using Owin. Needs reference to Microsoft.Owin.dll. 
       if (request.Properties.ContainsKey(OwinContext))
       {
           dynamic owinContext = request.Properties[OwinContext];
           if (owinContext != null)
           {
               return owinContext.Request.RemoteIpAddress;
           }
       }

        return null;
    }
}

Agora você pode usá-lo assim:

public class TestController : ApiController
{
    [HttpPost]
    [ActionName("TestRemoteIp")]
    public string TestRemoteIp()
    {
        return Request.GetClientIpAddress();
    }
}
Nikolai Samteladze
fonte
1
Esta solução deve usar este espaço para nome "System.Net.Http" para funcionar. Como esse é um nome de classe no Assembly System.Web.Http.dll, v5.2.2.0.
Wagner Bertolini Junior
@ WagnerBertolini, você está correto, precisa de using System.Net.Http;linha porque está se estendendo HttpRequestMessage. A menos que você esteja definindo sua extensão no System.Net.Httpnamespace, isso é muito questionável. No entanto, não tenho certeza de que seja essencial, porque será automaticamente adicionado por qualquer IDE ou ferramenta de produtividade. O que você acha?
Nikolai Samteladze
Eu estava com pressa tentando terminar um trabalho aqui e levei mais de 20 minutos para ver o que estava acontecendo no erro de compilação. Eu apenas copiei o código e criei uma classe para ele, quando compilá-lo não estava mostrando os métodos, quando usei "ir para a definição" no VS, ele me levou à minha classe e continuo sem entender o que está acontecendo, até que eu encontrou a outra classe. As extensões são um recurso bastante novo e não são usadas o tempo todo, pois acho que seria uma boa ideia economizar esse tempo.
Wagner Bertolini Junior
1
Ao usar OWIN, você pode apenas usar o OwinHttpRequestMessageExtensions para obter o contexto OWIN como: request.GetOwinContext (). Request.RemoteIpAddress
Stef Heyenrath
1
Na verdade, deve ser var ctx = request.Properties [MsHttpContext] como HttpContextWrapper; Se você transmitir, não precisará verificar nulo porque, se o elenco falhar, você receberá uma exceção
Stef Heyenrath
31

Se você realmente deseja uma linha e não planeja hospedar a API da Web:

((System.Web.HttpContextWrapper)Request.Properties["MS_HttpContext"]).Request.UserHostAddress;
zacharydl
fonte
13

As respostas acima exigem uma referência ao System.Web para converter a propriedade em HttpContext ou HttpContextWrapper. Se você não quiser a referência, poderá obter o ip usando uma dinâmica:

var host = ((dynamic)request.Properties["MS_HttpContext"]).Request.UserHostAddress;
Robin van der Knaap
fonte
-1

A solução fornecida pela carlosfigueira funciona, mas as one-liners seguras para o tipo são melhores: adicione um using System.Webacesso HttpContext.Current.Request.UserHostAddressao seu método de ação.

Warren Rumak
fonte
26
-1 isso não é confiável na API da Web porque HttpContext.Currentnão persiste corretamente em todo o pipeline de tarefas; pois todo o processamento de solicitações é assíncrono. HttpContext.Currentquase sempre deve ser evitado ao escrever o código da API da Web.
Andras Zoltan
@ Andras, eu gostaria de saber mais detalhes sobre por que usar HttpContext.Current é ruim, você conhece algum recurso valioso para isso?
cuongle 31/07/12
13
Olá @CuongLe; de fato - enquanto o problema de encadeamento pode ser um problema (embora nem sempre diretamente, se o SynchronizationContextfluxo for correto entre as tarefas); o maior problema com isso é se o seu código de serviço provavelmente será auto-hospedado (teste, por exemplo) - HttpContext.Currenté uma construção puramente do Asp.Net e não existe quando você é auto-hospedado.
Andras Zoltan
Segurança de tipo não é tudo. Esse código lançará um disco de difícil depuração NullReferenceExceptionse usado em um encadeamento (por exemplo, um garçom de tarefas, muito comum no código moderno da API da Web) ou em um contexto auto-hospedado. Pelo menos a maioria das outras respostas retornará null.
Aaronaught 26/05