Designing Software with Generics and Reflection in C# ASP .NET

Introduction

As software becomes more complex, the ability to design a modular and
re-usable architecture into your software becomes increasingly
important. Using the programming concept of generics with interfaces,
included in C# ASP .NET 2.0, software developers can abstract
algorithms and classes into individual class libraries. The libraries
can be accessed, in a generic fashion, from the main software. The key
to this process relies on using interface design patterns and .NET
reflection.



What's a Generic?

.NET generics can cover several related topics. The common usage of
generics is specifying templated object names instead of concrete
ones, which lets you pass multiple types to a function rather than
only the concrete form. However, in the example listed below, we will
be focusing on using generics and reflection with a class library
interface architecture. This is becoming a popular method of
programming enterprise software, as it provides a great deal of
re-usable modules and even allows for spreading assemblies across
multiple servers, which means more speed.

A .generic software design is a way to refer to a class or library by
using an interface name that the class inherits from, rather than
using the class name itself. By decoupling the main code from the
details of the class, and using its interface instead, we can deal
with a set of different classes in the same way. To really show the
power of C# ASP .NET generics and reflection, an example is needed.
We'll use the famous Hello World example, but within a .NET
generics/reflection framework.

Classic Hello World Example

In the classic version of a Hello World application, we simply print
text to the console. If you wanted to display different versions of
the text, you would just write multiple sentences to the console or
create individual functions to handle them. Since printing the text
"Hello World" is really just an example of running an algorithm, let's
create 3 classes to handle printing the text for us. Each class needs
to do it in a slightly different way. This is similar in nature to how
you may need different classes to handle a certain task.

void main()
{
HelloWorldOne MyHelloWorldOne = new HelloWorldOne();
MyHelloWorldOne.
SayHelloWorldOne();

HelloWorldTwo MyHelloWorldTwo = new HelloWorldTwo();
MyHelloWorldTwo.SayIt();
}

public class HelloWorldOne()
{
public void SayHelloWorldOne()
{
Console.WriteLine("Hello World!");
}
}

public class HelloWorldTwo()
{
public void SayIt()
{
Console.WriteLine("Hello World Again!");
}
}

The example above shows a straight-forward approach to including two
classes to perform a similar task. The classes are provided inline,
within the same .cs file. We instantiate each class and call the
proper function to display the text to the console. Note that each
class has its own name for the function to print the text. While this
certainly works, it can be imporved upon with generics and reflection.

Doing the Hello World with Generics and Reflection

By using C# ASP .NET generics and reflection, we can completely
abstract the individual Hello World classes into separate class
libraries (DLL files). We can then use reflection to find any dlls in
the same folder as ours, pull out their Hello World functions, and
call them to write the text out. This is a powerful enterprise-level
change, because it not only creates re-usable libraries, but it also
allows us to offload the libraries to different servers, especially in
the case of designing the libraries as COM+ modules.

Taking the above example, our main program using a generics/reflection
architecture would look like the following:

void main()
{
foreach (IGenericHello aHelloWorldImpl in
GenericHelloWorlds.MyLoader.GetHelloWorlds())
{
aHelloWorldImpl.SayHello();
}
}

The above example looks very different from the classic version. This
is because the actual class libraries that contain the functions for
printing the text are contained within individual class libraries. An
important note is that those class libraries all inherit from the same
generic interface. Because of this, we can use .NET reflection to load
the DLLs, extract the interface, and call the member function
SayHello(). Each library will perform its task in its own way. You can
add, remove, and update libraries as you need, simply by swapping in
and out, a DLL into the folder. Let's examine the details of putting a
generic Hello World implementation together.

Preparing your .NET Visual Studio Project

Since we will actually be creating multiple class libraries, as well
as a main program, we will need to make multiple .NET projects within
a parent solution. You can start this by creating a new Console
project in Visual Studio called "GenericsDLLTest". Once the project is
created, create another new project by clicking File->New->Project.
Select to make a Class Library. Under the "Solution" option, choose
"Add to Solution", so the project is added to the same solution,
rather than a different folder. Name this new project
"GenericHelloWorld". The child project that you just created will
house the GenericHelloWorld loader module. This is the module that
finds all related dlls and extracts their desired classes.

