Click here to Skip to main content
15,884,962 members
Articles / Game Development / Unity

Fast track to DI using Unity App Block

Rate me:
Please Sign up or sign in to vote.
4.67/5 (2 votes)
3 Sep 2013CPOL9 min read 10.3K   1   2
How to identify dependencies and resolve them using Unity App Block. Simple Hello World.

Introduction

In large enterprise applications, there are many components mixed with each other. Ideally the components should be loosely coupled and highly cohesive with respect to each other, but in the fast track development mode, we often tend to mix things up.

As the requirements change and we have to make corresponding changes in the system, we then lament later on as system becomes too complex. As modules are dependent upon each other, it gets very difficult to introduce changes in one module while taking care not to break others. 

The solution of this problem is a set of patterns identified under the name of "Inversion of Control". This phrase indicates the change in mindset. Earlier the component using the dependency is responsible for maintaining it within itself. Now the responsibility is taken out and given to someone else.

Unity Application Block is a toolkit from Microsoft, which allows a component to Register the dependency initially; then whenever it is needed, Resolve the need; and whenever the task is over, then Dispose the association.

So before we begin to check what Unity does and how it does it, we first need to figure out what exactly is a dependency, and more so, how to identify a dependency in an existing architecture. We will then think about what’s the issue with having everything self contained and then sort things out so that proper Separation of Concerns is achieved in terms of all SOLID goals.

Background

Aspect-oriented programming is all about "concerns", and how you manage them successfully. Instead of thinking of an application as a series of separate objects, you consider each function of the application as a concern

Components that relate directly to a specific application, are “core concerns”. AOP it considers all other types of components and code as "crosscutting concerns." These features are generic or semi-generic to many applications, and they usually include familiar functions, such as validation, authorization, caching, structured exception handling, logging, and performance monitoring. Aspect-oriented programming techniques aim to help you more efficiently manage these crosscutting concerns.
Some will be specific to the system in question and some will be more general in purpose. You can categorize some requirements as functional requirements, and some as non-functional requirements (or quality attributes).

Identifying Dependencies 

The literal meaning of dependency is something which is required to perform a core task. 

Usually we Create/Use/Dispose (CUD) the dependency in our owning module itself. So we do not notice it at start of development, until many other pieces also need to use that common function/entity. Even in this case, the same CUD code is replicated in all the places required.

This creates lot of duplicate code and any changes in the dependency itself has to be replicated in all places and then tested

If it is just for specific task, then its scope is limited to that implementation. However if the dependency is required for many functionalities, then the it is scoped to the entire module.

If we have a class having certain functions, the first case is resolved via "function parameters". If more than one function needs it, then a property is used to expose the dependency.

So, we will first construct a very simple application and explicitly place a dependency in there. Then we will resolve the dependency step by step.

Ok, lets start with the first program we always design for any new purpose, the GOD Hello World :)
Imagine what can be the dependency in such a overly simplistic program.... !!! All it does is print “hello world” on a device. If the last sentence was read a bit critically, you would figure out that the dependency is the “DEVICE”.
Why???
Usually the device is taken as the standard output “std-out” which is always the monitor. BUT, we can write this text to anywhere we want the output to be sent. And any output we chose, the methodology and the API is device dependent. For screen, a simple “console.out”; for file, “FileStream.Write”, for database, “connection.Execute(storedProc)”... etc. Here each output device can be considered as a “Stream” (and actually implemented as one in API).
So in order to define our business operation of writing to a stream, we design an abstract method; whether you place it in an abstract class or interface is your wish, that’s a different discussion.

C#
void WriteToStream(string message);

Now let's forget about IoC and decide that we will have the device as a file with everything related to its API within our own main class. So taking our main application as a web forms application, we have a page Welcome.aspx. All it does is write ‘Hello World’ to a file. Too crude to start with, but will slowly build up.

C#
public partial class Welcome : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        WriteToFile();
    }

    private void WriteToFile()
    {
        string message = "Hello World";
        System.IO.File.AppendAllText(@"c:\MyTestFile.txt", 
                     message, System.Text.ASCIIEncoding.ASCII);
    }
}

This seemingly naive program, has many problems (which we will discuss), but the most critical is that “it won’t run”. This will give a “security exception” because the web debugger account is not permitted to write to the “C:\” drive...

And what can we do about that. Simple, we will just change the location to “E:\”. Although this location exists on my system but I will never be sure that it does so in yours also. So if I send you the compiled application, it will give a PathNotFoundException, or if this is mapped to DVD drive, it will try to write there but fail, cause DVD is not written using standard file IO. If this is a shared drive, then the account must have write permission to that drive (not you, but the web account; which will never unless you do impersonate).

So many problems at first shot, we don’t know how many more are coming.

But the solution to all these and many more unknown is single. Move this botheration to another component and let it bother about how it wants to write the message and where it wants to write. Or even, if it really wants to write at all.....

C#
public class FileMessenger 
{
    private string _FilePath;        

    public FileMessanger()
    {
        string filePath = @"c:\myfolder\myfile.txt";
        var dir = Path.GetDirectoryName(filePath);

        if (!string.IsNullOrWhiteSpace(dir))
        {
            if (!Directory.Exists(dir))
                Directory.CreateDirectory(dir);
            this._FilePath = filePath;
        }
    }

    public void WriteToStream(string message)
    {
        File.AppendAllText(_FilePath, message, System.Text.ASCIIEncoding.ASCII);
    }

    public string ReadFromStream()
    {
        return File.ReadAllText(this._FilePath);
    }
}

The main component will just want to take this writer component whenever it wants and the writer will take care of everything required to write the message. It will just take in the message and optionally an indicator about where to write that; else there will be certain defaults.

