Click here to Skip to main content
Click here to Skip to main content

Dynamically Loading .NET Assemblies using Interfaces and Reflection

, 27 Apr 2011
Rate this:
Please Sign up or sign in to vote.
This article describes why you may want to load an assembly at runtime and how to accomplish it.

Introduction

The .NET Framework provides the capability of loading assemblies at runtime without having to reference them directly within your project at design time. This makes it possible to extend or customize an application without having to re-compile the entire project for that one simple (or complex) change. This concept helps you design and create customizable off the shelf solutions (COTS) for your customers while also making your applications more scalable and stable.

Background

For instance, say you’re designing an e-commerce application and one of the features you wish to provide is a shipping rate calculator for over-night and second day delivery. The easiest option is to create a set of internal functions that handles these calculations, and then call the appropriate one based on the user’s selection. This will work fine at first, however what happens when a new option is required? As you can imagine, this would not scale very well and could eventually lead to some big headaches down the road.

To resolve this obstacle, you could instead package up all of the shipping rate logic into self contained assemblies and then load them at runtime as they are needed. When a new requirement comes along, you simply create a new project that fulfills that requirement, compile it into a new assembly, place that assembly into the project’s “/bin” folder and then reference it within your web.config file or by adding a new record into your database. This new assembly will then get loaded via reflection, thereby making it available for use within your application.

As you can imagine, there are a few design considerations that must be factored in when implementing this approach; however, it is not as difficult as it may sound. To begin, you will first need to identify all of the necessary methods and properties that your object will require. In the shipping example above, we will create an IShipping interface that will define our contracts from which all shipping objects will derive from. For simplicity, this interface will define a single CalculateShipping method that accepts an OrderHeader object as a single parameter, along with some other read-only properties that we will use for descriptions of each individual object. Next up, we will create a new class within its own class library project and then implement the IShipping interface. This class will then contain a CalculateShipping method that performs the custom logic required to calculate shipping based on the given OrderHeader object.

Using the Code

Sounds simple enough, let’s examine the code. First up, let’s review the OrderHeader object. As you can see, it’s a simple CLR object containing all properties and no methods. In turn, it references a list of OrderDetail objects which in turn contains a list of Products that are being ordered.

namespace Dynamic.Model
{
    public class OrderHeader
    {
        public int OrderHeaderId { get; set; }
        public DateTime OrderDate { get; set; }

        public int OrderNumber { get; set; }
        public string ShipToName { get; set; }
        public string ShipToStreet { get; set; }
        public string ShipToCity { get; set; }
        public string ShipToState { get; set; }
        public string ShipToZip { get; set; }

        public double OrderTotal { get; set; }
        public List<orderdetail> OrderDetails { get; set; }

        public OrderHeader()
        {
            this.OrderDetails = new List<orderdetail>();
        }
    }

    public class OrderDetail
    {
        public int OrderDetailId { get; set; }

        public Product Product { get; set; }
        public int Quantity { get; set; }
    }

    public class Product
    {
        public int ProductId { get; set; }
        public string Sku { get; set; }
        public string ProductName { get; set; }
        public string ProductDescription { get; set; }
        public double Weight { get; set; }
        public double Price { get; set; }
    }
}

Next up is our IShipping interface. Note how there is no code described here as it is strictly used to define a contract from which all other objects will implement.

namespace Dynamic.Model
{
    public interface IShipping
    {
        double CalculateShipping(OrderHeader orderHeader);

        string ShippingType { get; }
        string Description { get; }
    }
}

Lastly, we have a shipping object that performs a simple calculation and returns a value. This object can reside within its own separate assembly, however you will need to reference the assembly containing the IShipping interface so that it can be properly implemented and used by your library.

namespace Overnight
{
    public class Shipping : Dynamic.Model.IShipping
    {
        public double CalculateShipping(OrderHeader orderHeader)
        {
            // Our shipping is simply 5 percent of the current order
            return orderHeader.OrderTotal * .05;
        }

        public string ShippingType { get { return "Over night shipping rate"; } }
        public string Description { get { 
        return "This class calculates shipping to be five percent of the order total"; } }
    }
}

Since we defined an interface and we are loading our shipping assemblies dynamically at runtime, we are not limited to a single shipping solution. Below is another example of a slightly more complex calculator that determines the shipping rate based on the total weight of all products within the incoming order.

