147

In ASP.NET there is a System.Web.HttpRequest class, which contains ServerVariables property which can provide us the IP address from REMOTE_ADDR property value.

However, I could not find a similar way to get the IP address of the remote host from ASP.NET Web API.

How can I get the IP address of the remote host that is making the request?

Nikolai Samteladze
  • 7,699
  • 6
  • 44
  • 70
paulius_l
  • 4,983
  • 8
  • 36
  • 42
  • Easiest way I found is here: https://stackoverflow.com/questions/53211824/get-remote-ip-address-in-azure-function – deathrace Aug 12 '21 at 13:53

7 Answers7

206

It's possible to do that, but not very discoverable - you need to use the property bag from the incoming request, and the property you need to access depends on whether you're using the Web API under IIS (webhosted) or self-hosted. The code below shows how this can be done.

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;
}
Amicable
  • 3,115
  • 3
  • 49
  • 77
carlosfigueira
  • 85,035
  • 14
  • 131
  • 171
  • Thank you! Just a small fix - HttpContextWrapper must be used instead of HttpContext. – paulius_l Mar 06 '12 at 10:23
  • 4
    Thanks, I was looking for this too. Minor improvement = extension class: https://gist.github.com/2653453 – MikeJansen May 10 '12 at 14:35
  • 32
    The WebAPI is for the most part very clean. It's a shame that code like this is needed for something trivial as an IP. – Toad Aug 17 '12 at 13:38
  • 2
    Is the `RemoteEndpointMessageProperty` the class in `System.ServiceModel.Channels` namespace, `System.ServiceModel.dll` assembly? Isn't that an assembly belonging to WCF? – Slauma Aug 22 '12 at 20:18
  • 4
    @Slauma, yes, they are. ASP.NET Web API is (currently) implemented in two "flavors", self-hosted and web-hosted. The web-hosted version is implemented on top of ASP.NET, while the self-hosted one on top of a WCF listener. Notice that the platform (ASP.NET Web API) itself is hosting-agnostic, so it's possible that someone will implement a different hosting in the future and the host will surface that property (remote endpoint) differently. – carlosfigueira Aug 23 '12 at 18:24
  • 1
    @carlosfigueira should this method be accessing `this.Request` rather than the HttpRequestMessage passed in for the self-host option? Seems like you would want to use the instance passed in. – Brandon Linton Oct 17 '12 at 14:09
  • 1
    @BrandonLinton, yes - I wrote this code on the Beta version of the framework. Nowadays you'd access this.Request. – carlosfigueira Oct 17 '12 at 14:50
  • How can I make sure, that address returned will be IPv4? – insomnium_ Oct 28 '13 at 09:22
  • 1
    @insomnium_, I don't think you can. If your controller is available in an IPv6 address, and the client accesses it via its IPv6 adapter, you'll have an IPv6 address. What you can do (and I don't know exactly how to do that without searching for it) is to only expose your API via its IPv4 address, this way you can guarantee that the clients will be talking to it via IPv4 as well. – carlosfigueira Oct 28 '13 at 13:32
  • 3
    Unfortunately, this does not work if you self-host using Owin (as it is recommended for Web API 2). Need another if there ... – Nikolai Samteladze Nov 07 '13 at 23:55
  • 1
    @carlosfigueira good answer. but what if `HttpRequestMessage` is not accessible? say if we are in a class lib? – amiry jd Apr 11 '15 at 08:54
  • I think this link can be important if the client is behind of a proxy: http://stackoverflow.com/a/740431/2387977 – Dherik Apr 14 '15 at 12:23
  • I've amended this solution to support a case of running behind a proxy or a load balancer: https://stackoverflow.com/a/65547951/1633658 – Asaff Belfer Jan 03 '21 at 08:36
80

This solution also covers Web API self-hosted using Owin. Partially from here.

You can create a private method in you ApiController that will return remote IP address no matter how you host your Web API:

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

References required:

  • HttpContextWrapper - System.Web.dll
  • RemoteEndpointMessageProperty - System.ServiceModel.dll
  • OwinContext - Microsoft.Owin.dll (you will have it already if you use Owin package)

A little problem with this solution is that you have to load libraries for all 3 cases when you will actually be using only one of them during runtime. As suggested here, this can be overcome by using dynamic variables. You can also write GetClientIpAddress method as an extension for 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;
    }
}

