A model SOA application in .NET Part 7: testing the client proxy

Introduction

In the previous post we finished building a thin but functional SOA web service. Now it’s time to test it. We could build just any type of consumer that’s capable of issuing HTTP requests to a service but we’ll stick to a simple Console app. The goal is to test the SOA service and not to win the next CSS contest.

The tester

Open the SOA application we’ve been working on. Set the WebProxy layer as the start up project and then press F5. Without setting the start up URL in Properties/Web you’ll most likely get a funny looking error page from IIS that says you are not authorised to view the contents of the directory. That’s fine. Extend the URI in the browser as follows:

http://localhost:xxxx/reservation

Refresh the browser and you should get a JSON message saying that there is no GET method for that resource. That’s also fine as we only implemented a POST method. The same is true for the purchase controller:

http://localhost:xxxx/purchase

Open another Visual Studio instance and create a new Console application called SoaTester. Import the Json.NET package through NuGet. Add assembly references to System.Net and System.Net.Http. Add the following private variables to Program.cs:

private static Uri _productReservationServiceUri = new Uri("http://localhost:49679/reservation");
private static Uri _productPurchaseServiceUri = new Uri("http://localhost:49679/purchase");

Recall that the Post methods require Request objects to function correctly. The easiest way to ensure that we don’t need to deal with JSON formatting and serialisation issues we can just copy the Request objects we already have the SoaIntroNet.Service layer. Insert the following two objects into the console app:

public class ReserveProductRequest
{
	public string ProductId { get; set; }
	public int ProductQuantity { get; set; }
}
public class PurchaseProductRequest
{
	public string ReservationId { get; set; }
	public string ProductId { get; set; }
}

Note that we dropped the correlation ID property from the purchase request. It’s irrelevant for the actual user and is set within the Purchase.Post() action anyway before the purchase request is passed to the Service layer.

We’ll need to read the JSON responses as well so insert the following three objects in the console app. They will all look familiar:

public abstract class ServiceResponseBase
{
	public ServiceResponseBase()
	{
		this.Exception = null;
	}

	public Exception Exception { get; set; }
}
public class PurchaseProductResponse : ServiceResponseBase
{
	public string PurchaseId { get; set; }
	public string ProductName { get; set; }
	public string ProductId { get; set; }
	public int ProductQuantity { get; set; }
}
public class ProductReservationResponse : ServiceResponseBase
{
	public string ReservationId { get; set; }
	public DateTime Expiration { get; set; }
	public string ProductId { get; set; }
	public string ProductName { get; set; }
	public int ProductQuantity { get; set; }
}

We’ll first call the Reservation operation. The below method calls the product reservation URI and returns a product reservation response:

private static ProductReservationResponse ReserveProduct(ReserveProductRequest request)
{
	ProductReservationResponse response = new ProductReservationResponse();
	try
	{
		HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, _productReservationServiceUri);
		requestMessage.Headers.ExpectContinue = false;
		String jsonArguments = JsonConvert.SerializeObject(request);
		requestMessage.Content = new StringContent(jsonArguments, Encoding.UTF8, "application/json");
		HttpClient httpClient = new HttpClient();
		httpClient.Timeout = new TimeSpan(0, 10, 0);
		Task<HttpResponseMessage> httpRequest = httpClient.SendAsync(requestMessage,
			HttpCompletionOption.ResponseContentRead, CancellationToken.None);
		HttpResponseMessage httpResponse = httpRequest.Result;
		HttpStatusCode statusCode = httpResponse.StatusCode;
        	HttpContent responseContent = httpResponse.Content;
		Task<String> stringContentsTask = responseContent.ReadAsStringAsync();
		String stringContents = stringContentsTask.Result;
		if (statusCode == HttpStatusCode.OK && responseContent != null)
		{					
			response = JsonConvert.DeserializeObject<ProductReservationResponse>(stringContents);
		}
		else
		{
			response.Exception = new Exception(stringContents);
		}
	}
	catch (Exception ex)
	{
		response.Exception = ex;
	}
	return response;
}

We send a HTTP request to the service and read off the response. We use Json.NET to serialise and deserialise the objects. We set the HttpClient timeout to 10 minutes to make sure we don’t get any timeout exceptions as we are testing the SOA application.