At the end of this tutorial, you should have four projects includes in
the same solution, similar to the following:

Solution 'GenericsDLLTest'
+ GenericHelloWorld
+ GenericsDLLTest
+ HelloWorldBetter
+ HelloWorldPlain

It Always Starts With an Interface

We'll start off easy, with a basic interface. As simple as the
interface looks, it is the key to generics. We define a basic
IGenericHelloWorld interface which contains one function to print text
to the console. All class libraries that we create will inherit from
this interface and define their own version of the SayHello()
function. You can create this interface as a new class in the
GenericHelloWorld project and name the file IGenericHelloWorld.cs.

public interface IGenericHelloWorld
{
void SayHello(string strText);
}

It's important to note that we create this interface in the child
project created in the last step. This interface will reside in a
separate class library because we will need to add it as a reference
to each HelloWorld library that we create (and the main program). In
general, everything we make will need to know about the
IGenericHelloWorld, and will thus require a reference to its dll.

Loading DLLs Just Got Easy

We now create a class to handle loading the DLLs and returning a list
of their generic interfaces. Our main program will call the loader
class to retrieve a list of all HelloWorld libraries. As DLLs are
added and removed from the target folder, this class will return a
different list, and thus, perform different algorithms of your
choosing. For simplicity, there is only one static function called
GetHelloWorldImplementations() that we will call. This class will also
reside in the GenericHelloWorld project with a file named
GenericHelloWorldLoader.cs.

using System.Runtime.InteropServices;
using System.Runtime.Remoting;
using System.Reflection;
using System.IO;

public class GenericHelloWorldLoader
{
public static List GetHelloWorldImplementations()
{
List m_ResultList = new
List();

try
{
// Get the directory our exe is running from.
string strPath =
System.Reflection.Assembly.GetExecutingAssembly().Location;
strPath = strPath.Substring(0, strPath.LastIndexOf('\\'));

// All DLLs will be expected to be found in a
helloworlds folder.
strPath += @"\helloworlds";

// Search for all DLLs in the folder and try to
extract their interface.
foreach (string strFile in Directory.GetFiles(strPath, "*.dll"))
{
// Open the class library.
Assembly anAssembly = Assembly.LoadFrom(strFile);

// Try to extract our IGenericHelloWorld
interface, if it exists.
foreach (Type aType in anAssembly.GetTypes())
{
if
(aType.GetInterface(typeof(IGenericHelloWorld).FullName) != null)
{
IGenericHelloWorld
aGenericHelloWorldImplementation =
(IGenericHelloWorld)Activator.CreateInstance(aType);
if (aGenericHelloWorldImplementation != null)
{
// We found an interface, let's add it
to the list.

m_ResultList.Add(aGenericHelloWorldImplementation);
}
}
}
}
}
catch (Exception excep)
{
throw new Exception("Error, please verify a
\"helloworlds\" folder exists in the same folder as the executable.",
excep);
}

return m_ResultList;
}
}

As you can see in the above example, we look for a folder called
"helloworlds", located in the same directory as our main executable.
Within that folder, we search for all DLLs, attempt to open each one,
and then attempt to extract our generic interface IGenericHelloWorld.
If the DLL implements the interface, we add it to the list of items to
return and continue. In the end, our main program retrieves a list of
interfaces and can call the SayHello() function on each one to
activate each class. This is the power of .NET generics and reflection
at work.

Defining the Main Program

Now that we have created the generic DLL loader, we should create our
main program that utilizes the loader. This will go in the project
GenericsDLLTest. The main program is quite simple, as shown below:

using GenericHelloWorld;

class Program
{
static void Main(string[] args)
{
foreach (IGenericHelloWorld aHelloWorldImpl in
GenericHelloWorld.GenericHelloWorldLoader.GetHelloWorldImplementations())
{
aHelloWorldImpl.SayHello("Mr. Smith");
}

Console.ReadKey();
}
}

