Retrieving the client’s IP address in ASP.NET Web API

· 578 words · 3 minutes to read

One of the common tasks, for logging or authorization purposes, is to obtain the IP address of the client accessing your Web API.

There are different ways to achieve this depending on whether you are web hosting (through HttpContext) or whether you are self hosting (through WCF’s RemoteEndpointMessageProperty).

To avoid the redundant code, we can unify those approaches under a single extension method, which you can use across all your projects.

Obtaining the client IP in ASP.NET Web API web host 🔗

To get the IP from the incoming request in the web host scenarios, we have to get hold of HttpContext. This is done very easily, through the Ms_HttpContext request property key.

if (request.Properties.ContainsKey["MS_HttpContext"])  
{  
var ctx = request.Properties["MS_HttpContext"] as HttpContextWrapper;  
if (ctx != null)  
{  
var ip = ctx.Request.UserHostAddress;  
//do stuff with IP  
}  
}

Of course this requires that you reference System.Web as that’s where HttpContextWrapper resides.

Obtaining the client IP in ASP.NET Web API self host 🔗

In self host scenarios, you can access client information through RemoteEndpointMessageProperty, which, similarly to web host, is stored in the properties of the incoming HttpRequestMessage. The property is added to each incoming message to a WCF service through both the HTTP and TCP transports. Since Web API self host uses WCF core, it’s there for our API host too.

if (request.Properties.ContainsKey[RemoteEndpointMessageProperty.Name])  
{  
var remote = request.Properties[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty;

if (remote != null)  
{  
var ip = remote.Address;  
//do stuff with IP  
}  
}

In this case, we look for the property key “RemoteEndpointMessageProperty.Name” which simply is the fully qualified name - “System.ServceModel.Channels.RemoteEndpointMessageProperty”.

Since we cast to RemoteEndpointMessageProperty we need to reference System.ServiceModel.dll, as that’s where this class is declared. You can read up more on MSDN.

Unifying the two approaches 🔗

Of course it’s not very convenient to have two separate bits of code that you have to drag along to each type of project (self vs web host). We can easily wrap both approaches in a single extension method, and hang it off HttpRequestMethod.

Since we don’t want self host to reference System.Web, and we don’t want web host to unnecessarily reference System.ServiceModel, we could use dynamic to avoid strong typing and defer the type evaluation until runtime (by-pass compile time type check). Since we deal with, effectively, an if-else scenario here, only one code path will ever be used in each type of hosting anyway.

Here’s the complete solution:

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

public static string GetClientIpAddress(this HttpRequestMessage request)  
{  
if (request.Properties.ContainsKey(HttpContext))  
{  
dynamic ctx = request.Properties[HttpContext];  
if (ctx != null)  
{  
return ctx.Request.UserHostAddress;  
}  
}

if (request.Properties.ContainsKey(RemoteEndpointMessage))  
{  
dynamic remoteEndpoint = request.Properties[RemoteEndpointMessage];  
if (remoteEndpoint != null)  
{  
return remoteEndpoint.Address;  
}  
}

return null;  
}  
}

You can now safely use this extension in each type of Web API project, and not worry about having to reference unnecessary DLLs just to make the code compile (as we would have to, if we wouldn’t use dynamic).

It’s important to mention that you have to reference System.ServiceModel in self hosted projects, as it’s not included by default (contrary to System.Web always being there for web host).

You can now use this anywhere, where you have access to the current HttpRequestMessage. For example:

public class MyHandler : DelegatingHandler  
{  
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)  
{  
Debug.WriteLine(request.GetClientIpAddress());  
return base.SendAsync(request, cancellationToken);  
}  
}

If you use WebApiContrib (and you should!), I have added this extension method there too.

About


Hi! I'm Filip W., a software architect from Zürich 🇨🇭. I like Toronto Maple Leafs 🇨🇦, Rancid and quantum computing. Oh, and I love the Lowlands 🏴󠁧󠁢󠁳󠁣󠁴󠁿.

You can find me on Github, on Mastodon and on Bluesky.

My Introduction to Quantum Computing with Q# and QDK book
Microsoft MVP