【问题标题】:Get the IP address of the remote host获取远程主机的IP地址
【发布时间】:2012-03-22 21:07:22
【问题描述】:

在 ASP.NET 中有一个System.Web.HttpRequest 类,其中包含ServerVariables 属性,它可以从REMOTE_ADDR 属性值中为我们提供IP 地址。

但是,我找不到类似的方法来从 ASP.NET Web API 获取远程主机的 IP 地址。

如何获取发出请求的远程主机的 IP 地址?

【问题讨论】:

标签: c# asp.net-web-api


【解决方案1】:

可以这样做,但不是很容易发现 - 您需要使用传入请求中的属性包,而您需要访问的属性取决于您是使用 IIS(webhosted)下的 Web API 还是 self -托管。下面的代码展示了如何做到这一点。

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

【讨论】:

  • 谢谢,我也在找这个。小改进 = 扩展类:gist.github.com/2653453
  • WebAPI 大部分都非常干净。很遗憾,像 IP 这样的微不足道的东西需要这样的代码。
  • RemoteEndpointMessagePropertySystem.ServiceModel.Channels 命名空间System.ServiceModel.dll 程序集中的类吗?那不是属于 WCF 的程序集吗?
  • @Slauma,是的,他们是。 ASP.NET Web API(当前)以两种“风格”实现,自托管和网络托管。 Web 托管版本是在 ASP.NET 之上实现的,而自托管版本是在 WCF 侦听器之上实现的。请注意,平台(ASP.NET Web API)本身与主机无关,因此将来有人可能会实现不同的主机,主机将以不同的方式显示该属性(远程端点)。
  • 不幸的是,如果您使用 Owin 自托管,这将不起作用(推荐用于 Web API 2)。如果有的话需要另一个...
【解决方案2】:

此解决方案还涵盖使用 Owin 自托管的 Web API。部分来自here

您可以在 ApiController 中创建一个私有方法,无论您如何托管您的 Web API,它都会返回远程 IP 地址:

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

需要参考:

  • HttpContextWrapper - System.Web.dll
  • RemoteEndpointMessageProperty - System.ServiceModel.dll
  • OwinContext - Microsoft.Owin.dll(如果您使用 Owin 包,您将拥有它)

此解决方案的一个小问题是,您必须为所有 3 种情况加载库,而在运行时您实际上只使用其中一种。正如here 建议的那样,这可以通过使用dynamic 变量来克服。您还可以编写GetClientIpAddress 方法作为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;
    }
}

现在你可以像这样使用它了:

public class TestController : ApiController
{
    [HttpPost]
    [ActionName("TestRemoteIp")]
    public string TestRemoteIp()
    {
        return Request.GetClientIpAddress();
    }
}

【讨论】:

  • 这个解决方案应该使用这个命名空间“System.Net.Http”来工作。由于这是 Assembly System.Web.Http.dll 上的类名,v5.2.2.0。
  • @WagnerBertolini,你是对的,你需要using System.Net.Http; 行,因为你正在扩展HttpRequestMessage。除非您在 System.Net.Http 命名空间中定义您的扩展,否则这是非常值得怀疑的。但不确定它是否必不可少,因为它会被任何 IDE 或生产力工具自动添加。你怎么看?
  • 我急于在这里完成一项工作,我花了 20 多分钟才看到构建错误发生了什么。我只是复制了代码并为它创建了一个类,在编译时它没有显示方法,当我在 VS 上使用“转到定义”时,它把我带到了我的班级,我一直不明白发生了什么,直到我找到了另一个班级。扩展是一个相当新的功能,并不是一直都在使用,因为,我认为节省这段时间是个好主意。
  • 使用 OWIN 时,您可以使用 OwinHttpRequestMessageExtensions 来获取 OWIN 上下文,例如:request.GetOwinContext().Request.RemoteIpAddress
  • 其实应该是 var ctx = request.Properties[MsHttpContext] as HttpContextWrapper; E如果你转换,你不需要检查 null 因为如果转换失败,你会得到一个异常
【解决方案3】:

如果您真的想要一个单行且不打算自托管 Web API:

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

【讨论】:

    【解决方案4】:

    以上答案需要引用 System.Web 才能将属性转换为 HttpContext 或 HttpContextWrapper。如果您不想要引用,您可以使用动态获取 ip:

    var host = ((dynamic)request.Properties["MS_HttpContext"]).Request.UserHostAddress;
    

    【讨论】:

      【解决方案5】:

      当服务器位于代理或负载平衡器后面时,标记的解决方案将返回代理的内部 IP 地址。在我们的例子中,我们的生产环境使用负载均衡器,而我们的开发和测试环境没有,所以我修改了标记的解决方案,以在相同的代码中适应这两种情况。

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

      【讨论】:

      • 正是我想要的。大多数值得生产的应用程序都在重写标头的代理后面,这是处理该问题的方法..
      【解决方案6】:

      carlosfigueira 提供的解决方案有效,但类型安全的单行更好:添加 using System.Web 然后在您的操作方法中访问 HttpContext.Current.Request.UserHostAddress

      【讨论】:

      • -1 这不能在 web api 中被信任,因为 HttpContext.Current 没有在整个任务管道中正确持久化;因为所有请求处理都是异步的。 HttpContext.Current 在编写 Web API 代码时几乎总是应该避免的。
      • @Andras,我想更详细地了解为什么使用 HttpContext.Current 不好,你知道任何有价值的资源吗?
      • 嗨@CuongLe;事实上 - 虽然线程问题可能是一个问题(尽管如果SynchronizationContext 在任务之间正确流动,则并不总是直接的);最大的问题是您的服务代码是否可能是自托管的(例如测试)-HttpContext.Current 是一个纯粹的 Asp.Net 构造,并且在您自托管时不存在。
      • 类型安全并不是一切。如果从线程(例如任务等待器,在现代 Web API 代码中非常常见)或自托管上下文中使用,此代码将引发难以调试的 NullReferenceException。至少大多数其他答案只会返回null
      猜你喜欢
      • 2012-03-01
      • 1970-01-01
      • 2016-10-17
      • 1970-01-01
      • 1970-01-01
      • 2011-05-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多