SteGriff

Blog

Next & Previous

WCF from scratch in 2019

I work with a (big!) layer of WCF services and I want to know more about them and how we can adapt for the future. So I’m doing a deep dive! If I switch tenses in this post, apologies. It’s part stream-of-consciousness and part review.

The big questions I hope to answer:

You can jump down to the pictures

Getting started

Solution explorer

Steps:

Service Project Web.config

To set up my config, I more or less followed this dotnetcurry guide to expose WCF services as SOAP and REST. Please note that REST is a misued term here; REST is not just JSON. Every dev has a different level of passion for the purity of the definition of REST. My preferred starting point is the Richardson Maturity Model, so I’ll be calling what we do here a ‘JSON API’.

Sample of config file:

<system.serviceModel>
  <services>
    <service name="Taco.Services.Location.LocationService" behaviorConfiguration="generic">
  	<!-- SOAP - No behaviorConfiguration, uses basicHttpBinding -->
  	<endpoint address="soap" binding="basicHttpBinding" contract="Taco.Services.Location.ILocationService" />
  	<!-- JSON - Needs a behaviorConfiguration with webHttp, uses webHttpBinding -->
  	<endpoint address="api" binding="webHttpBinding" contract="Taco.Services.Location.ILocationService" behaviorConfiguration="web" />
    </service>
  </services>
  <behaviors>
    <endpointBehaviors>
  	<behavior name="web">
  	  <webHttp helpEnabled="true" />
  	</behavior>
    </endpointBehaviors>
    <serviceBehaviors>
  	<behavior name="generic">
  	  <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
  	  <serviceDebug includeExceptionDetailInFaults="false" />
  	</behavior>
  	<behavior name="">
  	  <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
  	  <serviceDebug includeExceptionDetailInFaults="false" />
  	</behavior>
    </serviceBehaviors>
  </behaviors>
  <protocolMapping>
    <add binding="basicHttpsBinding" scheme="https" />
  </protocolMapping>
  <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>

Things to grok:

If you run a WCF project while the .svc.cs file is open in VS, it opens the WCF Test Client which will validate your service.

Running the test client, I followed the trail of errors:

WCF Test Client said “Added Service Successfully” but didn’t actually add my thing, this was because my services config was wrong (earlier version to the one above) and I’d configured all my endpoints to use the webHttp behaviour (this takes away their SOAP/WSDL powers).

I made a new Console App to consume the services, added the Service Reference (using ‘Discover Services in Solution’), and implemented the basic features of my LocationService.

SOAP works - making a second service

Everything in my LocationService now works, so I want to make a second service.

A bit tricky to find the new ‘WCF Service’ option when you go to ‘Add New Item’ on a project. It’s in the Web group, near the bottom, but it doesn’t appear in any of the subcategories of Web (General, Markup, Scripts, etc.)

I created an OrderService and stubbed out some behaviours.

One project - multiple App Service targets?

Nope. This isn’t the way to go. It makes a lot more sense to divide the services one-per-project and then have a PublishProfile for each project in the usual way.

I split the OrderService into a separate project which wasn’t too hard with some cunning renaming.

The test app then couldn’t reach the Orders API any more. Following the errors again:

Then it was fixed, and the Orders SOAP service started working again.

Test JSON

LocationService

Firstly, I discovered that the JSON endpoint has a default help page at: http://localhost:61142/LocationService.svc/api/help

WCF help page for HTTP services

…bear in mind that I used ‘/api’ as the address prefix of the JSON Service Endpoint in my web.config

Through the help page, I discovered that my JSON endpoints only accept POST right now. So I added the attribute [WebGet(ResponseFormat = WebMessageFormat.Json)] into the ILocationService contract.

Now they accepted GET, the two endpoints in LocationService just worked! I used JsonConvert.DeserializeObject to get the result and it was exactly what I was expecting - surprising!

OrderService

The POST on OrderService did not work right away even though I was sending an object which matched the defintion on the help page at “http://localhost:18070/OrderService.svc/api/help/operations/PlaceOrder”

The fix was twofold:

Add a [WebInvoke...] attribute as described in this WCF post by Dean Poulin