namespace SecondDayShipping
{
    public class Shipping : Dynamic.Model.IShipping
    {
        public double CalculateShipping(OrderHeader orderHeader)
        {
            double totalWeight = 0;
            double shippingRate = 0;

            // Do some extra logic here to find out how much our order weighs
            foreach (OrderDetail detail in orderHeader.OrderDetails)
            {
                totalWeight += detail.Product.Weight * detail.Quantity;
            }

            // Different rates for different weights
            if (totalWeight > 100)
                shippingRate = 20;
            else if (shippingRate > 50)
                shippingRate = 10;
            else
                shippingRate = 5;

            return shippingRate;
        }

        public string ShippingType { get { return "Second day shipping rate"; } }
        public string Description { get 
	{ return "This class calculates shipping for Second Day rates"; } }
    }
}

Now that we have our interfaces and shipping objects set up and defined, we have to program our application to actually use them. A less dynamic approach would be to create an instance of a shipping object using an early binding approach such as the example below, however this is exactly what we are trying to avoid.

function Main()
{
    // This example shows how an Overnight shipping object is created
    // using early binding. This method will work,
    //  however it is not very flexible for future updates
    Overnight.Shipping shipping = new Overnight.Shipping();
    double overNightRate = shipping.CalculateShipping(orderHeader);
}

Ideally, we will instead use reflection to load an assembly given our IShipping contract, and then instantiate that object so that we can call its CalculateShipping method. For this approach to work, we must first have access to the compiled assembly by either placing it within the GAC or the application's /bin folder. Second, we must then pass in the fully qualified name of the resource that we are trying to instantiate using the following format: “Namespace.Classname, AssemblyName”. Note that this is case sensitive and if you are unsure what your assembly name is, click on Project – Properties – Application and the name will be listed under Assembly name.

function Main()
{
    // This example shows how to create a SecondDay shipping object via reflection
    // First, we must get a reference to the proper assembly.
    // To do so, we pass in the name of the fully qualified class name
    // and the name of the assembly where the class is located
    IShipping secondDay = this.CreateShippingInstance(
       "SecondDayShipping.Shipping,SecondDayShipping");
    // Once we have an instance of this, we can call the method
    // defined by our IShipping interface to return the calculated price
    double secondDayRate = secondDay.CalculateShipping(orderHeader);
}

public IShipping CreateShippingInstance(string assemblyInfo)
{
    Type assemblyType = Type.GetType(assemblyInfo);
    if (assemblyType != null)
    {
        Type[] argTypes = new Type[] { };

        ConstructorInfo cInfo = assemblyType.GetConstructor(argTypes);

        IShipping shippingClass = (IShipping)cInfo.Invoke(null);

        return shippingClass;
    }
    else
    {
        // Error checking is needed to help catch instances where
        throw new NotImplementedException();
    }
}

Points of Interest

The included project contains all of the code required to see this in action, however, my example does take a slight shortcut when I call CreateShippingInstance and pass in a hardcoded string within the Main method. Ideally, this method would pull the assembly information from a database or some other configuration file so that they can be added, removed or updated without having to re-compile the entire project. How you do that is up to you and may be different depending upon your own unique needs and requirements.

With a properly designed system, updates and enhancements become a breeze and your applications will be much more scalable, stable and flexible as a result.

History

  • 27th April, 2011: Initial post

License

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

About the Author

Scott Bentley
Software Developer (Senior) Spectrum Medical
United States United States
No Biography provided

Comments and Discussions

 
GeneralMy vote of 5 PinmemberAndy41120-Jan-12 0:38 
GeneralNice job - vote of 5 Pinmemberdrobbins29-Apr-11 5:10 
GeneralMy vote of 5 Pinmemberdrobbins29-Apr-11 5:09 
GeneralMy vote of 5 PinmemberMonjurul Habib28-Apr-11 7:14 
GeneralMEF PinmemberSelvin28-Apr-11 0:19 
GeneralInstead of GetConstructor... PinmemberDerek Viljoen27-Apr-11 19:02 
GeneralRe: Instead of GetConstructor... PinmemberScott Bentley28-Apr-11 1:47 

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

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

| Advertise | Privacy | Mobile
Web02 | 2.8.140709.1 | Last Updated 27 Apr 2011
Article Copyright 2011 by Scott Bentley
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid