views:

122

answers:

3

My application has its own scripting language of which I cannot get rid of (lots of customer-specific scripts written). Now my customers are asking if it would be possible to call a SOAP service from within that scripting language. Of course, the SOAP service that needs to be called will be different for every customer. This leaves me with several options:

  • Use the WSDL utility to generate customer-specific SOAP client proxies and put the customer-specific logic in my application
  • Use the WSDL utility to generate customer-specific SOAP client proxies, put the customer-specific logic in customer-specific DLL's and foresee a plug-in system where the application can call the plug-in in a generic way
  • Write a generic module that dynamically generates the SOAP call

The first 2 options are no real alternative in my case since I don't want any customer-specific logic in the application, or customer-specific DLL's.

For me, the 3rd option is, in the long term, the best since it allows my consultant colleagues to call the SOAP service via my scripting language without doing any customer-specific developments. Dynamically adding functions to my scripting language is not a problem, generating the dynamic SOAP call is.

I started by looking at the output of the WSDL utility. Then I started to remove things until it didn't work anymore. The following piece of code is the one still working:

[System::CodeDom::Compiler::GeneratedCodeAttribute(L"wsdl", L"4.0.30319.1"), 
System::Diagnostics::DebuggerStepThroughAttribute, 
System::ComponentModel::DesignerCategoryAttribute(L"code"),
System::Web::Services::WebServiceBindingAttribute(Name=L"MyOwnScriptingSoapClient", Namespace=L"http://microsoft.com/webservices/")]
public ref class MyWebService : public System::Web::Services::Protocols::SoapHttpClientProtocol
   {
    public:
      MyWebService() {}

    public:
      [System::Web::Services::Protocols::SoapDocumentMethodAttribute(L"http://microsoft.com/webservices/GetPrimeNumbers", RequestNamespace=L"http://microsoft.com/webservices/", 
       ResponseNamespace=L"http://microsoft.com/webservices/", Use=System::Web::Services::Description::SoapBindingUse::Literal, ParameterStyle=System::Web::Services::Protocols::SoapParameterStyle::Wrapped)]
      System::String^  GetPrimeNumbers(System::Int32 max);
  };

inline System::String^  MyWebService::GetPrimeNumbers(System::Int32 max) {
    cli::array< System::Object^  >^  results = this->Invoke(L"GetPrimeNumbers", gcnew cli::array< System::Object^  >(1) {max});
    return (cli::safe_cast<System::String^  >(results[0]));
}

The URL of the web service can be dynamically by setting the Url property, but I can't find a way to make the method name dynamic.

Adding a generic method like this still seems to work:

...
[System::Web::Services::Protocols::SoapDocumentMethodAttribute(L"http://microsoft.com/webservices/GetPrimeNumbers", RequestNamespace=L"http://microsoft.com/webservices/", 
 ResponseNamespace=L"http://microsoft.com/webservices/", Use=System::Web::Services::Description::SoapBindingUse::Literal, ParameterStyle=System::Web::Services::Protocols::SoapParameterStyle::Wrapped)]
cli::array< System::Object^  >^  CallWs(cli::array< System::Object^  >^ args);
...

inline cli::array< System::Object^  >^  MyWebService::CallWs(cli::array< System::Object^  >^ args) {
    cli::array< System::Object^  >^  results = this->Invoke(L"GetPrimeNumbers", args);
    return results;

But as soon as I remove the GetPrimeNumbers method, the call doesn't work anymore and reports the following error:

Unhandled Exception: System.ArgumentException: GetPrimeNumbers Web Service method name is not valid.
   at System.Web.Services.Protocols.SoapHttpClientProtocol.BeforeSerialize(WebRequest request, String methodName, Object[] parameters)
   at System.Web.Services.Protocols.SoapHttpClientProtocol.Invoke(String methodName, Object[] parameters)
   at MyWebService.CallWs(Object[] args)
   at main(Int32 argc, SByte** argv)
   at _mainCRTStartup()

Also, changing the web service name in the SoapDocumentMethodAttribute attribute (e.g. to GetPrimo), gives this same error.

Therefore, my questions:

  • Does it make sense to continue on this path, i.e. looking at the WSDL generated logic trying to 'generalize' the call to a (any) SOAP service or will this simply never work?
  • Are there any other good ways of generating the SOAP call in a dynamic way (using .Net)?
  • Or is the only method to create the XML (Soap Envelope) yourself to do the SOAP call?
  • Any chance of finding some example code that I can continue to work on?

Thanks in advance, Patrick

+1  A: 

You can give a mechanism in your scripting language to call an external .NET assembly. You can use reflection to find the function and call them. Similar to way the plugins work in many applications.

This will not only allow the customers to call an external web service, but can be used for many other enhancements.

Or if you don't want to rely on your customer writing .NET assemblies, you can generate the Soap requests yourself by asking the user soap message name, parameter names, their type and values, service url etc. But I think it is not going to be an easy path and I admit that I never have done it myself.

Hemant
I also thought of this idea, but I don't want this for several reasons: I don't want to confuse my consultants with yet another language of doing customer specific things, I want to prevent my consultants from doing lots of fancy, but unnecessary things once they can use C# (this will become a nightmare for support). I really want to limit it to dirct calls from my scripting language to the web service.
Patrick
Hemant, I gave your idea a thought again and given all the problems with the Microsoft way of calling Webservices (they even rely on the fact that the method of your class has the same name as the web service method, yuck), I think that is still a good idea, and it may even open up other possibilities. Now it's up to me to prevent my consultants from writing customer-specific in C# and only limit this to things like customer-provided .Net assemblies, and WSDL generated WebService proxies. Thanks.
Patrick
A: 

Without knowing the capabilities of your scripting language this is difficult to answer.

One idea could be to create a more powerful service in another language that has a factory pattern to call the relevant customers service and return to your script information in a format that is non-customer specific.

Of course this is assuming you could call another program in the first place from your domain specific scripting language.

Maybe something like Managed Extensibility Framework (or MEF for short) could work for you too Managed Extensibility Framework (or MEF for short)

Mechamonkey
+1  A: 

You could try to generate your dynamic code on the fly using the build in compiler.
Either from code directly (using a code provider like http://msdn.microsoft.com/en-us/library/microsoft.csharp.csharpcodeprovider.aspx) or by building the class directly (see http://msdn.microsoft.com/en-us/library/system.codedom.compiler.codedomprovider.aspx for an example)

Generating the code can be done in several ways:

eli
Good idea. How do you suggest to write the proxy class? Generate this myself from C++, generate it at the customer using the WSDL utility, or can it be generated using a .Net class based on the WSDL URL from within my application?
Patrick
See my updated answer
eli