Introduction
This is an extension of my previous article "WCF for the Real World, Not Hello World". So I would presume you have read it before reading this or got used to similar project structures: typically, for each service, you would establish 3 assemblies for contracts, implementation and client API.
Background
"WCF for the Real World, Not Hello World" had demonstrated how to efficiently and effectively manage WCF service development in enterprise environment through team works in order to rapidly develop and deliver decent Web services. And the article had also explained WHY in addition to HOW.
Prerequsites :
- Visual Sutdio 2013/2015
- xUnit through NuGet
- xUnit runner for Visual Studio through NuGet
- Fonlow Testing through NuGet
Hints:
The dll files of NuGet packages are included in the repository.
Make Integration Tests Easier
I would like to run integration tests either in IDE or in a standalone runner, and the services should be launched and stopped automatically, without the need of running things as Administrator.
Basic Project Structure
In addition to service codes in folder RealWorldServices, I create a WCF Application project "WcfService1" to contain all service assemblies, for example, RealWorldService and RealWorldImp. Please notice that I had deleted all the scaffolding C# codes created by VS IDE, so this project is just a thin "main program"/booster/facade to launch services in IDE and IIS Express as well as IIS. As you might had seen in "WCF for the Real World, Not Hello World" , having a WCF Application project is not mandatory for hosting services in IIS, because CLR will just read Web.config and load respective WCF library codes accordingly.
Steps:
- Create a WCF Application project.
- Remove all C# codes generated by IDE.
- Add references to WCF service projects, for example RealWorldService and RealWorldImp.
The Web.config is telling IIS to activate the services without using SVC files:
<system.serviceModel>
<serviceHostingEnvironment>
<serviceActivations>
<!--This is to replace the standalone svc file whic is the legacy of asp.net web app.-->
<add relativeAddress = "RealWorldServices/RealWorld.svc" service = "Fonlow.Demo.RealWorldService.Service1"/>
</serviceActivations>
</serviceHostingEnvironment>
<services>
<service name="Fonlow.Demo.RealWorldService.Service1" behaviorConfiguration="authBehavior">
<!-- Service Endpoints. A Service may provide multiple endpoints -->
<!-- Not need to define host. Relative -->
<endpoint address="" binding="basicHttpsBinding" contract="Fonlow.Demo.RealWorldService.IService1" bindingConfiguration="httpsBindingConfig">
<!--
Upon deployment, the following identity element should be removed or replaced to reflect the
identity under which the deployed service runs. If removed, WCF will infer an appropriate identity
automatically.
-->
<identity>
<dns value="localhost" />
</identity>
</endpoint>
When you run WcfService1, it will be launched by IIS Express by default, and the services mentioned in web.config will be activated.
When you are developing more services, you may continue to add references to the WcfService1 project.
Remarks:
- Adding references to new service implementation assemblies is just for intellisense when modifying Web.config in Visual Studio IDE. For production, It is perfectly OK that the assembly of the WCF application has no static binding to service assemblies. The service host will read the Web.config during startup and load respective service assemblies at runtime. In production, you don't even need to deploy the dummy WCF application assembly.
- To support httpS when testing in IIS Express, you need some twists in the properties of the WCF application project, as documented in Scott Hanselman's article "Working with SSL at Development Time is easier with IIS Express". Please note there's a "magic" starting port number 44300 for httpS.
More Robust Client Proxies
If you had done WCF programming long enough, likely you have encountered this error message on client side without disclosing specific/useful error info:
"The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be used for communication because it is in the Faulted state."
This often happens when an exception is thrown on the service side by the run time rather than your service operation codes.
MSDN had suggested not to use the using statement with the client proxy class, even though the generated proxy classes had implemented the IDisposable interface. So this workaround does not conform to the programming rule in MSDN; or in other words, had made an exceptional case against the rule. While working well, the workaround might make your application codes look repetitive and long and clumsy.
The problems and the alternative solutions are further discussed in my blog "Proper Disposal of WCF Channels against a WCF Defect. Follow up". So after generating proxy classes using svcutil.exe, it is better to wrap the proxy client classes with "Safe Channel".
public class SafeChannel<T> : IDisposable where T : ICommunicationObject, IDisposable
{
public SafeChannel(T channel)
{
Instance = channel;
}
public static IDisposable AsDisposable(T client)
{
return new SafeChannel<T>(client);
}
public T Instance { get; private set; }
bool disposed;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
Close();
}
this.disposed = true;
}
}
void Close()
{
bool success = false;
try
{
if (Instance.State != CommunicationState.Faulted)
{
Instance.Close();
success = true;
}
}
finally
{
if (!success)
Instance.Abort();
}
}
}
public class RealWorldProxy : SafeChannel<Fonlow.RealWorldService.Clients.Service1Client>
{
public RealWorldProxy(string endpointName)
: base(new Fonlow.RealWorldService.Clients.Service1Client(endpointName))
{
}
}
Integration Tests with Automatic Setup and Teardown of Services
There are quite a few ways to construct integration tests by developers:
- Set multiple startup projects in the same VS solution.
- Host the services in IIS, and run the integration test suit either in VS IDE or a standalone runner. And you can attach service codes to respective w3wp.exe instance for debugging.
- Create service projects in A.Sln, and the test suit in B.Sln, so you can step through both the service hosted in IIS Express launched by VS IDE, and the client codes.
However, sometimes I would like to put the test suits and the service codes in the same VS solution file, and run tests with automatic setup and teardown of the services.
Generally I would write integration test suits on NUnit or xUnit, rather than MS Test, so I could run the test suits during deployment on host machines without Visual Studio installed.
public class TestConstants
{
public const string IisExpressAndInit = "IISExpressStartup";
}
[CollectionDefinition(TestConstants.IisExpressAndInit)]
public class IisCollection : ICollectionFixture<Fonlow.Testing.IisExpressFixture>
{
}
[Collection(TestConstants.IisExpressAndInit)]
public class IntegrationTest
{
const string realWorldEndpoint = "DefaultBinding_RealWorld";
[Fact]
public void TestGetData()
{
using (RealWorldProxy client = new RealWorldProxy(realWorldEndpoint))
{
client.Instance.ClientCredentials.UserName.UserName = "test";
client.Instance.ClientCredentials.UserName.Password = "tttttttt";
Assert.True(client.Instance.GetData(1234).Contains("1234"));
}
The IIsExpressFixture
is responsible for starting IIS Express at the beginning and stopping the service after all test cases in all test classes decorated with the same XUnit.CollectionAttribute in assembly TestRealWorldIntegration.dll are finished.
In app.config of TestRealWorldIntegration,
<appSettings>
<add key="Testing_UseIisExpress" value="True" />
<add key="Testing_HostSite" value="WcfService1" />
<add key="Testing_HostSiteApplicationPool" value="Clr4IntegratedAppPool" />
<add key="Testing_SlnRoot" value="C:\VSProjectsMy\HelloWorldAuth" />
<add key="Testing_BaseUrl" value="https://localhost:44300/" />
</appSettings>
The appSettings define some keys to be used by IisExpressAndInit for launching IIS Express and direct IIS Express to run WcfService1.
Hints:
In Visual Studio 2012, 2013 and 2015 before Update 1, IIS Express uses %userprofile%\documents\iisexpress\config\applicationhost.config for all host sites, registered by Visual Studio in the first run of Web applications or services. In Visual Studio 2015 Update 1, IIS Express uses YourVsSlnRootFolder\.vs\config\applicationhost.config. This is why the Sln Root folder has to be defined in the app.config of the test assembly.
I would run all tests regularly including unit tests and integration tests.
And you will see that IIS Express is launched by the IIsExpressAndInit
.
Points of Interest
Over years I have written a lot WCF services, reviewed/fixed legacy codes left by other developers, and consumed Web services from other vendors. I find that malpractice or inefficient practices resulting in dirty solutions were wide spread, consuming too much of company's wallets and developers' life unnecessarily.
While I firmly believe the essence of the problems are in project management and company culture, particularly the inappropriate mindsets when employing Agile practices or Scrum framework, I consider that developers themselves should be thrilled to write least amount of codes in least complexity, and automate their development works as much as possible.
WCF and MVC are very sophisticated, comprehensive and mature, we should study more in depth, rather than write clumsy codes that work but generate huge technical debts. Learn more, code less and deliver more.