Click here to Skip to main content
15,860,861 members
Articles / Desktop Programming / Windows Forms

Windows Forms Modular App Using MEF

Rate me:
Please Sign up or sign in to vote.
4.93/5 (34 votes)
12 Nov 2014CPOL7 min read 99.5K   6.4K   106   36
Creating a modular Windows Forms app using MEF.

Introduction

The idea of this article is to build a system that is completely modular. The MEF provides us with a complete framework to achieve this goal, however, how these modules will behave depends on our implementation.

I used the classic Customers, Orders, and Products example. Thus it's easy to understand the purpose of the system and how it works.

I believe that implementations are not best practices, but I tried to get as close as possible to the design patterns. If anyone knows a better implementation, please, instead of simply saying this is not good, give an example of what would be the best implementation. So everyone learns.

Background

MEF allows us to work with the concept of plug-ins. It provides a framework that allows us to specify the points where the application may be extended, exposing modules that can be plugged by external components. Rather than explicitly referencing the components in the application, MEF allows our application to find components at runtime, through the composition of parts, managing what it takes to keep these extensions. Thus, our application does not depend on an implementation, but an abstraction, and can add new features to a program without the need to recompile, or even, without interrupting its execution.

MEF architecture (quite simple...)

To build a pluggable application using MEF, we must follow these steps:

The extension points are the "parts" of our application where we want to allow extensions.

For each extension point set, we need to define an MEF contract that can be a delegate or an interface. In our example, we will create a DLL containing the interface that defines our MEF Contract.

To inform MEF how to manage our plug-ins, we use the Import and ImportMany attributes.

To create an extension, we should first implement the MEF Contract to the desired extension point. This extension is known as an MEF Composable Part. To define a class as a Composable Part, we use the attribute Export.

The Catalog holds, at runtime, a list of all imported Composable Parts. The Catalogs can be of type AssemblyCatalog, DirectoryCatalog, TypeCatalog, and DeploymentCatalog.

  1. Define extension points
  2. Define an MEF Contract for each extension point
  3. Define a class to manage the extension point
  4. Create the plug-in
  5. Define a Catalog

Requirements

For the design of this solution, the following requirements are mandatory:

  • The system should be modular, where the modules are independent of one another;
  • The modules should be loaded dynamically at application startup;
  • The system should allow modules to register their menus in the Host form;
  • The system should allow a module to interact with other modules without having to reference the other module;
  • The system should look for modules in different folders;
  • Each module must implement its own graphical user interface in the form of windows (MDI children);
  • The modules must share the same connection to the database.

Using the code

Our solution will consist of five projects:

Project Name Project Type Description
ModularWinApp Windows Forms Host Windows Forms
ModularWinApp.Core Class Library Core of the application
ModularWinApp.Modules.Clients Class Library Clients Module
ModularWinApp.Modules.Orders Class Library Orders Module
ModularWinApp.Modules.Products Class Library Products Module

The solution is based on the following concept:

ModularWinApp.Core contains interfaces and classes that make up the core of the application. This assembly will be referenced by all projects of our solution.

ModularWinApp contains the Host form and the Host app settings. The entry class Program will create a single instance of the class ModuleHandler and will initialize the modules and the connection that will be shared across the modules.

The modules will expose two classes, one that implements the IModule interface, that serves as the Facade that allows other modules to have access to the module commands, and another which implements the IMenu interface which supplies access to the menu of the module.

The core

The core of the application consists of common interfaces, and concrete classes that implement the features that should be used by the Host and the modules to which the requirements are met.

Interfaces

Interfaces to implement the commands system:

  • ModularWinApp.Core.Interfaces.ICommand
  • ModularWinApp.Core.Interfaces.ICommandDispatcher

Interface to implement the database access:

  • ModularWinApp.Core.Interfaces.IDataModule

Interface to implement the Host Form:

  • ModularWinApp.Core.Interfaces.IHost

Interface to implement the Menus:

  • ModularWinApp.Core.Interfaces.IMenu