Now you can use it like this:

public class TestController : ApiController
{
    [HttpPost]
    [ActionName("TestRemoteIp")]
    public string TestRemoteIp()
    {
        return Request.GetClientIpAddress();
    }
}
Nikolai Samteladze
  • 7,699
  • 6
  • 44
  • 70
  • 1
    This solution should use this namespace "System.Net.Http" to work. Since this is a class name on Assembly System.Web.Http.dll, v5.2.2.0. – Wagner Bertolini Junior Jul 20 '15 at 17:26
  • @WagnerBertolini, your are correct, you need `using System.Net.Http;` line because your are extending `HttpRequestMessage`. Unless your are defining your extension in `System.Net.Http` namespace which is very questionable. Not sure that it is essential though, because it will be automatically added by any IDE or productivity tool. What do you think? – Nikolai Samteladze Jul 20 '15 at 23:21
  • 1
    I was in hurry trying to finish one job here and it took me more then 20 minutes to see whats going on build error. I just copied the code and created a class for it, when compiling it was not showing the methods, when I did use "go to definition" on VS it took me to my class, and I keep not understanding whats going on, until I found the other class. Extensions are a pretty new feature and is not used all the time, since, I think that it would be a good idea to save this time. – Wagner Bertolini Junior Jul 22 '15 at 01:12
  • I think I could not express my self about what happened, but what I need to do is to declare this class as partial and part of System.Net.Http NAMESPACE. Not just put the using there. Because where I was using it, the System.Net.Http was already added. I agree with you now that the "using" is not helpful, sorry I did not understand before. – Wagner Bertolini Junior Jul 24 '15 at 13:07
  • 1
    When using OWIN, you can just use the OwinHttpRequestMessageExtensions to get the OWIN context like : request.GetOwinContext().Request.RemoteIpAddress – Stef Heyenrath Apr 07 '16 at 17:03
  • 1
    It should actually be var ctx = request.Properties[MsHttpContext] as HttpContextWrapper; EIf you cast, you dont need to check on null because if the cast fails, you get an exception – Stef Heyenrath Oct 20 '17 at 10:36
  • How-to use HttpRequestMessage in Minimal API NET 6? – Kiquenet May 31 '22 at 14:52
  • 2
    Note, does not work in Azure Functions – user160357 Aug 17 '22 at 01:29
34

If you really want a one-liner and don't plan to self-host Web API:

((System.Web.HttpContextWrapper)Request.Properties["MS_HttpContext"]).Request.UserHostAddress;
zacharydl
  • 4,278
  • 1
  • 29
  • 23
13

Above answers require a reference to System.Web to be able to cast the property to HttpContext or HttpContextWrapper. If you don't want the reference, you are able to get the ip using a dynamic:

var host = ((dynamic)request.Properties["MS_HttpContext"]).Request.UserHostAddress;
Robin van der Knaap
  • 4,060
  • 2
  • 33
  • 48
1

When the server is behind a proxy or a load balancer the marked solution will return the internal IP address of the proxy. In our case, our production environment is using a load balancer and our development and test environments are not so I've amended the marked solution to fit both cases in the same code.

public string GetSourceIp(HttpRequestMessage httpRequestMessage)
    {
        string result = string.Empty;

        // Detect X-Forwarded-For header
        if (httpRequestMessage.Headers.TryGetValues("X-Forwarded-For", out IEnumerable<string> headerValues))
        {
            result = headerValues.FirstOrDefault();
        }
        // Web-hosting
        else if (httpRequestMessage.Properties.ContainsKey("MS_HttpContext"))
        {
            result = ((HttpContextWrapper)httpRequestMessage.Properties["MS_HttpContext"]).Request.UserHostAddress;
        }
        // Self-hosting
        else if (httpRequestMessage.Properties.ContainsKey(RemoteEndpointMessageProperty.Name))
        {
            RemoteEndpointMessageProperty prop;
            prop = (RemoteEndpointMessageProperty)httpRequestMessage.Properties[RemoteEndpointMessageProperty.Name];
            result = prop.Address;
        }

        return result;
    }
