There are few solution, I will sort them from simplicity to accuracy. :)
1. Don't worry about URLs in WSDL. **Just use `Url` property** on a client side, like in that case:
```
public class MyWebService : RemoteService
{
public MyWebService() : base()
{
Url = ConfigurationManager.AppSettings["MyServiceCustomUrl"];
}
}
```
Obvious drawback: most probably you will not able to test the service properly using generated html (I mean generated forms for web methods).
2. **Use custom `wsdlHelpGenerator`**.
I didn't test this solution, but at a glance it looks simply enough if you create your own based on original **DefaultWsdlHelpGenerator.aspx** (you can find it in *C:\Windows\Microsoft.NET\Framework\** folders, in my case it was *C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\DefaultWsdlHelpGenerator.aspx*).
3. Use `soapExtensionReflectorTypes`. You already did that.
A drawback: no control over HttpPost/HttpGet protocols.
4. **Implement IIS rewrite module**. It will allow you to modify any output produced by your service. Can be used together with #3.
Create 2 classes, stream decorator:
```
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;
}
}
```
and the module:
```
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()
{
}
}
```
Then register the module in 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>
```
Please note, the registration above is for integration mode, for classic one you need to register it inside `system.web`.
So, in our legacy project I used 3+4 approach, but if I'd want to do that again, I will try to use #2 approach instead. :)
There are few solution, I will sort them from simplicity to accuracy. :)
1. Don't worry about URLs in WSDL. **Just use `Url` property** on a client side, like in that case:
```
public class MyWebService : RemoteService
{
public MyWebService() : base()
{
Url = ConfigurationManager.AppSettings["MyServiceCustomUrl"];
}
}
```
Obvious drawback: most probably you will not able to test the service properly using generated html (I mean generated forms for web methods).
2. **Use custom `wsdlHelpGenerator`**.
I didn't test this solution, but at a glance it looks simply enough if you create your own based on original **DefaultWsdlHelpGenerator.aspx** (you can find it in *C:\Windows\Microsoft.NET\Framework\** folders, in my case it was *C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\DefaultWsdlHelpGenerator.aspx*).
3. Use `soapExtensionReflectorTypes`. It will allow you to do anything with your HttpSoap protocol addresses.
Create a handler (sample below will change http to https):
```
public class HttpsReflector : SoapExtensionReflector
{
public override void ReflectMethod()
{
// nothing to override
}
public override void ReflectDescription()
{
ServiceDescription description = ReflectionContext.ServiceDescription;
foreach (System.Web.Services.Description.Service service in description.Services)
{
foreach (Port port in service.Ports)
{
foreach (ServiceDescriptionFormatExtension extension in port.Extensions)
{
if (extension is SoapAddressBinding binding)
{
binding.Location = binding.Location.Replace("http://", "https://");
}
}
}
}
}
}
```
Register it in web.config:
```
<system.web>
<webServices>
<soapExtensionReflectorTypes>
<!-- Required to replace http in addresses for HttpSoap protocol -->
<add type="MyWebServices.WsdlFixHttp.HttpsReflector, MyWebServices"/>
</soapExtensionReflectorTypes>
</webServices>
</system.web>
```
A drawback: no control over HttpPost/HttpGet protocols.
4. **Implement IIS rewrite module**. It will allow you to modify any output produced by your service. Can be used together with #3.
Create 2 classes, stream decorator:
```
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;
}
}
```
and the module:
```
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()
{
}
}
```
Then register the module in 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>
```
Please note, the registration above is for integration mode, for classic one you need to register it inside `system.web`.
So, in our legacy project I used 3+4 approach, but if I'd want to do that again, I will try to use #2 approach instead. :)