Interface to implement the Modules:

  • ModularWinApp.Core.Interfaces.IModule

Interface to implement the MEF Attributes:

  • ModularWinApp.Core.Interfaces.IModuleAttribute

Interface to implement the ModuleHandler:

  • ModularWinApp.Core.Interfaces.IModuleHandler

Concrete classes

The MEF Custom Attributes classes:

  • ModularWinApp.Core.MenuAttibute
  • ModularWinApp.Core.ModuleAttribute

Commands System classes:

  • ModularWinApp.Core.ModuleCommand
  • ModularWinApp.Core.ModuleCommandDispatcher

The Core main class:

  • ModularWinApp.Core.ModuleHandler

The database access class:

  • ModularWinApp.Core.SqlDataModule

The custom attributes

The custom attributes are used to help us provide metadata to our modules, so after MEF loads them, we can identify our modules.

Metadata is optional in MEF. Using metadata requires that the exporter defines which metadata is available for importers to look at and the importer who is able to access the metadata at the time of import.

In our case, we want our modules to provide us a type of the module, as a Clients Module, a Products Module, or on Orders Module.

Lazy<T> is a new type in the .NET 4 BCL that allows you to delay the creation of an instance. As MEF supports Lazy, you can import classes, but instantiate them later.

The custom attributes class defines the metadata.

C#
using System;

//MEF Reference
using System.ComponentModel.Composition;

using ModularWinApp.Core.Interfaces;

namespace ModularWinApp.Core
{
    [MetadataAttribute]
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
    public class ModuleAttibute : ExportAttribute, IModuleAttribute
    {
        public ModuleAttibute(string moduleName_)
            : base(typeof(IModule))
        {
            ModuleName = moduleName_;
        }

        public string ModuleName { get; private set; }
    }

}

The usage:

C#
....
namespace ModularWinApp.Modules.Clients
{
  [ModuleAttibute("Clients")]
  public class ClientsModule : IModule
  {
    //Create a new instance of the ModuleCommandDispatcher to store the commands
    ICommandDispatcher _commands = new ModuleCommandDispatcher();
    ....

Declaring ImportMany to use Lazy:

C#
[ImportMany(typeof(IModule), AllowRecomposition = true)]
public List<Lazy<IModule, IModuleAttribute>> ModuleList
{ get; set; } 

When we want to get a specific module, we use its metadata to identify it.

C#
//return a module instance based on it's name
public IModule GetModuleInstance(string moduleName_)
{
    IModule instance = null;
    foreach (var l in ModuleList)
    {
        if (l.Metadata.ModuleName == moduleName_)
        {
            instance = l.Value;
            break;
        }
    }
    return instance;
}

The ModuleHandler

The ModuleHandler class is the main class of the core. It is responsible for loading modules, exposing the list of loaded modules, exposing the list of loaded menus, and exposing the host to the modules. This class also provides methods to check and retrieve the instance of each loaded module.

C#
//The MEF Export attribute is used to export the class to the other modules
[Export(typeof(IModuleHandler))]
public class ModuleHandler : IDisposable, IModuleHandler
{
    //static variable to be sure that only on instance will exist
    private static IDataModule _dataModule;
 

    //The ImportMany attribute allows us to import many instances of classes that 
    //implements the IModule interface
    // The AllowRecomposition makes it possible
    // to update the module list during runtime
    [ImportMany(typeof(IModule), AllowRecomposition = true)]
    // The ModuleList will be filled with the imported modules
    public List<Lazy<IModule, IModuleAttribute>> ModuleList
    { get; set; }
 
    [ImportMany(typeof(IMenu), AllowRecomposition = true)]
    // The MenuList will be filled with the imported Menus
    public List<Lazy<IMenu, IModuleAttribute>> MenuList
    { get; set; }
 
    [Import(typeof(IHost))]
    // The imported host form
    public IHost Host
    { get; set; }
 
