Introduction
There are many ways to make your application loosely-coupled. Using Dependency injection is one such way.
Loosely coupled application helps you in making your application maintainable, extensible and it also helps in testing your code, etc. This article will walk you through step by step into implementing IOC container into your WCF service library project. Finally, we are going to add the self hosting component. Here, we are going to use Autofac as the IOC Container.
Background
For making application loosely coupled, it should adhere to the decorum of SOLID principles in object oriented programming.
- Single responsibility principle: A class should only have a single responsibility, that is, only changes to one part of the software's specification should be able to affect the specification of the class.
- Open–closed principle: "Software entities ... should be open for extension, but closed for modification."
- Liskov substitution principle: "Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program." See also design by contract.
- Interface segregation principle: "Many client-specific interfaces are better than one general-purpose interface."
- Dependency inversion principle: One should "depend upon abstractions, [not] concretions."
Dependency injection helps in attaining 'Dependency Inversion Principle'
Source: https://en.wikipedia.org/wiki/SOLID
For IOC Container, we are going to use Autofac and Autofac.WCF
Source: https://autofaccn.readthedocs.io/en/latest/integration/wcf.html
Things You Need
- Visual Studio (I am using VS Community Edition 2017)
- Autofac
- Autofac.WCF
- And an open mind :)
Using the Code
I am going to use very simple code as an example. My focus is on making things simple to understand.
Step 1: Create a Solution and Add WCF Library Project
We'll name the solution as "DependencyInjectionWcfWithSelfHosting
" and the WCF service library project as "TestService
".
Step 2: Add Scaffolding Code in WCF Service Project
- Remove app.config, and the system created
IService1
, Service1.cs file (You can rename these files but I like to start with a clean slate):
- Right click on your
TestService
project and add new item as a WCF Service class and name it as "DisplayService
".
It should look like this:
IDisplayService.cs
[ServiceContract]
public interface IDisplayService
{
[OperationContract]
string DisplayInputValue(string input);
}
DisplayService.cs
public class DisplayService : IDisplayService
{
public string DisplayInputValue(string input)
{
return input;
}
}
As you can see, there is nothing fancy in code, just an input passed is return back as is.
But hold on a minute, we can do something that would decorate that input with some string
message, say for an example:
If I pass "Sunny
" as an input, I can add some prefix string
to it as "You've entered
" to the passed input and return it as "You've entered Sunny
".
Well, I can simply append input string
with "You've entered
" and return it from DisplayInputValue()
.
public string DisplayInputValue(string input)
{
return "You've entered" + input;
}
But, to Get Around the Topic of Dependency Injection in Wcf Service, We Will Try to Do the Appending Part From Some Utility Classes. and Then, Inject Its Instance Through Constructor Injection.
Step 3: Add Just Enough Code for Utility Class
We are going to make use of these utilities in a moment.
Step 4: Add Hosting Support
- Add the console application to the solution and name it as "
Host
". - Go to the program.cs in console application and add the following code in
Main()
. Make sure you've added reference for "TestService
" and "System.ServiceModel
".
using System;
using System.ServiceModel;
using TestService;
namespace Host
{
class Program
{
static void Main(string[] args)
{
ServiceHost host = new ServiceHost(typeof(DisplayService));
host.Open();
Console.WriteLine("Host has started");
Console.ReadLine();
}
}
}
Now there might be an app.config file in the console application we've recently added. Add the following ServiceModel
section in it.
<system.serviceModel>
<services>
<service name="TestService.DisplayService">
<endpoint address=""
binding="basicHttpBinding"
contract="TestService.IDisplayService"
name="TestServiceEndPoint" />
<endpoint address="mex"
binding="mexHttpBinding"
contract="IMetadataExchange"
name="TestServiceMexEndPoint"/>
<host>
<baseAddresses>
<add baseAddress="http://localhost/TestService" />
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceMetadata httpGetEnabled="true" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
OR
If you want, you can manually create your own service model configuration by right clicking on app.config and click on "Edit WCF Configuration" and do as shown in the wizard (make sure it will run).
- Run your "
Host
" Application to make sure everything works - You might get the access permission error, simply run your Visual Studio in administrator mode and then run your
Host
application
If you managed to get so far, congratulations. you are halfway done !!
Step 5: Verify What We Did So Far Is Working or Not
- Try to open the url "http://localhost/TestService" in your
WCFTestClient
and make sure it works.
Step 6: Complete Unfinished Business
As I mentioned at the end of point 3, we are going to inject the utility class "StringMixer
" in the "DisplayService
" as a constructor injection.
- Get to the "
TestService
" project, open your display service class and add a constructor. - Inject the dependency of
StringMixer
using its interface as an abstraction, i.e., "IStringMixer
".
DisplayService.cs now might look like this:
public class DisplayService : IDisplayService
{
private readonly IStringMixer _mixer;
public DisplayService(IStringMixer mixer)
{
_mixer = mixer;
}
public string DisplayInputValue(string input)
{
return _mixer.MixStringValue(input);
}
}
We are going to inject this dependency from the host application. Let's get started for an exciting adventure.
Step 7: Setup Autofac and Autofac.Wcf in "Host" Project
Quote:
It's like whenever Autofac sees the reference to those dependencies during constructor injection based upon their abstraction (interfaces), It will inject their registered class into it.
For example
"builder.Register(c => new StringMixer()).As<IStringMixer>();"
means, wherever there is a reference about IStringMixer, Autofac will inject StringMixer as its implementation.
Step 8: Tweak "Host" Project - A Little
- Modify code in program.cs file by adding the following code in it:
static void Main(string[] args)
{
using (IContainer container = Bootstrapper.RegisterContainerBuilder().Build())
{
ServiceHost host = new ServiceHost(typeof(DisplayService));
IComponentRegistration registration;
if (!container.ComponentRegistry.TryGetRegistration
(new TypedService(typeof(IDisplayService)), out registration))
{
Console.WriteLine("The service contract
has not been registered in the container.");
Console.ReadLine();
Environment.Exit(-1);
}
host.AddDependencyInjectionBehavior<IDisplayService>(container);
host.Open();
Console.WriteLine("Host has started");
Console.ReadLine();
host.Close();
Environment.Exit(0);
}
Step 9: Run the Host Application. This Time, You've Got the Powers of Dependency Injection.
Thank you for your patience. We are done walking those steps !!
(Image Courtesy: daniel-cheung from unsplash)
Points of Interest
- You can remove the app.config file from "
Host
" project and write the code related to EndPoint
configuration in your host application instead. Just for information, I've added support for "http
" and "netNamedPipe
".
static void Main(string[] args)
{
using (IContainer container =
Bootstrapper.RegisterContainerBuilder().Build())
{
var httpLocation = "http://localhost/DisplayService";
Uri address = new Uri(httpLocation);
var netNamedPipeLocation = "net.pipe://localhost/DisplayService/";
ServiceHost host = new ServiceHost(typeof(DisplayService));
host.AddServiceEndpoint(typeof(IDisplayService),
new BasicHttpBinding(), httpLocation);
host.AddServiceEndpoint(typeof(IDisplayService),
new NetNamedPipeBinding(NetNamedPipeSecurityMode.None),
netNamedPipeLocation);
IComponentRegistration registration;
if (!container.ComponentRegistry.TryGetRegistration
(new TypedService(typeof(IDisplayService)), out registration))
{
Console.WriteLine("The service contract has not been registered
in the container.");
Console.ReadLine();
Environment.Exit(-1);
}
host.AddDependencyInjectionBehavior<IDisplayService>(container);
host.Description.Behaviors.Add(new ServiceMetadataBehavior
{ HttpGetEnabled = true, HttpGetUrl = address });
host.AddServiceEndpoint(
ServiceMetadataBehavior.MexContractName,
MetadataExchangeBindings.CreateMexNamedPipeBinding(),
netNamedPipeLocation + "mex"
);
host.Open();
Console.WriteLine("The host has been opened.");
Console.ReadLine();
host.Close();
Environment.Exit(0);
}
}
- You can also make use of the information provided in this article in WAS hosting as well.
- You can use any IOC container apart from Autofac or can make one of your own for dependency injection.
- It's not always necessary to have an interface for your class, But it gives better abstraction and composition over inheritance. Though, there are certain ways to Inject dependency for such class (I mean class without interface) using IOC Container.
References
History
- 29/07/2019
- Format added for code snippet
- Updated images