An important note is that back in your main program project, be sure
to right-click on your project, select "Add Reference", navigate to
the bin/Debug folder for your GenericHelloWorldLoader, and select
GenericHelloWorld.dll to add as a reference. You will also do this for
each class library that you create. Also remember to include a "using
GenericHelloWorld" at the top of your file to include the reference.

At this point, you should create a folder called "helloworlds" in the
same directory as your main executable. This is where you will place a
copy of each class library DLL file. The main program will search this
folder to locate modules to execute. Advanced Visual Studio users can
even setup post-build steps to automatically copy the compiled
libraries into the helloworlds folder. Although, manually copying the
files is good enough for this example.

Our First Generic Hello World Implementation

Now it's time to begin creating the individual generic Hello World
modules. Just as we did with the loader module, we'll create a new
project called "HelloWorldPlain" and add it to the current solution.
Be sure to add a reference to the GenericHelloWorld.dll in this child
project. In the Class1.cs file, we define the following class:

using GenericHelloWorld;

public class HelloWorldPlain : IGenericHelloWorld
{
#region IGenericHelloWorld Members

public void SayHello(string strText)
{
Console.WriteLine("Hello World!");
}

#endregion
}

Notice that this concrete implementation derives from our interface
and fills in the body for the SayHello() function. Since we are
creating a very simple class, we ignore the parameter strText
(although the interface includes it for other classes that may want to
utilize it).

Upon building the class, you should have a HelloWorldPlain.dll file in
the bin/Debug folder for the child project. You can now copy that DLL
into the main program's "helloworlds" folder. You can then select the
main program in visual studio and run it. The main program will locate
the HelloWorldPlain.dll and execute its SayHello() function,
displaying the following text to the console:

Hello World!

One is a Lonely Number

Here is where .NET modularization and generic design come in. Just as
we did in the first Hello World implementation, we will create another
child project, add it to the same solution, inherit from our
interface, and provide a body for the SayHello() function. Don't
forget to add a reference to GenericHelloWorld for this child project.

using GenericHelloWorld;

public class HelloWorldBetter : IGenericHelloWorld
{
#region IGenericHelloWorld Members

public void SayHello(string strText)
{
Console.WriteLine("Hello World, " + strText);
}

#endregion
}

The process is the same as the last one, except this time, we utilize
the strText parameter along with our usual text. After building the
library, copy the HelloWorldBetter.dll into the main program's
"helloworlds" folder. Select the main program in Visual Studio and run
it. You should now see two lines displayed on the console, even though
we have not changed a single line in the main program's executable:

Hello World!
Hello World, Mr. Smith

The loader locates both DLLs, extracts their interfaces, and the main
program executes each one.

The Good, The Bad, and The Complicated

Now that you can see what .NET generics can do, you may want to
utilize it throughout your software design. However, designing a
software architecture in this manner doesn't come without its
drawbacks. You should carefully consider when and when not to use
generics and reflection.

First, the good. The generics-reflection design lets you create
enterprise-style architectures, re-usable modules, neatly organized
libraries, and speed enhancements, particularly when creating COM+
libraries and spreading them across servers. The design also creates
an organized method for multiple developers to work on a single
project, by splitting the work up amongst individual libraries.

Next, the bad. Generics and reflection are complicated. It can add
unneccessary complexity to your software architecture, especially when
used in the wrong instances. It complicates the build process by
forcing the developer to manage multiple child projects, multiple
folders, and complicated post-build steps.

In short, consider the pros and cons of generic software patterns when
designing your application's architecure.



Conclusion

C# ASP .NET generic design patterns with reflection can enhance a
software architecture with re-usable modules, generic interfaces, and
speed enhancements. Generic modules allow swapping in and out
individual DLLs from an application's folder, instantly changing
algorithms the program uses, often without interrupting the program's
execution. By using generics and reflection in your own software, you
can bring enterprise-style patterns to your own projects.

About the Author

This article was written by Kory Becker, founder and chief developer
of Primary Objects, a software and web application development
company. You can contact Primary Objects regarding your software
development needs athttp://www.primaryobjects.com

Comments

Popular posts from this blog

Cloud Computing in simple

How to Write an Effective Design Document

Bookmark