我们有一些旧的 ASMX Web 服务,它们已移至额外的安全层后面。 WSDL 是自动生成的运行时,现在使用其运行的服务器的 IP,而不是公共 DNS。如何通过代码或 web.config 更改公共 DNS 的 IP?
Web 服务通过带有查询字符串的 HTTP POST/GET 提供 SOAP,以及更常见的场景,通过带有 SOAP-request-body 的 HTTP POST 提供 SOAP。我想将本地 IP 与运行时生成的 WSDL 的公共 DNS 名称进行切换。我已经使用 SoapExtensionReflector 在常见场景中成功做到了这一点,但它不适用于使用查询字符串的 WSDL 端口。
SoapExtensionReflector 的简化版本如下所示:
public class MySoapExtensionReflector : SoapExtensionReflector
{
public override void ReflectDescription()
{
var description = ReflectionContext.ServiceDescription;
foreach (Service service in description.Services)
{
foreach (Port port in service.Ports)
{
foreach (ServiceDescriptionFormatExtension extension in port.Extensions)
{
SoapAddressBinding binding = extension as SoapAddressBinding;
if (null != binding)
{
binding.Location = Regex.Replace(binding.Location,
"http://localhost:2017",
"https://example.com");
}
}
}
}
}
public override void ReflectMethod()
{
// No implementation
}
}
web.config的相关部分:
<system.web>
<webServices>
<soapExtensionReflectorTypes>
<add type="MyService.MySoapExtensionReflector, MyService"/>
</soapExtensionReflectorTypes>
<protocols>
<add name="HttpGet"/>
<add name="HttpPost"/>
</protocols>
</webServices>
</system.web>
这会在 WSDL 中产生以下结果:
<wsdl:service name="MyService">
<wsdl:port name="MyServiceSoap" binding="tns:MyServiceSoap">
<soap:address location="https://example.com/Myservice.asmx"/>
</wsdl:port>
<wsdl:port name="MyServiceSoap12" binding="tns:MyServiceSoap12">
<soap12:address location="https://example.com/Myservice.asmx"/>
</wsdl:port>
<wsdl:port name="MyServiceHttpGet" binding="tns:MyServiceHttpGet">
<http:address location="http://localhost:2017/Myservice.asmx"/>
</wsdl:port>
<wsdl:port name="MyServiceHttpPost" binding="tns:MyServiceHttpPost">
<http:address location="http://localhost:2017/Myservice.asmx"/>
</wsdl:port>
</wsdl:service>
这并不是理想的结果。最后两个端口由协议生成 - web.config 中的 HttpPost 和 HttpGet 定义。
如何更改此设置,以便 WSDL 端口具有所需的主机名?
结果应该是这样的:
<wsdl:service name="MyService">
<wsdl:port name="MyServiceSoap" binding="tns:MyServiceSoap">
<soap:address location="https://example.com/Myservice.asmx"/>
</wsdl:port>
<wsdl:port name="MyServiceSoap12" binding="tns:MyServiceSoap12">
<soap12:address location="https://example.com/Myservice.asmx"/>
</wsdl:port>
<wsdl:port name="MyServiceHttpGet" binding="tns:MyServiceHttpGet">
<http:address location="http://example.com/Myservice.asmx"/>
</wsdl:port>
<wsdl:port name="MyServiceHttpPost" binding="tns:MyServiceHttpPost">
<http:address location="http://example.com/Myservice.asmx"/>
</wsdl:port>
</wsdl:service>
解决方案很少,我会按照从简单到准确的顺序进行排序。 :)
Url
属性,就像在这种情况下: public class MyWebService : RemoteService
{
public MyWebService() : base()
{
Url = ConfigurationManager.AppSettings["MyServiceCustomUrl"];
}
}
明显的缺点:很可能您将无法使用生成的 html(我的意思是为 Web 方法生成的表单)正确测试服务。
wsdlHelpGenerator
。我没有测试这个解决方案,但乍一看,如果您基于原始的 DefaultWsdlHelpGenerator.aspx 创建自己的解决方案,它看起来很简单(您可以在 C:\Windows\Microsoft.NET\Framework* 文件夹中找到它) ,就我而言,它是 C:\Windows\Microsoft.NET\Framework 4.0.30319\Config\DefaultWsdlHelpGenerator.aspx)。
soapExtensionReflectorTypes
。你已经这么做了。缺点:无法控制 HttpPost/HttpGet 协议。
创建2个类,流装饰器:
public class StreamWatcher : Stream
{
private readonly Stream _base;
private readonly MemoryStream _memoryStream = new MemoryStream();
public StreamWatcher(Stream stream)
{
_base = stream;
}
public override void Flush()
{
_base.Flush();
}
public override int Read(byte[] buffer, int offset, int count)
{
return _base.Read(buffer, offset, count);
}
public override void Write(byte[] buffer, int offset, int count)
{
_memoryStream.Write(buffer, offset, count);
_base.Write(buffer, offset, count);
}
public override string ToString()
{
return Encoding.UTF8.GetString(_memoryStream.ToArray());
}
public override bool CanRead => _base.CanRead;
public override bool CanSeek => _base.CanSeek;
public override bool CanWrite => _base.CanWrite;
public override long Seek(long offset, SeekOrigin origin) => _base.Seek(offset, origin);
public override void SetLength(long value) => _base.SetLength(value);
public override long Length => _base.Length;
public override long Position
{
get => _base.Position;
set => _base.Position = value;
}
}
和模块:
public class WsdlFixHttpModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.EndRequest += (s, e) => OnEndRequest(s, e);
context.BeginRequest += (s, e) => OnBeginRequest(s, e);
}
private void OnBeginRequest(object sender, EventArgs e)
{
HttpContext httpContext = HttpContext.Current;
if (httpContext.Request.Url.Query.Equals("?WSDL", StringComparison.InvariantCultureIgnoreCase)
|| IsAsmxGetRequest(httpContext))
{
httpContext.Response.Filter = new StreamWatcher(httpContext.Response.Filter);
}
}
private void OnEndRequest(object sender, EventArgs e)
{
HttpContext httpContext = HttpContext.Current;
string oldValue = "", newValue = "";
bool replacementRequired = false;
if (httpContext.Request.Url.Query.Equals("?WSDL", StringComparison.InvariantCultureIgnoreCase))
{
oldValue = ":address location=\"http://";
newValue = ":address location=\"https://";
replacementRequired = true;
}
else if (IsAsmxGetRequest(httpContext))
{
oldValue = "<form target=\"_blank\" action='http://";
newValue = "<form target=\"_blank\" action='https://";
replacementRequired = true;
}
if (replacementRequired)
{
string wsdl = httpContext.Response.Filter.ToString();
wsdl = wsdl.Replace(oldValue, newValue);
httpContext.Response.Clear();
httpContext.Response.Write(wsdl);
httpContext.Response.End();
}
}
private static bool IsAsmxGetRequest(HttpContext httpContext)
{
return httpContext.Response.ContentType == "text/html"
&& httpContext.Request.CurrentExecutionFilePathExtension.Equals(".asmx", StringComparison.InvariantCultureIgnoreCase)
&& httpContext.Request.HttpMethod == "GET";
}
public void Dispose()
{
}
}
然后在web.config中注册模块:
<system.webServer xdt:Transform="Insert">
<modules>
<!-- Required to replace http in addresses for HttpPost/HttpGet protocols -->
<add name="WsdlFixHttpModule" type="MyWebServices.WsdlFixHttp.WsdlFixHttpModule, MyWebServices"/>
</modules>
</system.webServer>
请注意,上面的注册是集成模式,经典模式需要在里面注册
system.web
。
因此,在我们的遗留项目中,我使用了 3+4 方法,但如果我想再次这样做,我会尝试使用#2 方法。 :)