The below bit of code calls this method from Main:

ReserveProductRequest reservationRequest = new ReserveProductRequest();
reservationRequest.ProductId = "13a35876-ccf1-468a-88b1-0acc04422243";
reservationRequest.ProductQuantity = 10;
ProductReservationResponse reservationResponse = ReserveProduct(reservationRequest);

Console.WriteLine("Reservation response received.");
Console.WriteLine(string.Concat("Reservation success: ", (reservationResponse.Exception == null)));
if (reservationResponse.Exception == null)
{
	Console.WriteLine("Reservation id: " + reservationResponse.ReservationId);
}
else
{
	Console.WriteLine(reservationResponse.Exception.Message);
}

Console.ReadKey();

Recall that we inserted a product with that ID in the in-memory database in the post discussing the repository layer.

Set two breakpoints in the SOA app: one within the ReservationController constructor and another within the ReservationController.Post method. Start the console application. You should see that the code execution stops within the controller constructor. Check the status of the incoming productService parameter. It is not null so StructureMap correctly resolved this dependency. The next stop is at the second breakpoint. Check the status of the reserveProductRequest parameter. It is not null and has been correctly populated by Web API and the Json serialiser. From this point on I encourage you to step through the execution by pressing F11 to see how each method is called. At the end the product should be reserved and a product reservation message will be sent back to the client. Back in the client the ProductReservationResponse object is populated by Json.NET based on the string contents from the web service. The results are then printed on the console window.

You can test the service response in the following way:

  • Send a non-existing product id
  • Send a malformatted GUID as the product id
  • Send a quantity higher than the original allocation, e.g. 10000

You’ll get the correct error messages in all three cases.

It’s now time to purchase the product. The below method will send the purchase request to the web service:

private static PurchaseProductResponse PurchaseProduct(PurchaseProductRequest request)
{
	PurchaseProductResponse response = new PurchaseProductResponse();
	try
	{
		HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, _productPurchaseServiceUri);
		requestMessage.Headers.ExpectContinue = false;
		String jsonArguments = JsonConvert.SerializeObject(request);
		requestMessage.Content = new StringContent(jsonArguments, Encoding.UTF8, "application/json");
		HttpClient httpClient = new HttpClient();
		httpClient.Timeout = new TimeSpan(0, 10, 0);
		Task<HttpResponseMessage> httpRequest = httpClient.SendAsync(requestMessage,
			HttpCompletionOption.ResponseContentRead, CancellationToken.None);
		HttpResponseMessage httpResponse = httpRequest.Result;
		HttpStatusCode statusCode = httpResponse.StatusCode;
		HttpContent responseContent = httpResponse.Content;
		Task<String> stringContentsTask = responseContent.ReadAsStringAsync();
		String stringContents = stringContentsTask.Result;
		if (statusCode == HttpStatusCode.OK && responseContent != null)
		{
			response = JsonConvert.DeserializeObject<PurchaseProductResponse>(stringContents);
		}
		else
		{
			response.Exception = new Exception(stringContents);
		}
	}
	catch (Exception ex)
	{
		response.Exception = ex;
	}
	return response;
}

I realise that this is almost identical to PurchaseProduct and a single method probably would have sufficed – you can do this improvement as homework.

Below the code line…

Console.WriteLine("Reservation id: " + reservationResponse.ReservationId);

…add the following bit to complete the loop:

PurchaseProductRequest purchaseRequest = new PurchaseProductRequest();
purchaseRequest.ProductId = reservationResponse.ProductId;
purchaseRequest.ReservationId = reservationResponse.ReservationId;
PurchaseProductResponse purchaseResponse = PurchaseProduct(purchaseRequest);
if (purchaseResponse.Exception == null)
{
	Console.WriteLine("Purchase confirmation id: " + purchaseResponse.PurchaseId);
}
else
{
	Console.WriteLine(purchaseResponse.Exception.Message);
}

So we purchase the reserved product immediately after receiving the reservation response. Run the tester object without any break points. You should see the purchase ID in the console window.

