Introduction
Presumably, you have created a few commercial WCF projects. If not, it is recommended that you create a few Hello World
projects first for getting hand-on experiences in WCF. There are tons of tutorials on the Internet for creating a Hello World
WCF project, like Implementing a Hello World WCF Project, and Implementing a WCF Service in the Real World. This article is to demonstrate how to efficiently and effectively manage WCF service development in enterprise environment through team work in order to rapidly develop and deliver decent Web services.
Hello World
You know how good WCF is, and how convenient building Web services using the “WCF Service Application” template is. With simply a few mouse clicks, the VS IDE creates a set of skeleton codes for you so you could rush to coding business features right away, as illustrated in the following screenshots you are already familiar with.
For small projects, say less than 2000 lines in the WCF modules, the template has provided a decent RAD support. I used these templates in my early days of programming WCF as well. However, what crafted by the template has a few vulnerabilities against the productivity of the whole SDLC.
- Having the
abstract interface
and the concrete implementation in the same package (assembly) is smelly. - The service contract and the data contract have no namespaces defined.
Vulnerability 1 destroys the abstraction and the separation of concern which are the keys for managing complexity.
Without explicitly defining namespaces, the default namespaces are used, namely, http://tempuri.org for operation contracts and http://schemas.datacontract.org for data contracts, and the second URL is owned by Microsoft. While this sounds trivial, however, this is not a good publicity to your company which is apparently not owned by Microsoft. In addition, when you have a lot of data contracts to handle, sooner or later, you are going to hit the wall. For example, if you have over one hundred data contracts all living in the same default namespace http://schemas.datacontract.org, it is not unlikely programmers will create 2 contracts named “CompositeType
” or alike, then these 2 data contracts could hardly live in the same AppDomain
, since data types in the same schema namespace will likely be transformed into classes in the same CLR namespace
. This is especially true if BizTalk or other implementations of service bus is involved in system integration, which is an information hub of enterprise architecture. Not explicitly defining namespaces is exchanging hassles in the future, and particularly high costs in maintenance.
Real World
In enterprise environment, WCF is a powerful and convenient framework for wrapping up new business components and legacy applications. The complexity of the whole development work soon require great degree of separation of concerns and separation of responsibilities into these areas:
- Interface design
- Implementation
- Testing
- Configuration
- Client programming
WCF provides inherent supports for such separations. For some reasons, those templates coming with VS hide and limit such power of WCF, though they are good enough to deliver your first WCF solution ASAP, and get applause from your boss. However, if you want to deliver elegant solutions for complicated problems in efficient way, you had better follow the SOLID OOD principles and separate concerns as much as possible. Otherwise, you will have to work harder, rather than smarter, when the project needs to evolve and resolve more complicated problems.
In a typical enterprise application, classes of different life cycles had better stay in different packages, or in different Visual Studio solutions, or even in different revision control repositories. Planning for such arrangement is essential for improving maintainability and flexibility in order to lower the costs and improve productivity and quality. In addition, the build time could be reduced.
The following screenshot illustrates the basic structure of a simple real world project.
Create Project RealWorldService with Contracts Only
namespace Fonlow.Demo.RealWorldService
{
[ServiceContract(Namespace = "http://www.fonllow.com/demo/RealWorldService/2012/08")]
public interface IService1
{
[OperationContract]
[FaultContract(typeof(Evil666Error))]
string GetData(int value);
[OperationContract]
CompositeType GetDataUsingDataContract(CompositeType composite);
}
[DataContract(Namespace = "http://www.fonllow.com/demo/RealWorldService/Data/2012/08")]
public class CompositeType
{
[DataMember]
public bool BoolValue
{
get;
set;
}
[DataMember]
public string StringValue
{
get;
set;
}
}
[DataContract(Namespace = "http://www.fonllow.com/demo/RealWorldService/Data/2012/08")]
public class Evil666Error
{
[DataMember]
public string Message { get; set; }
}
}
And the project also contains a batch file of generating WSDL.
cd %~dp0
:: generate wsdl. Run this when there's a breaking change in the interface.
You still need to be careful about interface versioning.
"C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools\
svcutil.exe" bin\Debug\Fonlow.RealWorldService.dll /directory:..\deployment
Create Project RealWorldImp for Implementation
namespace Fonlow.Demo.RealWorldService
{
public class Service1 : IService1
{
public string GetData(int value)
{
if (value == 666)
throw new FaultException<Evil666Error>(
new Evil666Error() { Message = "Hey, this is 666." });
return string.Format("You entered: {0}", value);
}
public CompositeType GetDataUsingDataContract(CompositeType composite)
{
if (composite == null)
{
throw new ArgumentNullException("composite");
}
if (composite.BoolValue)
{
composite.StringValue += "Suffix";
}
return composite;
}
}
}
Create Project TestRealWorld for Unit Test
[TestMethod]
public void TestGetData()
{
IService1 service = new Service1();
Assert.IsTrue(service.GetData(1234).Contains("1234"));
}
It is more handy to unit test a service function as an in-process function.
Create RealWorld Web Service with IIS
Steps
- Create a Web site http://localhost:8998 in C:\inetpub\wwwroot\RealWorld with an application pool of .NET 4.
- Copy assemblies of project
RealWorldImp
to the bin directory of the Web site
Step 2 can be done through the post build event of project RealWorldImp
.
Hints
If you don't like port number like 8998 and would use something like http://MyService.localhost, you may alter your local HostFile. If you haven't never done so, please Google "Windows HostFile".
Create Project RealWorldServiceClientApi
The class library project should have references, System.ServiceModel
and System.Runtime.Serialization
.
The code of this project and the client config are generated by a batch file named CreateClientApi.bat.
cd %~dp0
rem generate client proxy classes with the wsdl
"C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools\svcutil.exe"
..\deployment\*.wsdl ..\deployment\*.xsd /language:C#
/n:http://www.fonllow.com/demo/RealWorldService/2012/08,
Fonlow.RealWorldService.Clients /n:http://www.fonllow.com/demo/RealWorldService/Data/2012/08,
Fonlow.RealWorldService.ClientData /o:RealWorldClientApiAuto.cs /config:appAuto.config
The client API assembly and the config file could then be used by your client programs, including the integration test suit.
Hints
If your Web services will be consumed by other IT vendors which use .NET for software development, you may provide the client API to them so they could rapidly develop applications consuming your Web service.
Note that I named those generated files with Auto suffix, so I could exclude them when running some static code analysis tools like Source Monitor.
Create Project TestRealWorldIntergration for Integration Test
This project is the first consumer of the client API.
const string realWorldEndpoint = "DefaultBinding_RealWorld";
[TestMethod]
public void TestGetData()
{
using (Service1Client client = new Service1Client(realWorldEndpoint))
{
Assert.IsTrue(client.GetData(1234).Contains("1234"));
}
using (Service1Client client = new Service1Client(realWorldEndpoint))
{
try
{
Assert.IsTrue(client.GetData(666).Contains("1234"));
Assert.Fail("Expect fault");
}
catch (FaultException<Evil666Error> e)
{
Assert.IsTrue(e.Detail.Message.Contains("666"));
}
}
}
Hints: The source code of both Hello World and Real World are here.
How Real Is This Real World Service?
The construction of this Real World service is not utilizing many handy features provided by the IDE Wizards, and require some more manual steps to setup the project structures. What is the bargain?
Please do consider, in a decent Web service development project, the majority of project time is spent on analysis, interface design, implementation and testing. The time saved by the Wizards is very insignificant in non-trivial projects, however, the costs bought by the “poor” structures created by Wizard will be long standing, and haunting you soon. In the future, I may be presenting some negative case studies about how poor practices of software engineering using WCF bring low productivity of software development and poor quality of the service.
Here, I would just try to explain how to establish positive experience of developing Web services using WCF.
Separating the Interfaces and the Implementation Into Two Assemblies
I believe that you have heard of and studied separation of concerns many times in your career as a software developer. Why separation of concerns?
Separation of concerns is a computer science principle that promotes separating the responsibility for an application or use case across multiple components, where each component has a distinct, small set of responsibilities.
IMHO, this is to manage complex and volatile requirements using the very limited capacity of our human brain, so we are able to design working and maintainable solutions against complicated and dynamic problems. And respective team work during development could be more efficient. In contrast, computers do not care about separation of concerns, and the loosely coupled components during software development will be glued together at run time after all.
Putting the abstraction and the concrete implementation in the same package is a significant design smell. Why?
The life cycles of the abstraction and the implementation tend to be greatly different. The abstraction is usually stable, once defined, rarely change or even should not change, for example, interfaces once published should not be changed. The implementation are subject to improvement and bug fixing.
Generate WSDL from the Assembly that Defines Contracts
The assembly that defines contracts (interfaces in C#) has enough information for generating WSDL (interfaces in XML), and the Web service that is configured to publish WSDL simply reads information from the assembly and generates WSDL on a fly. So you should not need to run the Web service first before getting the WSDL.
In a large project of distributed computing, it is often desired to start the client programming before the service implementation is done. With the WSDL, you could use .NET SDK to generate a mock implementation of the Web service. The client programmers may then use the mock Web service for constructing and testing client programs. And you as service developer may use a copy of the mock service codes to start implementing the Web service. Please note, the client programs of the WCF service might be coded in Java or PHP. Java programmers and PHP programmers may then import the WSDL to generate client proxy classes.
Not Using the Built-in Web Server of VS
Probably you have heard that testing in an environment is as similar as possible to the production environment. Since we are going to host the service in IIS, why not test it all the time with IIS?
You will get a clear idea of how your services perform. In addition, please consider this scenario.
You have developed and tested a Web service using VS IDE, then give the package to the system admin who will install and test in another environment. It is not uncommon that the Admin comes back to you and say “It doesn’t work”. You are puzzled, “It works on my computer, see, the client and the service work beautifully in harmony”. And the admin may be responding: “Do we run Visual Studio in our staging environment?”.
If you test all the time with IIS in your development machine, you may say, “It works on my IIS instance, shall we examine the difference between your IIS and my IIS?”. Do you think this sounds more pleasant to the system admin?
BTW, in VS 2012, 2013 and 2015, the built-in Web server IIS Express behaves more like an IIS server. So testing with IIS Express could be good enough for not being surprised. Anyway, it is good to do integration testing on both IIS Express for convenience and IIS for real world scenarios.
Unit Testing
Shall I explain more?
Probably here is not a good place to explain the benefits of unit testing. I would just say how bad it could be without unit testing. Without covering your service implementation as much as possible, you will spend many more hours on debugging through attaching your codes with a hosting process.
Integration Testing
Here, I use MS Test as test harness. And you may use NUnit, xUnit.NET or other test harness frameworks to construct automatic integration testing. Even if your unit tests have covered the implementation as much as possible, there are still some features of the Web service can’t be tested through unit testing, for example, service behaviors and fault contracts, etc.. The integration testing is basically a client program of the Web service, talking to the service through HTTP typically.
Depending on your company’s system configuration, it might be handy to develop the integration tests using NUnit or xUnit.NET as a test harness. So after the system administrator has installed the service, the administrator may run the tests right away to verify the deployment before the service is open for business.
Client API
A typical WCF tutorial you could find on MSDN or the Internet adds Service Reference to the consumer project, pointing to the WSDL of a running Web service or a static WSDL file, and IDE will create the client API codes within the consumer project.
Through building a client API assembly, multiple consumer projects might consume the same client API library, saving the efforts of generating client API codes in each consumer project and avoiding the mishap of having inconsistent references to a Web service. Amazon and Google, etc. typically provide client API libraries in .NET, Java, and PHP, in addition to publishing WSDL. So this is a common practice well proven in prominent software development shops.
In addition to reducing duplicate codes generated, you have better control over the namespaces of the client proxy classes. Such practice is even more beneficial when versioning of the service is needed.
If interoperability of the Web service is a part of the requirement, you may use other software development platforms such as NetBean to create client codes from the WSDL. In fact, I have seen legacy Web services in productions coded in WCF could not be consumed by non .NET platforms, and then the costs of supporting new types of client programs were huge.
Summary
The WCF templates of Visual Studio and respective Hello World tutorials are good enough for the following scenarios:
- Hello World projects
- Prototype
- Small projects in small companies used by small consumers
- Contract work without QA
For large and complex projects, those shortcuts or conveniences provided by IDE will soon become a liability giving more trouble than good. We have better program into WCF, rather than program in Visual Studio WCF. Have you heard of “Program into your language, not in it”?
Please note, this article is not about a template for every WCF projects. Do you really need to remember all these steps of creating a real world WCF project?
I don’t. However, I had already grown such habit based on these principles or practices of design:
- Separation of concerns. In particular, separate abstraction and implementation. And at coding level, put them into different VS projects.
- Understand the basic, respect related standards and conventions
- Prepare for fast changing requirements
- Test early
Many had considered that using WCF is a RAD way of developing Web services, and skipped understanding the basic (not only the basic of WCF, but the basic of Web service). Unfortunately, such practices always lead to Slow and Buggy Application Development: rapid only at the beginning of coding. People might have developed business application features rapidly, however RAD had often become Rough Application Delivery, costing dearly with technical debts.
In the real world, the business / functional requirements keep changing. The problems or market demands keep evolving. And our understanding about the problems or demands are evolving too, given the same problems and demands. A good design or a good practice is to solve complicated things through least complex solutions, while taking care of volatile requirements through Agile practices.
The good thing of WCF is making it easy to practice decoupling, and realize the integration during deployment through configuration. The software development requires decoupling for productivity, quality, flexibility and maintainability, while the system integration will bind components together to deliver services according to many environmental factors. WCF takes care of both worlds, provided you understand the basic of Web services and OOD.
This article is intended to be a stepping stone for you to learn more about managing complex projects of distributed computing using WCF.
References for Hello World