[OperationContract]
[WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
Result PlaceOrder(OrderRequest order);

Set the Content-Type header in the request (i.e. in the code of the consuming client) to ‘application/json’

client.Headers.Add(HttpRequestHeader.ContentType, "application/json");

Now everything works as well in JSON as it does via SOAP! Pretty cool!

Adding SwaggerWcf

I added the NuGet package with Install-Package SwaggerWcf and had to do that on both services projects and on the common project.

My service projects didn’t have global.asax files but the SwaggerWcf readme setup guide said I needed one. You can add a global.asax to your project in the Add New Item dialog by searching for ‘global’ and selecting ‘Global Application Class’. Or you can find it under Web/General.

It was tricky to configure SwaggerWcf. The docs are not great. It will work and come up at your configured URL as soon as you’ve sorted out the routing (below) but won’t have any valuable information until you’ve annotated everything with the attributes described in the readme.

Configure with RouteTable

Even though my project is just straight WCF (not really “ASP.NET”) eventually it was the ASP.NET way of configuring that won out. If you go with the self host, you need an absolute URL in the web.config which doesn’t work for me because I’m using IIS Express with its whimsically-numbered ports.

//Global.asax.cs
protected void Application_Start(object sender, EventArgs e)
{
	RouteTable.Routes.Add(new ServiceRoute("docs", new WebServiceHostFactory(), typeof(SwaggerWcfEndpoint)));
}

Doing it this way, you do not need a <service> entry for Swagger in web.config. I found this pull request on a sample project helpful in fixing my config issues.

You might have to manually add a framework reference to System.ServiceModel.Activation for the code above to work. Sadly, Intellisense doesn’t suggest the fix automatically (VS 2017).

Practical notes

If you run your project(s) while looking at the svc file and the WCF Test Client opens up, you have to wait for it to load before you can successfully get to the Swagger page.

In OrderService.svc.cs, my attribute ended up looking like this:

[SwaggerWcf("/OrderService.svc/api")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class OrderService : IOrderService

…in order for the Swagger sample requests to work.

The sample project has you put loads of SwaggerWcfResponse annotations on, and maybe your API is not that clever. You don’t need all of those. On the other hand, I was able to enhance my API by adding a tiny bit of extra code right before I return the response from OrderService.PlaceOrder:

WebOperationContext.Current.OutgoingResponse.StatusCode = result.Success
	? HttpStatusCode.Created
	: HttpStatusCode.BadRequest;

I then went ahead and duplicated these changes on the LocationService. I thought I’d nailed it, went to test the ‘/docs’ page, and got:

Request Error
The server encountered an error processing the request. See server logs for more details.

It was because I forogt the swaggerwcf section in the web.config for the LocationService project:

<configSections>
	<section name="swaggerwcf" type="SwaggerWcf.Configuration.SwaggerWcfSection, SwaggerWcf" />
</configSections>
...
<swaggerwcf>
	<settings>
	  <setting name="InfoTitle" value="LocationService" />
	  <setting name="InfoDescription" value="Interface for finding the locations which serve Tacos" />
	  <setting name="InfoVersion" value="0.0.1" />
	  <setting name="InfoContactUrl" value="http://github.com/stegriff" />
	  <setting name="InfoContactEmail" value="github@stegriff.co.uk" />
	</settings>
</swaggerwcf>

Ok, weirdly, the swagger defs “bleed” into each other. The first service to startup knows only about itself, but the second picks up the details of both APIs. I’m still not sure why. I thought it was because one of my service projects held a reference to the other, and the common project had definitions affecting one project and not the other but was being pulled into both. But even after I changed those facts, still the LocationService has a Swagger def for both APIs, and the OrderService has only itself. Mysterious.

Outcomes - what did we get?

Firstly, you can find all the source code for TacoServices on GitHub.

This is what I get when I run the solution (the consumer project is in JSON mode). Directory listing is switched on, and you can click into a SVC file to see the service discovery screen.

Two localhost browser windows and a command window showing the result from API calls

The Swagger defs work offline, but to prove a point I set up an Azure App Service for each service, downloaded the PublishProfiles and published each project to the cloud, at taco-order and taco-location; here is one of the online Swagger defs:

A Swagger API help page

(If it’s still online - unlikely - you can reach it at http://taco-order.azurewebsites.net/docs/)

Conclusion

WCF is a still a workable technology, and parallel SOAP with JSON is possible and practical. The URLs are ugly but this can be fixed with IIS Rewrites or with Azure API Management, which I’ll explore in the next post. Swagger is easy to integrate with the SwaggerWcf library, but there are some snags.

Overall, for me, it was a cool experience and a helpful deep-dive of the technology.

Sources

I’ll repeat my proviso that “REST is not JSON” and when these authors say REST they generally mean a plain-ol’ JSON service.

How to enable REST and SOAP both on the same WCF Service
https://debugmode.net/2011/12/22/how-to-enable-rest-and-soap-both-on-the-same-wcf-service

SwaggerWcf (NuGet Package)
https://www.nuget.org/packages/SwaggerWcf

5 simple steps to create your first RESTful service (WCF)
http://www.topwcftutorials.net/2013/09/simple-steps-for-restful-service.html

Expose WCF 4.0 Service as SOAP and REST
https://www.dotnetcurry.com/wcf/728/expose-wcf-service-soap-rest