    //Expose the DataAccess module
    public IDataModule DataModule
    { 
      get 
      {
        if (_dataModule == null)
          _dataModule = new SqlDataModule();
        return _dataModule; 
      } 
    }
 
    // AggregateCatalog stores the MEF Catalogs
    AggregateCatalog catalog = new AggregateCatalog();
 
    public void InitializeModules()
    {
      // Create a new instance of ModuleList
      ModuleList = new List<Lazy<IModule, IModuleAttribute>>();
      // Create a new instance of MenuList
      MenuList = new List<Lazy<IMenu, IModuleAttribute>>();
 
      // Foreach path in the main app App.Config
      foreach (var s in ConfigurationManager.AppSettings.AllKeys)
      {
        if (s.StartsWith("Path"))
        {
          // Create a new DirectoryCatalog with the path loaded from the App.Config
          catalog.Catalogs.Add(new DirectoryCatalog(
                                       ConfigurationManager.AppSettings[s], "*.dll"));
        }
      }
      // Create a new catalog from the main app, to get the Host
      catalog.Catalogs.Add(new AssemblyCatalog(
                                       System.Reflection.Assembly.GetCallingAssembly()));
      // Create a new catalog from the ModularWinApp.Core
      catalog.Catalogs.Add(new DirectoryCatalog(
            System.IO.Path.GetDirectoryName(
                   System.Reflection.Assembly.GetExecutingAssembly().Location), "*.dll"));
 
      // Create the CompositionContainer
      CompositionContainer cc = new CompositionContainer(catalog);

      // Do the MEF Magic
      cc.ComposeParts(this);
    }
 
    //Verify if a specific module is imported
    public bool ContainsModule(string moduleName_)
    {
      bool ret = false;
      foreach (var l in ModuleList)
      {
        if (l.Metadata.ModuleName == moduleName_)
        {
          ret = true;
          break;
        }
      }
      return ret;
    }
 
    //return a module instance based on it's name
    public IModule GetModuleInstance(string moduleName_)
    {
      IModule instance = null;
      foreach (var l in ModuleList)
      {
        if (l.Metadata.ModuleName == moduleName_)
        {
          instance = l.Value;
          break;
        }
      }
      return instance;
    }
 
    public void Dispose()
    {
      _dataModule = null;
      catalog.Dispose();
      catalog = null;
      ModuleList.Clear();
      ModuleList = null;
    }
  }
}

The Host

The Host is the main form; in this case, it is an MDI parent. In this form, there is a MainMenu where the modules will insert the menus. The module can decide whether the form it presents is an MDI child or not.

The application entry point is in the Program static class. This static class will keep a single instance of ModuleHandler. Before calling the main form, the InitializeModules method of the ModuleHandler is called to load the modules, the core, and the Host.

The Program.cs class

C#
namespace ModularWinApp
{
  static class Program
  {
    //Create a new instance of ModuleHandler. Only one must exist.
    public static ModuleHandler _modHandler = new ModuleHandler();
 
    [STAThread]
    static void Main()
    {
      ...
      //Initialize the modules. Now the modules will be loaded.
      _modHandler.InitializeModules();
      //Create a new database connection that will be used by the modules.
            _modHandler.DataModule.CreateSharedConnection(
                ConfigurationManager.ConnectionStrings[
                "dbOrdersSampleConnectionString"].ConnectionString);
 
      //Start the Host Form
      Application.Run(_modHandler.Host as Form);
    }
  }
}

The Host Form

C#
namespace ModularWinApp
{
  //Exports the Host, so it can be imported into the Modules
  [Export(typeof(IHost))]
  public partial class frmHost : Form, IHost
  {
    public frmHost()
    {
      InitializeComponent();
    }
 
