ACKNOWLEDGEMENT: This has been originally posted on Mexia blog
When we work for a service integration project for a customer's information systems, not all systems use cutting-edge technologies. Rather, still many information systems use legacy ways to get integration works done. For example, some legacy applications still drop files to a designated folder so that other applications pick up those files periodically. On the other hand, other legacy applications support SOAP (Simple Object Access Protocol) based webservices. In .NET world, we can easily handle those SOAP webservices through WCF by creating service references. Now, everything has changed. We use Azure API Management, Logic Apps and Functions for service integration more than ever.
SOAP over API Management and Logic Apps
API Management supports SOAP out-of-the-box using WSDL. However, we have to know that API Management has some restrictions of using WSDL. In other words, some SOAP-based webservices having complex structure might not be suitable for using API Management.
What about Logic Apps? If we create a custom connector and import WSDL through it, we can use SOAP webservices directly from the Logic App instances. However, there are still concerns around Logic Apps using SOAP-based webservices. As it uses API Management behind the scene, it has same restrictions that API Management has. Moreover, Logic Apps' customer connector itself has limitations. Therefore, this also needs to be considered, when we design our integration architecture.
Then we have the last one standing – Azure Functions. It's basically C# code, so we can easily refer to service references, which looks perfect. Let's have a look.
The sample code used for this post can be found here.
Analysing Service References
When we create a service reference proxy, it contains service client class inheriting ClientBase. It also has several constructors taking no parameter, string parameters and instance parameters.
public partial class Service1Client : ClientBase<IService1>, IService1 | |
{ | |
public Service1Client() | |
{ | |
} | |
// This constructor is of no use for Azure Functions | |
// because it doesn't use Web.config or App.config | |
public Service1Client(string endpointConfigurationName) | |
: base(endpointConfigurationName) | |
{ | |
} | |
// This constructor is of no use for Azure Functions | |
// because it doesn't use Web.config or App.config | |
public Service1Client(string endpointConfigurationName, string remoteAddress) | |
: base(endpointConfigurationName, remoteAddress) | |
{ | |
} | |
// This constructor is of no use for Azure Functions | |
// because it doesn't use Web.config or App.config | |
public Service1Client(string endpointConfigurationName, EndpointAddress remoteAddress) | |
: base(endpointConfigurationName, remoteAddress) | |
{ | |
} | |
// Only this constructor is used for Azure Functions | |
public Service1Client(Binding binding, EndpointAddress remoteAddress) | |
: base(binding, remoteAddress) | |
{ | |
} | |
... | |
} |
Except the first and last constructors, all other constructors take string parameters for binding and endpoint information, which are stored at Web.config
or App.config
. In fact, the configuration file looks like:
<configuration> | |
... | |
<system.serviceModel> | |
<bindings> | |
<basicHttpBinding> | |
<binding name="BasicHttpBinding_IService1"/> | |
</basicHttpBinding> | |
</bindings> | |
<client> | |
<endpoint name="BasicHttpBinding_IService1" | |
address="http://localhost:10679/Service1.svc" | |
binding="basicHttpBinding" | |
bindingConfiguration="BasicHttpBinding_IService1" | |
contract="ServiceReference1.IService1" | |
/> | |
</client> | |
</system.serviceModel> | |
... | |
</configuration> |
Both basicHttpBinding
and endpoint
nodes are used for setting up the WCF service client. That's actually not possible for Azure Functions, because it doesn't have Web.config
file! Wow, that's a kind of critical to use WCF in our Azure Function code, yeah?
Fortunately, the last constructor of the service client accepts both Binding
instance and EndpointAddress
instance for its parameters. In other words, as long as we can create both instances and pass them to the service client as dependencies, we can still use WCF service references without the Web.config
file.
SOAP over Azure Functions
Let's have a look a the function code. We should note that there are binding
and endpoint
instances to instantiate WCF service client. This doesn't require the system.serviceModel
node at Web.config
, but only needs the actual endpoint URL that is defined in the application settings blade of the Azure Functions instance (equivalent to local.settings.json
at our local development environment).
public static class WcfCallingHttpTrigger | |
{ | |
[FunctionName("WcfCallingHttpTrigger")] | |
public static async Task<HttpResponseMessage> Run( | |
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "data/{value}")] HttpRequestMessage req, | |
int value, | |
TraceWriter log) | |
{ | |
log.Info("C# HTTP trigger function processed a request."); | |
// Instantiates the binding object. | |
// Depending on the security definition, the security mode should be adjusted. | |
var binding = new BasicHttpBinding(BasicHttpSecurityMode.None); | |
// If necessary, appropriate client credential type should be set. | |
//binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic; | |
// Instantiates the endpoint address. | |
var endpoint = new EndpointAddress(Config.WcfServiceEndpoint); | |
using (var client = new Service1Client(binding, endpoint)) | |
{ | |
// If necessary, username and password should be provided. | |
//client.ClientCredentials.UserName.UserName = "username"; | |
//client.ClientCredentials.UserName.Password = "password"; | |
var result = await client.GetDataAsync(value).ConfigureAwait(false); | |
log.Info($"{result} received."); | |
return req.CreateResponse(HttpStatusCode.OK, result); | |
} | |
} | |
} |
Based on the connection type, both binding instance and an appropriate BasicHttpSecurityMode
value should be carefully chosen. In addition to this, if necessary, user credentials like username and password should be provided from app settings.
Once we implement this and run it, we can get an expected result like:
Service Client as Singleton Dependency
Now, we can send requests to SOAP webservices through Azure Functions. There's one more thing to keep in mind, from the implementation design perspective. As we can see, the WCF service client is just a proxy and majority of SOAP applications are monolithic. In other words, the service client is reusable over time so that it's always a good idea to register it within an IoC container, as a singleton instance. I have written many posts for dependency management in Azure Functions and this is the most recent one that is worth checking. Therefore, with this approach, the service client instance can be registered as a singleton and injected to functions, if necessary.
Which One to Choose?
So far, we have discussed which Azure service is good to handle SOAP webservices. Here's a simple chart for comparison:
Logic Apps | API Management | Functions | |
---|---|---|---|
Connector Limit | x | o | o |
Complex Message Structure | x | x | o |
- Azure Logic Apps has a connector limitation – number of connectors and number of requests per connector. So frequent access to SOAP webservice through Logic App wouldn't be ideal.
- Azure API Management has restrictions on complex SOAP message structure. As Azure Logic Apps relies on API Management, it also has the same restrictions.
- Azure Functions doesn't have a concept of connector and can directly use WCF proxy libraries, so it has virtually no limitation but requires heavy-lifted coding efforts.
From these perspective, we can choose an appropriate one for our integration application structure. So, have you decided what to choose?
After posting this, Darrel Miller from Microsoft left comments worth noting:
Dealing with SOAP-based webservices using @AzureApiMgmt @logicappsio and @AzureFunctions , and their pros and cons. Check this out on our @TeamMexia blog!https://t.co/oQjwyrZ1U4
— ~/☁️/🥑/justinyoo/.dotnet/azure (@justinchronicle) December 10, 2017
Interesting comparison. Worth noting that this is a comparison of the "SOAP2REST" style of SOAP services for API Management. SOAP Passthrough in APIM supports more complex messages than the S2R style.
— Darrel Miller (Not here, over on the other place) (@darrel_miller) December 10, 2017
Thanks for your comment! According to this doc, https://t.co/YpdmssXYqF, are these restrictions only applicable for S2R style?
— ~/☁️/🥑/justinyoo/.dotnet/azure (@justinchronicle) December 11, 2017
There is a mix. Some issues are specific to importing WSDL, others are related to generating the liquid template for S2R. Some issues have been fixed already in Logic Apps and soon will be re-incorporated back into API Management. It's complicated :-)
— Darrel Miller (Not here, over on the other place) (@darrel_miller) December 11, 2017
Once we have Logic Apps and APIM functionality back in sync, I'll get the restrictions updated and clarify which are for S2R and which are for passthrough. Hopefully in January.
— Darrel Miller (Not here, over on the other place) (@darrel_miller) December 11, 2017
Therefore, hopefully in January 2018, we might be able to expect something very exciting stuff around API Management and Logic Apps!