Asaff Belfer
  • 221
  • 1
  • 2
  • 8
0

Based on Asaff Belfer's answer, here's a couple of basic methods that I used to deploy this to an Azure Function (serverless / consumption plan) to obtain both the client IP and the client user agent. Also included a bit that is supposed to work in APIM, but have not tested that part yet. That information about APIM can be found at Stefano Demiliani's blog.

NOTE: these will return "(not available)" for local / self hosting. Someone can fix these to include self hosting, but I could not use those bits as the required Nuget packages from Asaff's answer (and others here) don't appear to work on the target framework I'm using (.NET 6.0).

public static string GetSourceIp(HttpRequestMessage httpRequestMessage)
{
    string result = "(not available)";
    // Detect X-Forwarded-For header
    if (httpRequestMessage.Headers.TryGetValues("X-Forwarded-For", out IEnumerable<string> headerValues))
    {
        result = headerValues.FirstOrDefault();
    }
    //for use with APIM, see https://demiliani.com/2022/07/11/azure-functions-getting-the-client-ip-address
    if (httpRequestMessage.Headers.TryGetValues("X-Forwarded-Client-Ip", out IEnumerable<string> headerValues2))
    {
        result = headerValues2.FirstOrDefault();
    }
    return result;
}

public static string GetClientUserAgent(HttpRequestMessage httpRequestMessage)
{
    string result = "(not available)";
    // Detect user-agent header
    if (httpRequestMessage.Headers.TryGetValues("user-agent", out IEnumerable<string> headerValues))
    {
        result = string.Join(", ", headerValues);
    }
    return result;
}

Usage would be as follows:

string clientUserAgent = GetClientUserAgent(httpRequestMessage);
string clientIP = GetSourceIp(httpRequestMessage);

Hosted locally on my dev box using a Chrome browser, these returned:

User IP Address: (not available)

User Client: Mozilla/5.0, (Windows NT 10.0; Win64; x64), AppleWebKit/537.36, (KHTML, like Gecko), Chrome/108.0.0.0, Safari/537.36

Hosted in my serverless function in Azure commercial, using a Chrome browser, these returned:

User IP Address: 999.999.999.999:12345 (Of course, this is not a real IP address. I assume the 12345 part is a port number.)

User Client: Mozilla/5.0, (Windows NT 10.0; Win64; x64), AppleWebKit/537.36, (KHTML, like Gecko), Chrome/108.0.0.0, Safari/537.36

Speedcat
  • 146
  • 1
  • 9
-3

The solution provided by carlosfigueira works, but type-safe one-liners are better: Add a using System.Web then access HttpContext.Current.Request.UserHostAddress in your action method.

Warren Rumak
  • 3,824
  • 22
  • 30
  • 27
    -1 this can't be trusted in web api because `HttpContext.Current` is not persisted correctly throughout the task pipeline; since all request handling is asynchronous. `HttpContext.Current` should almost always be avoided when writing Web API code. – Andras Zoltan Jul 04 '12 at 23:32
  • @Andras, I would like to know more detail why using HttpContext.Current is bad, do you know any valuable resource for this? – cuongle Jul 31 '12 at 17:30
  • 13
    Hi @CuongLe; in fact - whilst the threading issue can be a problem (although not always directly if the `SynchronizationContext` is flowed correctly between tasks); the biggest issue with this is if your service code is ever likely to be self-hosted (testing, for example) - `HttpContext.Current` is a purely Asp.Net construct and doesn't exist when you self-host. – Andras Zoltan Jul 31 '12 at 22:29
  • Type safety isn't everything. This code will throw a hard-to-debug `NullReferenceException` if used from a thread (e.g. a task awaiter, very common in modern Web API code) or in a self-hosted context. At least most of the other answers will just return `null`. – Aaronaught May 26 '14 at 02:08