    private void frmHost_Load(object sender, EventArgs e)
    {
      //Here, the exported menus will be attached to the main menu.
      foreach (var menu in Program._modHandler.MenuList)
      {
        this.menuStrip1.Items.Add(menu.Value.WinFormsMenu);
      }
    }
    ...
  }
}

The modules

The modules are designed to be independent of one another. Each module exposes two concrete classes, one that implements the IModule interface and another that implements the IMenu interface.

The class that implements the interface IModule serves as a Facade for other modules to have access to commands, while the class that implements the interface IMenu serves to expose the menu, which is inserted into the MainMenu Host.

Although a module can be different from another in terms of business rules, the structure must be equal for all modules.

The structure of a module

Each module must implement the following classes:

  • CommandFacade: This is a static class that will handle all the methods that are used by Menu and used by other modules;
  • Menu: This class will build the Windows Forms menu that will be exposed to the Host;
  • Module: This class will handle the module instance and the commands exposed by the module;
  • Service: This class will handle all the database operations for the module.

The code below is extracted from the Clients Module.

ModularWinApp.Modules.Clients.ClientsCommandFacade

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
 
using System.Windows.Forms;
 
using ModularWinApp.Core;
using ModularWinApp.Core.Interfaces;
 
namespace ModularWinApp.Modules.Clients
{
  public static class ClientsCommandFacade
  {
    public static IModuleHandler ModuleHandler { get; set; }
 
    //Method called by the Menu
    public static void MenuNovo(object sender, EventArgs e)
    {
      NewClient();
    }
 
    //Method called by the Menu
    public static void MenuConsultar(object sender, EventArgs e)
    {
      ViewClient();
    }
 
    public static bool NewClient()
    {
      frmClient f = new frmClient();
      f.MdiParent = (Form)ModuleHandler.Host;
      f.Show();
      return true;
    }
 
    public static bool ViewClient()
    {
      frmClients f = new frmClients();
      f.MdiParent = (Form)ModuleHandler.Host;
      f.Show();
      return true;
    }
 
    public static DataSet SelectClients()
    {
      frmClients f = new frmClients();
      f.ShowDialog();
      DataSet l = f.SelectedClients;
      return l;
    }
 
    public static string GetClientName(int id_)
    {
      ClientsService _cs = new ClientsService();
      return _cs.GetClientName(id_);
    }
 
    public static DataTable GetClientList()
    {
      ClientsService _cs = new ClientsService();
      return _cs.GetClientsDataTable();
    }
  }
}

ModularWinApp.Modules.Clients.ClientsMenu

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
using System.Windows.Forms;
 
using ModularWinApp.Core.Interfaces;
using ModularWinApp.Core;
using System.ComponentModel.Composition;
 
namespace ModularWinApp.Modules.Clients
{
  [MenuAttibute("Clients")]
  public class ClientsMenu : IMenu
  {
    private ToolStripMenuItem ClientsMainMenu;
    private ToolStripMenuItem ClientsConsultarMenu;
    private ToolStripMenuItem ClientsNovoMenu;
 
    private ToolStripMenuItem CreateMenu()
    {
      this.ClientsMainMenu = new System.Windows.Forms.ToolStripMenuItem();
      this.ClientsConsultarMenu = new System.Windows.Forms.ToolStripMenuItem();
      this.ClientsNovoMenu = new System.Windows.Forms.ToolStripMenuItem();
 
      // 
      // MenuClientsMain
      // 
      this.ClientsMainMenu.DropDownItems.AddRange(
        new System.Windows.Forms.ToolStripItem[] {
          this.ClientsConsultarMenu,
          this.ClientsNovoMenu});
      this.ClientsMainMenu.Name = "MenuClientsMain";
      this.ClientsMainMenu.Text = "Clients";
      // 
      // MenuClientsConsultar
      // 
      this.ClientsConsultarMenu.Name = "MenuClientsConsultar";
      this.ClientsConsultarMenu.Text = "Consultar";
      this.ClientsConsultarMenu.Click += new EventHandler(
                                                    ClientsCommandFacade.MenuConsultar);
      // 
      // MenuClientsNovo
      // 
      this.ClientsNovoMenu.Name = "MenuClientsNovo";
      this.ClientsNovoMenu.Text = "Novo";
      this.ClientsNovoMenu.Click += new EventHandler(ClientsCommandFacade.MenuNovo);
 
      return ClientsMainMenu;
    }
 
    [ImportingConstructor()]
    public ClientsMenu([Import(typeof(IModuleHandler))] IModuleHandler moduleHandler_)
    {
      CreateMenu();
      ClientsCommandFacade.ModuleHandler = moduleHandler_;
    }
 
    public ToolStripMenuItem WinFormsMenu
    {
      get { return ClientsMainMenu; }
    }
  }
}

ModularWinApp.Modules.Clients.ClientsModule

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
 
using System.ComponentModel.Composition;
 
using ModularWinApp.Core;
using ModularWinApp.Core.Interfaces;
 
namespace ModularWinApp.Modules.Clients
{
  [ModuleAttibute("Clients")]
  public class ClientsModule : IModule
  {
    //Create a new instance
    //of the ModuleCommandDispatcher to store the commands
    ICommandDispatcher _commands = new ModuleCommandDispatcher();
 
    public string Name
    {
      get { return "Clients"; }
    }
 
    public ICommandDispatcher Commands
    {
      get { return _commands; }
    }
 
    public IModuleHandler ModuleHandler
    {
      get { return ClientsCommandFacade.ModuleHandler; }
    }
 
    //When the module instace is created,
    //the reference to the ModuleHandler is injected
    [ImportingConstructor()]
    public ClientsModule([Import(typeof(IModuleHandler))] 
           IModuleHandler moduleHandler_)
    {
      ClientsCommandFacade.ModuleHandler = moduleHandler_;
      RegisterCommands();
    }
 
    public void RegisterCommands()
    {
      _commands.Register("Clients.GetClientList", 
        new ModuleCommand<DataTable>(ClientsCommandFacade.GetClientList));
      
    }
  }
}

ModularWinApp.Modules.Clients.ClientsService

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.SqlClient;
 
using ModularWinApp.Core;
using ModularWinApp.Core.Interfaces;
 
namespace ModularWinApp.Modules.Clients
{
  public class ClientsService
  {
    IDataModule _dataModule;
 
    public ClientsService()
    {
      _dataModule = ClientsCommandFacade.ModuleHandler.DataModule;
    }
 
    public DataRow GetClient(int id_)
    {
      try
      {
        SqlConnection _conn = (SqlConnection)_dataModule.GetSharedConnection();
        SqlDataAdapter _da = new SqlDataAdapter(
                       "Select * from tb_Clients Where ID = " + 
                       id_.ToString(), _conn);

        DataSet _ds = new DataSet();
 
        _dataModule.OpenSharedConnection();
 
        _da.Fill(_ds);
        if (_ds.Tables.Count > 0 && _ds.Tables[0].Rows.Count > 0)
        {
          return _ds.Tables[0].Rows[0];
        }
        else
          return null;
      }
      catch (Exception ex)
      {
        throw;
      }
      finally
      {
        _dataModule.CloseSharedConnection();
      }
    }
 
    public string GetClientName(int id_)
    {
      try
      {
        SqlConnection _conn = (SqlConnection)_dataModule.GetSharedConnection();
        SqlDataAdapter _da = new SqlDataAdapter(
                          "Select * from tb_Clients Where ID = " + 
                          id_.ToString(), _conn);
        DataSet _ds = new DataSet();
 
        _dataModule.OpenSharedConnection();
 
        _da.Fill(_ds);
        if (_ds.Tables.Count > 0 && _ds.Tables[0].Rows.Count > 0)
        {
          return _ds.Tables[0].Rows[0]["Name"].ToString();
        }
        else
          return "";
      }
      catch (Exception ex)
      {
        throw;
      }
      finally
      {
        _dataModule.CloseSharedConnection();
      }
    }
 
