Some of the work I'm doing right now involves writing a web service that has custom WSDL. By "custom" I mean "written by hand, not generated by the ASP.NET web services infrastructure". In this case, it's because we've got a bunch of external schema types that we'd like to use in a certain way, but you might want to do the same thing for a variety of reasons, including just not liking the way the generated WSDL looks.
As it turns out, using an external WSDL is fairly easy. It's just a little obscure - I had a hard time tracking down how to do it using Google, so I'm documenting the process here. Following, the steps required:
Write a custom WSDL file.
Use the tool of your choice. The one thing you're going to want to do is to omit the <wsdl:service> declaration. This bit actually will be generated by the ASP.NET infrastructure. That makes sense, since it contains a URL, and can be more easily determined at runtime.
Drop the WSDL in the web directory next to your .asmx file.
Presumably, you still want people to be able to download your custom WSDL, so putting it somewhere web-accessible is a good idea.
Mark your web service implementation class with the [WebServiceBinding] attribute.
For instance, you might do something like this:
[WebServiceBinding(Name = "MyBinding", Location = "MyCustom.wsdl")] public class MyServiceImplementation : WebService
{
// ...
}
The most important thing to set here is the Location property, which tells ASP.NET where your custom WSDL file lives. The Name property gives the name of the <wsdl:binding> from your custom WSDL, and is needed by ASP.NET to allow it to dispatch calls onto your implementation correctly.
Mark your web method with the [SoapDocumentMethod] attribute.
For example:
[WebMethod] [SoapDocumentMethod(Action = "urn:foo-com:service/DoSomething",
Binding = "MyBinding")]
public DoSomethingResponse DoSomething(DoSomethingRequest request)
{
// …
}
The Action property associates this method with the action listed in your WSDL file. It is particularly important because ASP.NET chooses which method to invoke based on the action in the incoming message.
The binding element associates the operation with a particular <wsdl:binding>, in much the same way that the Name property of the [WebServiceBinding] attribute does. It’s required to be on the [SoapDocumentMethod] attribute for proper operation, but I still haven’t quite figured out why you need both.
Observe the generated WSDL.
If you surf to the automatically-generated WSDL page (e.g. http://server/path/to/service.asmx?wsdl), you’ll observe that ASP.NET is still generating a WSDL document for you. However, because of the changes you’ve made, that WSDL consists solely of a <wsdl:import> referencing the WSDL file you wrote, and a <wsdl:service> element containing the URL for the service. It’ll look something like this:
<wsdl:definitions targetNamespace="urn:foo-com:service">
<wsdl:import namespace="" location="MyCustom.wsdl"/>
<wsdl:types/>
<wsdl:service name="MyService">
<wsdl:port name="MyBinding" binding="tns:MyBinding">
<soap:address location="http://localhost /MyService/MyService.asmx"/>
</wsdl:port>
<wsdl:port name="MyBinding1" binding="tns:GetContentBinding">
<soap12:address location="http://localhost/MyService/MyService.asmx"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
Luckily, Add Web Reference respects the <wsdl:import> and (assuming you write the WSDL correctly), .NET clients will be able to generate code off of this document successfully. Other toolkits should be able to as well, but YMMV.
Particularly when coupled with the flexibility that IXmlSerializable gives you over the serialized XML, taking control of the WSDL is a very powerful technique.