We have this component ready and the main component then consume this by instantiating the class and consuming the methods. However if it decides to write to some other stream at some other stage, a lot needs to be changed. Also since this component will be used in many places, there will be lots of places where this will be consumed, there will be lots of instances created and destroyed. Also the responsibility of handling these instances will be of the main component.

C#
string message = "Hello World";
FileMessenger messengerFile = new FileMessenger();
messengerFile.WriteToStream(message);

As entire interaction with this component lies within the main component, every time the component needs to be changed, the main component will change as well. This violates the Single Responsibility Principle. So what is the option? The way is the Interface Segregation Principle, so we have an interface which gets instantiated with a live object. This object needs to  be supplied from outside the main class by a so called manager.  

C#
public interface IMessenger
{
    /// <summary>
    /// Writes a message to the implementing stream
    /// </summary>
    /// <param name="message">The text to be written. 
    /// If it's an object, do a preformat in the ToString override</param>
    void WriteToStream(string message);

    /// <summary>
    /// Reads a message from the stream
    /// </summary>
    /// <returns>Message stored within the stream. 
    /// Entire stream contents will be returned</returns>
    string ReadFromStream();
}

public class FileMessenger : IMessenger
{
    // Various methods
}
public class DatabaseManager : IMessenger
{
}

Now we can have an instance of the interface and take in the desired object at runtime. Seems good, right? In a simple application, yes; however as it grows complex, no.

But what exactly is the issue.

The decision of which instance to create is taken by the consumer; so if there are many consumers to our component, this decision will be spread over the application rather than being taken at a central place.

So the next step is to move this decision to a separate component, which can manage the instances and offer them for consumption to whoever wants them. This component will expose the functionality of the inner component, and based on the instantiated object, invoke the functionality. The consumer will just get an instance of this outer component, and call the function without knowing which actual instance served the request.

This completes the dependency cycle, we have each component clean within and least dependent upon each other.

This is implemented by some Inversion of Control container, and for us it will be Unity Application Block from Microsoft.

Steps:

  1. Create interface and implementing classes. One implementation has no dependency outside the class (parameter-less constructor) and another has a dependency taken through the constructor.
  2. Create entries in web.config file to identify desired component. We can also define other rules and conditions along with, if the decision is dependent on many variables.
  3. In the UnityInteraction class, we register the different classes with Unity and associate it with the interface. Now whenever an instance of the interface is required, Unity will internally create an instance of the configured class and return it for consumption.
  4. Since this is a web application and the Unity component needs to be referred all over the application. This class is instantiated at application root, in this case the Global.asax.csApplication_Start method, which stores it in an application state variable for availability to entire application scope.
  5. Create a consumer of the interface we illustrated above. This will be called by the consumer instead of directly calling the interface or its implementations.  This will have the dependency which will be taken via its constructor. Here we take in an instance of the interface in the constructor and expose methods to the interface directly. Here we are not concerned about the particular instance. In this sense, this class acts like a Decorator over the actual interface, although it doesn’t do anything extra.
  6. In the web code, resolve the consumer component. Unity automatically resolves the underlying dependency of the interface. In our main consumer class, we resolve our dependency through Unity and request an instance of the MessageInteraction class. Here’s the difference, we don’t instantiate the component or even its interfaces, we just ask for the Decorator object. Unity figures out the decorator expects an instance of the interface, and then it looks for the appropriate objects in its registration database. It finds a configuration which says the FileMessenger is to be used, and that also expects a parameter. So it gets the parameter and instantiates FileMessenger and then in turn instantiates MessageInteraction and passes it to the consumer class.
XML
<appSettings>
    <add key="MessengerType" value="FileMessanger"/>
<!--<add key="messengerType" value="DatabaseMessanger"/>-->
    <add key="FilePath" value="c:\myfolder\myfile.txt"/>
</appSettings>
C#
public class UnityInitializer
{
    private IUnityContainer container;

    public IUnityContainer UnityContainer { get { return this.container; } }

    public UnityInitializer()
    {
        ConfigureUnity();
    }

    private void ConfigureUnity()
    {
        container = new UnityContainer();
        if (ConfigurationManager.AppSettings["MessengerType"] == "FileMessanger")
        {
            var filePath = ConfigurationManager.AppSettings["FilePath"];                
            container.RegisterType<IMessenger, 
               FileMessanger>(new InjectionConstructor(filePath));
        }
        else
            container.RegisterType<IMessenger, DatabaseManager>();
    }
}

void Application_Start(object sender, EventArgs e)
{
    UnityInitializer init = new UnityInitializer();
    Application["UnityInit"] = init;
}

public class MessageInteraction
{
    private IMessenger _messanger;

    public MessageInteraction(IMessenger messanger)
    {
        this._messanger = messanger;
    }
    public void WriteUsingUnity(string message)
    {
        _messanger.WriteToStream(message);
    }
}

protected void Page_Load(object sender, EventArgs e)
{
    var container = (this.Application["UnityInit"] as UnityInitializer).UnityContainer;

    MessageInteraction interact = 
      container.Resolve<MessageInteraction>() as MessageInteraction;

    interact.WriteUsingUnity("Hello World");
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead 3PillarGlobal
India India
I am a solutions lead working on .NET for about 10 years, focusing on core framework implementations in project. My development interests are in middle tier and backend components and sometimes, identifying design elements for technical solutions. Apart, I collect coins and stamps and do photography whenever I get a chance.

Comments and Discussions

 
QuestionSome thoughts about your article Pin
Klaus Luedenscheidt3-Sep-13 19:43
Klaus Luedenscheidt3-Sep-13 19:43 
AnswerRe: Some thoughts about your article Pin
Nitin Singh India3-Sep-13 21:29
Nitin Singh India3-Sep-13 21:29 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.