    public DataTable GetClientsDataTable()
    {
      try
      {
        SqlConnection _conn = (SqlConnection)_dataModule.GetSharedConnection();
        SqlDataAdapter _da = new SqlDataAdapter(
          "Select * from tb_Clients ", _conn);
        DataSet _ds = new DataSet();
 
        _dataModule.OpenSharedConnection();
 
        _da.Fill(_ds);
        if (_ds.Tables.Count > 0)
          return _ds.Tables[0];
        else
          return null;
      }
      catch (Exception ex)
      {
        throw;
      }
      finally
      {
        _dataModule.CloseSharedConnection();
      }
    }
 
    public int Insert(string name_, DateTime dateOfBirth_, string fone_)
    {
      try
      {
        SqlConnection _conn = (SqlConnection)_dataModule.GetSharedConnection();
        SqlCommand _cmd = new SqlCommand();
        _cmd.CommandText = "INSERT INTO tb_Clients (ID, Name, DateOfBirth, Fone)" +  
                           " VALUES (@ID, @Name, @DateOfBirth, @Fone)";
 
        int newID = NewID();
 
        SqlParameter _paramID = new SqlParameter("@ID", newID);
        SqlParameter _paramName = new SqlParameter("@Name", name_);
        SqlParameter _paramDateOfBirth = 
          new SqlParameter("@DateOfBirth", dateOfBirth_);
        SqlParameter _paramFone = new SqlParameter("@Fone", fone_);
        _cmd.Parameters.AddRange(new SqlParameter[] { 
             _paramID, _paramName, _paramDateOfBirth, _paramFone });
        _cmd.Connection = _conn;
 
        _dataModule.OpenSharedConnection();
 
        _cmd.ExecuteNonQuery();
 
        return newID;
      }
      catch (Exception ex)
      {
        throw;
      }
      finally
      {
        _dataModule.CloseSharedConnection();
      }
    }
 
    private int NewID()
    {
      try
      {
        SqlConnection _conn = (SqlConnection)_dataModule.GetSharedConnection();
        SqlCommand _cmd = new SqlCommand();
        _cmd.CommandText = "SELECT IsNull(MAX(ID), 0) + 1 FROM tb_Clients";
        _cmd.Connection = _conn;
 
        _dataModule.OpenSharedConnection();
 
        return (int)_cmd.ExecuteScalar();
      }
      catch (Exception ex)
      {
        throw;
      }
      finally
      {
        _dataModule.CloseSharedConnection();
      }
    }
  }
}

Modules communication

Communication between modules is made by commands. Each command has a name and a delegate that points to a function. By default, the functions are on a CommandFacade within each module.

There are two types of commands, one that takes no parameters and one that takes parameters. The two types implement the same interface.

The commands are recorded and stored in the class CommandDispatcher that is responsible to fire commands.

Each module must expose a CommandDispatcher so that other modules can trigger the commands registered. Thus, it is possible to communicate between modules.

ModularWinApp.Core.ModuleCommand

There are two Commands classes. There are two types of commands, differentiated by their generic types.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ModularWinApp.Core.Interfaces;
 
namespace ModularWinApp.Core
{
  //A module command concrete class with parameter and output type
  public class ModuleCommand<InType, OutType> : ICommand
  {
    //This delegate will point to a function
    private Func<InType, OutType> _paramAction;
 
    //Constructor receiving the delegate as parameter
    public ModuleCommand(Func<InType, OutType> paramAction_)
    {
      _paramAction = paramAction_;
    }
 
    //Execute the delegate
    public OutType Execute(InType param_)
    {
      if (_paramAction != null)
        return _paramAction.Invoke(param_);
      else
        return default(OutType);
    }
  }
 
  //A module command concrete class with only output type
  public class ModuleCommand<OutType> : ICommand
  {
    //This delegate will point to a function
    private Func<OutType> _action;
 
    //Constructor receiving the delegate as parameter
    public ModuleCommand(Func<OutType> action_)
    {
      _action = action_;
    }
 