You can test the SOA app in the following way:

  • Set a breakpoint in the Purchase controller and step through the code slowly with F11 – make sure that the purchase expiry time of 1 minute is reached
  • Alternatively use Thread.Sleep() in the tester app before the call to the Purchase controller
  • Modify the reservation id to some non-existent GUID
  • Set the reservation id to a malformed GUID

You will get the correct exception messages in all cases from the SOA app.

That’s all folks about SOA basics in this series. I hope you have learned a lot of new concepts.

View the list of posts on Architecture and Patterns here.

Advertisement

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

10 Responses to A model SOA application in .NET Part 7: testing the client proxy

  1. Matthew says:

    Is there a pre-built solution for this exercise available to download? I would like to see it in action first. Also there are a lot of paste problems with the code here related to quotes and brackets.

  2. Hugo says:

    Excellent post(s) Andras. I’ve been seeking in the interconnected pieces something where I can get the picture of a web service and a full client (e.g. WPF) consuming that web service, i.e. one scenario where I own the two. Based on this last example, would it be safe to assume that one can apply DDD fully both on the client and service as well? – If so, does that mean I’d end up with a duplicate set of objects, services, etc. (with the exception of the repositories on the client side) in the (unlikely) event that my client could switch to a different service?
    Thanks!-

    • Andras Nemes says:

      Hi Hugo,
      If you own both the WCF consumer and the web service backend then why not call the backend from WCF directly? A WCF consumer is just the “view” layer which can consume the services of the backend – the service, domain + repository layers.
      I’m not sure how much you’ve read on this blog but the DDD series shows you an example how to layer your project so that the backend parts can potentially be called from multiple fronts.
      //Andras

      • Hugo says:

        I’ve skimmed through the series and I am quite impressed the beginning to end implementation. Thank you. It’s becoming clearer now. I should say that the following statement on part 9: ” this layer can be any type of presentation layer: MVC, a web service interface, WPF, a console app, you name it” throws me off a bit in the sense that in the series your ultimate consumer is a web API (web service) – and in my case, I have a WPF (btw, I assume you meant to say WPF above vs WCF-) as the ultimate consumer BUT that consumes a web API which it its own context it is also a (ultimate) consumer (so to speak) so in part 10 you depict your dependencies like:

        UI (Web API) —–> Application Services —-> Domain <–
        | | |
        V | |
        Infrastructure “UI” (Web API) ———-> etc?

        where I just consume the web api directly (per your advice) ? or

        ———————————————————————–
        | |
        | UI (WPF MVVM) —> Application Service (Client Side) |
        | |
        ————————————————————————
        |
        V
        Server-side (consumer/”UI”, Web API)
        |
        V
        Application Services (Server Side – a) > etc?

        where I use some sort of facade (like an Application Service) where at least it can serve as a wrapper to my web api calls? –

        Thanks again.

      • Andras Nemes says:

        Hi Hugo,

        No, it was no typo, I really meant WPF but could have written WCF as well, it doesn’t matter.

        What you see in part 10 is a test application to call the web service so that we can test the DDD application. Within the actual DDD application the Web API layer is the consumer. The reason I went for a web service as the top layer is that I didn’t want to waste time on view-specific details, like HTML and CSS. However, there’s nothing stopping you from replacing the Web API layer with an MVC one, you just hook into the endpoints provided by the Service layer that same way as the Web API layer does. The way the Web API layer consumes the services of the Service layer can be transferred to a WPF consumer as well. The difference will be in how WPF handles the responses from the Service layer to arrange the elements on the screen. Web API has no screen elements so it simply outputs the results as JSON to the caller.

        //Andras

  3. Hugo says:

    Apologies for the ‘graphics’, it seems they didn’t turn out well but in the first one I mean to put the WPF piece only on the front of all the rest.

  4. Pingback: Architecture and patterns | Michael's Excerpts

  5. Pingback: Architecture and patterns | Michael's Excerpts

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Elliot Balynn's Blog

A directory of wonderful thoughts

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

WEB APPLICATION DEVELOPMENT TUTORIALS WITH OPEN-SOURCE PROJECTS

Once Upon a Camayoc

Bite-size insight on Cyber Security for the not too technical.

%d bloggers like this: