Introduction
Application requirements change frequently and software is constantly evolving.
As a result, such applications often become monolithic making it difficult to
add new functionality. The Managed Extensibility Framework (MEF) is a new
library in .NET Framework 4 that addresses this problem by simplifying the
design of extensible applications and components.
In this blog I will be trying to simplify the importance of MEF and where can
MEF be applied. Let me ask a question before explaining the uses of MEF. Imagine
you have an application which communicates with SQL database. One fine day your
client asks you to migrate the data layer from SQL database to Oracle or MYSql
or so on… How will you accommodate the change without having an impact on the
application? Will you not want a database layer wherein any change in the
database layer does not force you to change even a single line of code any
where? Will you not want a database layer such that a dll is picked
accommodating the database changes instead of modifying the existing code? Will
you not want a form of deployment such that the dll is picked at runtime
accommodating the new database changes?
If the answer is yes for all the above questions; you should have explored MEF
by now. Today I will be taking you through how to create a database layer using
a MEF such that a new database change can be accommodated by deploying a dll in
the database folder.
Prerequisites
- Visual Studio 2010
- Knowledge of C#.Net
Project Flow
Add a class library project having namespace BSIL.POC.MEFDatabase.
Add an interface named IDatabase.cs
In the IDatabase.cs we have the following code:
namespace
BSIL.POC.MEFDatabase
{
public interface
IDatabase
{
string DatabaseName {
get; set; }
bool Login(string
userName, string password);
}
}
Add another class project having namespace BSIL.POC.MEFDatabase.SQL . Add a
class named Database.cs. This class will connect to SQL database and implements
IDatabase interface as below:
using
System;
using
System.ComponentModel.Composition;
namespace
BSIL.POC.MEFDatabase.SQL
{
[Export(typeof(IDatabase))]
public class
Database :
IDatabase
{
private string
_databaseName=string.Empty;
public Database()
{
_databaseName = "SQL";
}
public bool
Login(string userName,
string password)
{
if (userName !=
string.Empty && password !=
string.Empty)
{
return
true;
}
return false;
}
public string
DatabaseName
{
get
{
return _databaseName;
}
set
{
_databaseName = value;
}
}
}
}
Add reference for System.ComponentModel.Composition and BSIL.POC.MEFDatabase in
BSIL.POC.MEFDatabase.SQL project. Important for MEF is the "Export" attribute
from the "System.ComponentModel.Composite" (MEF) Namespace.
Export means: This is a "IDatabase" plugin.
Add another class project having namespace BSIL.POC.MEFDatabase.Oracle .
Add a class named Database.cs. This class will connect to Oracle database and
implements IDatabase interface as below:
using
System;
using
System.ComponentModel.Composition;
namespace
BSIL.POC.MEFDatabase.Oracle
{
[Export(typeof(IDatabase))]
public class
Database :
IDatabase
{
private string _databaseName=string.Empty;
public Database()
{
_databaseName = "Oracle";
}
public bool Login(string
userName, string password)
{
if (userName !=
string.Empty && password !=
string.Empty)
{
return
true;
}
return false;
}
public string DatabaseName
{
get
{
return _databaseName;
}
set
{
_databaseName = value;
}
}
}
}
Add reference for System.ComponentModel.Composition and BSIL.POC.MEFDatabase in
BSIL.POC.MEFDatabase.Oracle project.
Console App
Create a console app having namespace BSIL.POC.ConsoleApp. Add reference of
System.ComponentModel.Composition and BSIL.POC.MEFDatabase.
Plugin directory
Your application needs to know where plugins are placed, that´s why we create a
"PlugIns" dictionary. This plugin directory will have the dll which we want to
use at run time. It could be dll of either SQL or Oracle or anything which
implements IDatabase.
For ease of use we write a Container.cs class so that it can be accessed by
Program.cs or any other class file. The Container.cs will have code as below.
Container.cs
using
System.ComponentModel.Composition;
using
BSIL.POC.MEFDatabase;
using
System.ComponentModel.Composition.Hosting;
namespace
BSIL.POC.ConsoleApp
{
public class
Container
{
[Import(typeof(IDatabase))]
public
IDatabase Database { get;
set; }
public Container()
{
DirectoryCatalog catalog =
new DirectoryCatalog("Plugins");
CompositionContainer container =
new
CompositionContainer(catalog);
CompositionBatch batch =
new CompositionBatch();
batch.AddPart(this);
container.Compose(batch);
}
}
}
Plugins are manged with parts and catalogs. We tell our catalog to search for
plugins in this assembly. Through the container we tell MEF that here is a
plugin interface (the list with the import attribute) and start the "compose"
process.
Directory Catalog
To discover all the exports in all the assemblies in a directory one would use
the [System.ComponentModel.Composition.Hosting.DirectoryCatalog].
DirectoryCatalog catalog = new DirectoryCatalog("Extensions");
If a relative directory is used it is relative to the base directory of the
current AppDomain.
The DirectoryCatalog will do a one-time scan of the directory and will not
automatically refresh when there are changes in the directory. However, you can
implement your own scanning mechanism, and call Refresh() on the catalog to have
it rescan. Once it rescans, recomposition will occur.
var catalog = new
DirectoryCatalog("Extensions");
The entry point of BSIL.POC.ConsoleApp.Program.cs. The code will look like
below:
using
System;
namespace
BSIL.POC.ConsoleApp
{
class Program
{
static void
Main(string[] args)
{
Console.WriteLine("Loading
the database dll using directory catalog....");
Container container =
new Container();
Console.WriteLine(string.Empty);
Console.WriteLine("Connecting
to database....");
bool isLoggedIn =
container.Database.Login("username",
"password");
Console.WriteLine(string.Empty);
Console.WriteLine("I
am {0}logged in using {1}", isLoggedIn ? string.Empty
: "not ", container.Database.DatabaseName);
Console.ReadLine();
}
}
}
Inside the "Program" class is a "IDatabase" property, which is decorated with
the "Import" attribute from the MEF namespace.
Import means: I took everything of type IDatabase inside plugins directory.
Initially I have put Sql dll inside plugins directory as in screenshot below:
When I run the application I see the output as below
Output seen when Sql dll is placed inside Plugins folder
Later I delete the Sql dll and put Oracle dll. When I run the application I see
the output as below
Output seen when Oracle dll is placed inside Plugins folder
Finally the project structure should look like this
Conclusion
Now at any point of time we can change the implementation of datalayer by just
replacing the dll either SQL, Oracle or any dll which implements IDatabase
interface. We can implement the same flow using Unity blocks. I felt Unity could
be a better match as it is more easily configurable and more developer friendly.
Next article we can see how to implement the same Database change using Unity
blocks.