    //Execute the delegate
    public OutType Execute()
    {
      if (_action != null)
        return _action.Invoke();
      else
        return default(OutType);
    }
  }
}

ModularWinApp.Core.ModuleCommandDispatcher

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ModularWinApp.Core.Interfaces;
 
namespace ModularWinApp.Core
{
  public class ModuleCommandDispatcher : ICommandDispatcher
  {
    //A dictionary to store the module commands
    private Dictionary<string, ICommand> _commands = 
            new Dictionary<string, ICommand>();
 
    //Register the command into the _commands dictionary
    public void Register(string key_, ICommand command_)
    {
      _commands.Add(key_, command_);
    }
 
    //Execute the command without parameters
    public OutType Execute<OutType>(string key_)
    {
      if (CanExecute(key_))
      {
        ModuleCommand<OutType> _cmd = 
               (ModuleCommand<OutType>)_commands[key_];
        return _cmd.Execute();
      }
      else
        return default(OutType);
    }
 
    //Execute the command with parameters
    public OutType Execute<InType, OutType>(string key_, InType params_)
    {
      if (CanExecute(key_))
      {
        ModuleCommand<InType, OutType> _cmd = 
                    (ModuleCommand<InType, OutType>)_commands[key_];
        return _cmd.Execute(params_);
      }
      else
        return default(OutType);
    }
 
    //Verify if the command is registered
    public bool CanExecute(string key_)
    {
      return _commands.ContainsKey(key_);
    }
  }
}

Data access

Each module can implement data access regardless of the other modules, however, one requirement is that the modules use the same connection to the database system, so I created an interface and a concrete class that exposes the connection as well as the transaction that will be shared between modules.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
// MEF References
using System.ComponentModel.Composition;
//Core Reference
using ModularWinApp.Core.Interfaces;
 
namespace ModularWinApp.Core
{
  public class SqlDataModule: IDataModule, IDisposable
  {
    //Singleton Connection. Must be only one
    private static IDbConnection _sharedConnection;
    //Singleton Transaction. Must be only one
    private static IDbTransaction _sharedTransaction;
 
    //Create the connection that will be shared across the modules
    public void CreateSharedConnection(string connectionString_)
    {
      //If the connection instance is not created, create the instance
      if (_sharedConnection == null)
      {
        _sharedConnection = 
          new System.Data.SqlClient.SqlConnection(connectionString_);
      }
      else
      {
        throw new Exception("The connection is already created!");
      }
    }
 
    //Open the shared connection
    public void OpenSharedConnection()
    {
      if (_sharedConnection.State == ConnectionState.Closed)
        _sharedConnection.Open();
    }
 
    //Close the shared connection
    public void CloseSharedConnection()
    {
      if (_sharedConnection.State == ConnectionState.Open)
        _sharedConnection.Close();
    }
 
    //Begin a transaction on shared connection
    public void BeginTransaction()
    {
      if (_sharedTransaction == null)
        _sharedTransaction = _sharedConnection.BeginTransaction();
    }
 
    //Commit a transaction on shared connection
    public void CommitTransaction()
    {
      if (_sharedTransaction != null)
        _sharedTransaction.Commit();
 
      _sharedTransaction = null;
    }
 
    //Rollback a transaction on shared connection
    public void RollbackTransaction()
    {
      if (_sharedTransaction != null)
        _sharedTransaction.Rollback();
 
      _sharedTransaction = null;
    }
 
    //Return the shared connection
    public IDbConnection GetSharedConnection()
    {
      if (_sharedConnection != null)
        return _sharedConnection;
      else
        throw new Exception("The connection is not created!");
    }
 
    //Return the shared transaction
    public IDbTransaction GetSharedTransaction()
    {
      if (_sharedTransaction != null)
        return _sharedTransaction;
      else
        throw new Exception("The transaction is not created!");
    }
 
