Skip to content

Tutorial

João Simões edited this page Aug 3, 2016 · 11 revisions

Content

Installing Simple SOAP Client

Simple SOAP Client can be installed via NuGet packages. Just run the following command in your project or search for it in Visual Studio NuGet package explorer:

Install-Package SimpleSOAPClient -Pre

The currently recommended version to install is 2.0.0-rc03 or greater, even in its release candidate state. Older and stable versions should work as expected (1.2.2) but a major improvement was made in releases 2.x, specially in the handlers pipeline logic.

SOAP Envelopes

This library provides a way to send SOAP Envelopes using the HTTP protocol. These objects are serialized to their XML representations using attributes and classes present in the System.Xml.Serialization and System.Xml.Linq namespaces. This should be extensible enough to create any XML representation of a given SOAP body or header to the developer needs.

To create SOAP Envelopes you can either create an instance new SoapEnvelope() or use the helper method SoapEnvelope.Prepare().

Creating SOAP Envelope Body

To create a type that can be included or read from the SOAP Envelope body, the only requirement is to have the XmlRoot attribute. Here are some examples:

[XmlRoot("CreateUserRequest", Namespace = "http://services.company.com")]
public class CreateUserRequest
{
	[XmlElement("Username")]
	public string Username { get; set; }

	[XmlElement("Password")]
	public string Password { get; set; }
}

[XmlRoot("CreateUserResponse", Namespace = "http://services.company.com")]
public class CreateUserResponse
{
	[XmlElement("Suceeded")]
	public bool Suceeded { get; set; }
}

To assign or read the object to and from the SOAP Envelope body, the easiest way is to use the helper methods EnvelopeHelpers.Body overloads. Example:

var envelope = SoapEnvelope.Prepare();
envelope.Body(new CreateUserRequest());
var request = envelope.Body<CreateUserRequest>();

Creating SOAP Envelope Headers

To create a type that can be included or read as a SOAP Envelope header, it must have XmlRoot attribute and extend from the SoapHeader class (extending is not a requirement, but helps keeping with the standard). Here are some examples:

[XmlRoot("Tracking", Namespace = "http://services.company.com")]
public class TrackingHeader : SimpleSOAPClient.Models.SoapHeader
{
	[XmlElement("Id")]
	public Guid Id { get; set; }

	[XmlElement("CreatedOn")]
	public DateTime CreatedOn { get; set; }

	public TrackingHeader()
	{
		MustUnderstand = 0; // this is the default value
	}
}

To assign or read the object to and from the SOAP Envelope header, the easiest way is to use the helper methods EnvelopeHelpers.WithHeaders overloads. Example:

var envelope = SoapEnvelope.Prepare();
envelope.WithHeaders(new TrackingHeader());
var header = envelope.Header<TrackingHeader>("{http://services.company.com}Tracking");

SOAP Client

To send SOAP Envelopes to a given endpoint, the SoapClient class can be used. You can create instances of this class in the following ways:

using(var client = new SoapClient()){

}

using(var client = SoapClient.Prepare()){

}

Note: this is an IDisposable instance, so don't forget to release the allocated resources or memory leaks may occur.

Sending SOAP Envelopes

To send a server request to the SOAP endpoint, one of the ISoapClient.Send overloads can be used. Here is an example using the previous example objects:

using(var client = SoapClient.Prepare()){
	var responseEnvelope = 
		await client.SendAsync(
			"https://services.company.com/Service.svc",
			"http://services.company.com/IService/CreateUser",
			SoapEnvelope.Prepare()
				.WithHeaders(new TrackingHeader())
				.Body(new CreateUserRequest()), ct);
	
	var response = responseEnvelope.Body<CreateUserResponse>();
}

Note: Don't forget, if needed, to handle SimpleSOAPClient exceptions, like FaultException or SoapEnvelopeDeserializationException.

Handlers

Handlers add behavior to a SOAP Client instance and are invoked as a pipeline based on their ISoapHandler.Order ascending value. They can, as an example, add security SOAP headers or log all requests and responses made by a given client. Here is a sample code:

using(var client = 
	SoapClient.Prepare()
		.WithHandler(new DelegatingSoapHandler
		{
			Order = int.MaxValue, // Will be the last handler before the request and the first after the response
			OnSoapEnvelopeRequest = args =>
			{
				args.Envelope.WithHeaders(new TrackingHeader
				{
					Id = args.TrackingId, CreatedOn = DateTime.UtcNow
				}, KnownHeader.Oasis.Security.UsernameTokenAndPasswordText("some-user", "some-password"));
			}
			OnHttpRequestAsyncAction = async (c, args, cnt) =>
			{
				Logger.Trace(
					"SOAP Outbound Request [{0}] -> {1} {2}({3})\n{4}",
					args.TrackingId, args.Request.Method, args.Url, args.Action,
					await args.Request.Content.ReadAsStringAsync());
			},
			OnHttpResponseAsyncAction = async (c, args, cnt) =>
			{
				Logger.Trace(
					"SOAP Outbound Response [{0}] -> {1}({2}) {3} {4}\n{5}",
					args.TrackingId, args.Url, args.Action, (int) args.Response.StatusCode,
					args.Response.StatusCode, await args.Response.Content.ReadAsStringAsync());
			}
		})){
	var responseEnvelope = 
		await client.SendAsync(
			"https://services.company.com/Service.svc",
			"http://services.company.com/IService/CreateUser",
			SoapEnvelope.Prepare().Body(new CreateUserRequest()), ct);
	
	var response = responseEnvelope.Body<CreateUserResponse>();
}

Advanced Example

This is a working example of the tutorial code:

public static class Program
{
	//	NLog in this case
	private static readonly ILogger Logger = LogManager.GetCurrentClassLogger();

	public static void Main(string[] args)
	{
		try
		{
			Logger.Info("Application started...");

			MainAsync(args, CancelationToken.None).Wait(CancelationToken.None);
		}
		catch (Exception e)
		{
			Logger.Fatal(e, "An unexpected exception has occured");
		}
		finally
		{
			Logger.Info("Application terminated. Press <enter> to exit...");
			Console.ReadLine();
		}
	}
	
	public static async Task MainAsync(string[] args, CancelationToken ct)
	{
		using(var client = 
			SoapClient.Prepare()
				.WithHandler(new DelegatingSoapHandler
				{
					Order = int.MaxValue, // Will be the last handler before the request and the first after the response
					OnSoapEnvelopeRequest = args =>
					{
						args.Envelope.WithHeaders(new TrackingHeader
						{
							Id = args.TrackingId, CreatedOn = DateTime.UtcNow
						}, KnownHeader.Oasis.Security.UsernameTokenAndPasswordText("some-user", "some-password"));
					}
					OnHttpRequestAsyncAction = async (c, args, cnt) =>
					{
						Logger.Trace(
							"SOAP Outbound Request [{0}] -> {1} {2}({3})\n{4}",
							args.TrackingId, args.Request.Method, args.Url, args.Action,
							await args.Request.Content.ReadAsStringAsync());
					},
					OnHttpResponseAsyncAction = async (c, args, cnt) =>
					{
						Logger.Trace(
							"SOAP Outbound Response [{0}] -> {1}({2}) {3} {4}\n{5}",
							args.TrackingId, args.Url, args.Action, (int) args.Response.StatusCode,
							args.Response.StatusCode, await args.Response.Content.ReadAsStringAsync());
					}
				})){
			
			var requestEnvelope = SoapEnvelope.Prepare().Body(new CreateUserRequest
			{
				Username = "[email protected]",
				Password = "amazing, my password is!!!"
			});
			
			SoapEnvelope responseEnvelope;
			
			try
			{
				responseEnvelope = 
					await client.SendAsync(
						"https://services.company.com/Service.svc",
						"http://services.company.com/IService/CreateUser",
						requestEnvelope, ct);
			}
			catch (SoapEnvelopeSerializationException e)
			{
				Logger.Error(e, $"Failed to serialize the SOAP Envelope [Envelope={e.Envelope}]");
				throw;
			}
			catch (SoapEnvelopeDeserializationException e)
			{
				Logger.Error(e, $"Failed to deserialize the response into a SOAP Envelope [XmlValue={e.XmlValue}]");
				throw;
			}

			try
			{
				var response = responseEnvelope.Body<CreateUserResponse>();
				Logger.Debug("Response-> Suceeded: {0}", response.Suceeded);
			}
			catch (FaultException e)
			{
				Logger.Error(e, $"The server returned a fault [Code={e.Code}, String={e.String}, Actor={e.Actor}]");
				throw;
			}
		}
	}
}

[XmlRoot("CreateUserRequest", Namespace = "http://services.company.com")]
public class CreateUserRequest
{
    [XmlElement("Username")]
    public string Username { get; set; }

    [XmlElement("Password")]
    public string Password { get; set; }
}

[XmlRoot("CreateUserResponse", Namespace = "http://services.company.com")]
public class CreateUserResponse
{
    [XmlElement("Suceeded")]
    public bool Suceeded { get; set; }
}

[XmlRoot("Tracking", Namespace = "http://services.company.com")]
public class TrackingHeader : SimpleSOAPClient.Models.SoapHeader
{
    [XmlElement("Id")]
    public Guid Id { get; set; }

    [XmlElement("CreatedOn")]
    public DateTime CreatedOn { get; set; }

    public TrackingHeader()
    {
        MustUnderstand = 0; // this is the default value
    }
}