Thursday, December 15, 2005

Using a Custom WSDL File in ASP.NET Web Services

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.

36 comments:

  1. Craig,



    thanks for this. I am trying to figure something out though. I get an error on the client proxy generation through Add Web Reference. c:\Inetpub\wwwroot\BlahTwoClient\Web References\localhost\Reference.map(1): Custom tool warning: Ignore duplicate WSDL document 'http://localhost/BlahTwo/Service1.asmx?wsdl' with TargetNamespace 'http://mynamespace'.



    It all works but I am afraid this is wrong. I am pretty sure it's in my usage of the namespaces.

    ReplyDelete
  2. What does your Reference.map file look like? Are you using imports in either your WSDL or your schema?

    ReplyDelete
  3. I have no imports(I did but removed them).



    Reference.map

    <DiscoveryClientResult referenceType="System.Web.Services.Discovery.ContractReference" url="file:///c:/inetpub/wwwroot/wsdl/blahtwo.wsdl" filename="blahtwo.wsdl" />

    <DiscoveryClientResult referenceType="System.Web.Services.Discovery.DiscoveryDocumentReference" url="http://localhost/BlahTwo/Service1.asmx?disco" filename="Service1.disco" />

    <DiscoveryClientResult referenceType="System.Web.Services.Discovery.ContractReference" url="http://localhost/BlahTwo/Service1.asmx?wsdl" filename="Service1.wsdl" />



    Thanks for your help on this.

    ReplyDelete
  4. Hmm. Well, I'm not really sure what the problem is. Perhaps you should delete the web references entirely (including going in to the filesystem and nuking the directory) and then start over.

    ReplyDelete
  5. ASP.NET Podcast Show #30 - Minimizing the ASP.NET ViewState

    Part #2 [Via: Wallym ]

    Complex data binding...

    ReplyDelete
  6. Craig, in WSCF 0.6 we will enable exactly this custom behavior: you can choose wheter you want your modeled WSDL to be returned by the .asmx or not.

    In addition, we have developed a fixed Default***.aspx which will ship with WSCF 0.6.

    Once we have WSCF 0.6 out of the door I will post the code on my blog.



    Cheers!

    ReplyDelete
  7. Nice. I haven't been using WCSF lately, but I'll have to take a look at it again when you ship 0.6.

    ReplyDelete
  8. Craig,

    Thanks again. I figured out what happened. You said it in your post, "assuming you write the WSDL correctly". Funny how that works. Namespace clashes.



    By the way(to Christian also), I use WSCF .51 and went the binding route due to this not being in WSCF. Greatly appreciated if the Thinktecture guys get this in.



    thanks again

    ReplyDelete
  9. I'm probably bumbing up against my lack of WSDL knowledge but I'm having a problem using your example to create a service that passes the WS-I Basic profile 1.1 validation step ( in Visual Stuid 2005 ).



    I started with a shell web service and added a web method that takes a single string as a parameter and returns a string as the result.



    I compiled and auto generated the WSDL. At this point the service passes the WS-I basic profile validation.



    I then did the following:



    1. Saved the auto generated WSDL to a file.

    2. Commented out the <wsdl:service> section.

    3. Added the WebServiceBinding attribute as you described in your blog entry. ( Location set to the saved WSDL )

    4. Added the SoapDocumentMethod as you described in your blog entry.



    When I run the service now I get warning that the service does not conform to the WS-I Basic Profile because the SOAP 1.1 binding was not found.



    The auto gen WSDL now looks like the example in your blog entry.



    Am I missing something obvious?



    thanks



    Mike

    ReplyDelete
  10. How are you verifying compliance? Is it possible the tool simply doesn't like the wsdl:import?



    When you view the WSDL that it's creating, it includes both SOAP 1.1 and 1.2 bindings, right? That's what makes me think it's not respecting the import.

    ReplyDelete
  11. When you use the default 'debug' mode to run a web service in VS2005 it will check for compliance -controlled of course by various web.config and webservice attribute parameters.



    I don't know if the visual studio isn't handling the import. I'll run it through the WS-I org tool and see if that likes it better.

    ReplyDelete
  12. I'm experiencing the same problem as Mike. Just run the web services .asmx from the Visual Studio 2005 environment and you'll get the error. Any idea what causes this?

    ReplyDelete
  13. Nice work, Craig. Thanks.

    ReplyDelete
  14. I'm having the same problem as Mike (up above) - maybe it's a Mike thing! Any suggestions as to how to fix this? Or how did you get around it Mike?



    Thanks!

    ReplyDelete
  15. iam facing saop action error when my service is accessed by a java client so tell me how to access this service by a java client

    ReplyDelete
  16. giving binding error when i am implementing the above example

    ReplyDelete
  17. bhargavi: Some Java clients don't set the SOAPAction header properly. You'll need to make sure it's set to the value specified in the WSDL. How you do this is dependent on the software you're using. Consult the documentation.



    hema: Can you give me more information?

    ReplyDelete
  18. Mike & Mike: sorry it has taken me so long to reply. I am lame.



    Since you need to generate the binding yourself, what does it look like in your WSDL file?



    Also, I should point out that for the project I was using this approach on, I ultimately ditched this approach and went with an approach where we just wrote the entire WSDL by hand, then wrote an HttpHandler to return it when a GET with ?wsdl on it came in.

    ReplyDelete
  19. when ever i try to use this webservice some other application using add webreference error is coming like

    'unabel to download following file from

    http://localhost:4296/AdviceSheetWebservices/AdvceSheetService.asmx?WSDL'



    can u help me it's urgent

    ReplyDelete
  20. Well, what happens when you open the WSDL page in a browser?

    ReplyDelete
  21. Did anyone sort this out? I'm getting the basic profile 1.1 error and i can't add a web reference to the service.

    ReplyDelete
  22. Still get the basic profile error but can now add a web reference. Need to ensure the binding name in the attributes matches the binding name in the wsdl. By default this is set to <Servicename>Soap.

    ReplyDelete
  23. Silverlight [Via: gduthie ] Tim Sneath on Silverlight [Via: gduthie ] More on Lazy Loading vs. Pre-loading...

    ReplyDelete
  24. Has anybody used IXmlSerializable class as input and return(response) type for a webservic method?

    If so, do we need 2 xsd schema`s for validating input/response ?

    Do we need to create custom wsdl file?

    How do we create wsdl?



    Please reply asap. got a deadline by end of today.

    ReplyDelete
  25. I also get the basic profile error. Has anybody successfully got this working? If not, maybe this Blog should be deleted so we all aren't wasting our time.

    ReplyDelete
  26. Create a global.asax.cs file and put the following code and twik to meet your needs:

    (probably some way to do this in the web.config.) There was a good article about this on http://www.123aspx.com, but I can't find it now.



    namespace localhost

    {

    public class Global : System.Web.HttpApplication

    {

    protected void Application_BeginRequest(Object sender, EventArgs e)

    {

    string requestPath = Request.RawUrl.Trim().ToLower();

    if (requestPath.IndexOf("?wsdl") > 0 || requestPath.IndexOf("?wsdl") > 0)

    {

    Response.Redirect("http://localhost/MyWebService/MyWSDL.wsdl");

    }

    }

    }

    }

    ReplyDelete
  27. if (requestPath.IndexOf("?wsdl") > 0 || requestPath.IndexOf("?wsdl") > 0)

    Should Be:

    if (requestPath.IndexOf("?wsdl") > 0 || requestPath.IndexOf("?disco") > 0)





    ReplyDelete
  28. I receive this error:



    "Unable to import WebService/Schema. Element binding named WsdlBind from namespace ...is missing."



    Anyone come across this?

    ReplyDelete
  29. I'm having trouble mapping your "urn:foo-com:service/DoSomething" to my own WSDL. Can anyone share an actual example of a working WSDL and the matching code? Thanks!

    ReplyDelete
  30. I am a new to web service world. For the leaning purpose, I have
    created a web service in 2005 with one asmx.cs file, in this file, I
    have one method called GetUser() which returns a custom user object,
    something like

    [WebMethod]
    public User GetUser()

    When I run it from IDE, I noticed after clicking the Service
    Description link that the XSD for my custom object user is missing an
    element id, which is a read only property of object, and it would
    shown if it's changed to read and write. Then, I followed above example tried to use a custom wsdl that contains xsd of an int id element for my user object, but for some reasons, when I run it, this id element still disappeared, any ideas?

    Thanks a lot.

    ReplyDelete
  31. I have an ASP.Net Website Webservice and this method of specifying the custom wsdl does not work in it. Is there any idea why?

    ReplyDelete
  32. At beginning I noticed the generated wsdl has something

    instead of


    any comments for that?

    ReplyDelete
  33. Thanks a lot for the article. It solved my problem.

    ReplyDelete
  34. [SoapDocumentMethod(Action = "urn:foo-com:service/DoSomething",
    Binding = "MyBinding")]
    public DoSomethingResponse DoSomething(DoSomethingRequest request)

    I am using VS2008.

    It tells me it cannot find xxxResponse and xxxRequest.
    What do I need to reference to make it work? The WSDL?
    How do I do it?

    Thanks

    ReplyDelete
  35. I was able to reference the wsdl, compile and deploy without errors. But my webservice is not exposed, it looks like this.

    [WebMethod]
    [SoapHeader("soapHeaders")]
    [SoapDocumentMethod(Action = "http://schema/documentation/getReportTypes_2_0",
    Binding = "Documentation_2")]
    public getReportTypes_2_0Response getReportTypes_2_0(getReportTypes_2_0Request request)
    {
    return null;
    }

    Any ideas?

    ReplyDelete
  36. The method is not exposed on the sample page but when I call it with SOAPUI it works. But my request object is always empty. Any ideas?

    ReplyDelete