    public void Dispose()
    {
      if (_sharedTransaction != null)
      {
        _sharedTransaction.Dispose();
        _sharedTransaction = null;
      }
      if (_sharedConnection != null)
      {
        _sharedConnection.Dispose();
        _sharedConnection = null;
      }
 
    }
  }
}

Points of interest

Here is the MEF site on CodePlex: http://mef.codeplex.com/documentation.

And a very good article on MEF: http://www.codeproject.com/KB/aspnet/DOTNETMEF4_0.aspx.

License

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


Written By
Team Leader ActionPoint
Ireland Ireland
I'm Brazilian currently living in in Limerick, Ireland. I've been working with software development since 1996.

Comments and Discussions

 
QuestionMenu not created Pin
ilprincipe13-May-21 6:48
ilprincipe13-May-21 6:48 
Great articleThumbs Up | :thumbsup: Thumbs Up | :thumbsup: , but I have a problem with your code:
Program._modHandler.MenuList gives me an empty list, some indication, thanks so much!
Il principe Wink | ;)

Questionhow to add activity based authorization? Pin
Member 1226380927-Mar-18 3:26
Member 1226380927-Mar-18 3:26 
QuestionGeneric Repository pattern and unitofwork Pin
Member 1226380921-Mar-18 7:48
Member 1226380921-Mar-18 7:48 
QuestionExport a form from DLL Pin
RanjiniChandra6-Feb-18 22:25
RanjiniChandra6-Feb-18 22:25 
QuestionNice Article Pin
Shailesh vora20-Jul-17 5:29
Shailesh vora20-Jul-17 5:29 
AnswerRe: Nice Article Pin
fmsalmeida25-Jul-17 0:30
professionalfmsalmeida25-Jul-17 0:30 
QuestionEntity Framework Pin
martiza7-Jul-17 2:16
martiza7-Jul-17 2:16 
AnswerRe: Entity Framework Pin
fmsalmeida27-Jul-17 10:21
professionalfmsalmeida27-Jul-17 10:21 
Questioni build a application from your samples,thank you very much Pin
bird999917-Jan-17 21:05
professionalbird999917-Jan-17 21:05 
AnswerRe: i build a application from your samples,thank you very much Pin
fmsalmeida1-Mar-17 0:46
professionalfmsalmeida1-Mar-17 0:46 
QuestionStill up to date ? Pin
Mikgau8-Mar-16 5:53
Mikgau8-Mar-16 5:53 
AnswerRe: Still up to date ? Pin
fmsalmeida8-Mar-16 7:00
professionalfmsalmeida8-Mar-16 7:00 
GeneralRe: Still up to date ? Pin
Mikgau8-Mar-16 21:24
Mikgau8-Mar-16 21:24 
PraiseExcellent post ! Pin
JeanPaul MENSAH26-Dec-15 6:45
JeanPaul MENSAH26-Dec-15 6:45 
QuestionWindows Runtime Examples Please? Pin
Member 119265692-Sep-15 6:40
Member 119265692-Sep-15 6:40 
SuggestionNice Project! Pin
tencrocs3-Mar-15 14:59
tencrocs3-Mar-15 14:59 
QuestionHi Pin
Member 103800174-Dec-13 10:37
Member 103800174-Dec-13 10:37 
GeneralWOW - yes, 5 stars Pin
GarNet9-Feb-13 22:14
GarNet9-Feb-13 22:14 
GeneralRe: WOW - yes, 5 stars Pin
fmsalmeida12-Nov-14 3:39
professionalfmsalmeida12-Nov-14 3:39 
GeneralMy vote of 5 Pin
sachu_vidya11-Jul-12 0:29
sachu_vidya11-Jul-12 0:29 
QuestionVB.net version Pin
Ed_Lon4-Jul-12 22:49
Ed_Lon4-Jul-12 22:49 
QuestionRe: VB.net version Pin
Member 399444520-Sep-12 18:50
Member 399444520-Sep-12 18:50 
QuestionIncorrect tagging? Pin
WhitW13-May-12 17:26
WhitW13-May-12 17:26 
AnswerRe: Incorrect tagging? Pin
fmsalmeida14-May-12 3:11
professionalfmsalmeida14-May-12 3:11 
GeneralRe: Incorrect tagging? Pin
WhitW25-May-12 7:53
WhitW25-May-12 7:53 

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.