November 9, 2008 by
ryan
UPDATE: See here
In the near future, I have a web project coming out that I've been working on for the past month or so. I'm not going to give too much detail on that just yet, however, in working on this project, I had to come up with a way for users to have profile images. I would rather not host these images on my server, I decided to leverage Gravatar (Globally Recognized Avatars).
There are a couple pre-existing options for dealing with gravatar in the .NET framework, however, I'm using the MVC framework -- standard ASP.NET controls are out of the question. After a brief look on the Gravatar site (specifically the page dealing with how the URL is constructed), it seemed like this would be a pretty easy task to write a class that I could use to get gravatar image paths. Below is the main part of the class I created to retrieve gravatar information based on an e-mail address:
public static string GetGravatarURL(string email, string size)
{
return (string.Format("http://www.gravatar.com/avatar/{0}?s={1}&r=PG",
EncryptMD5(email), size));
}
public static string GetGravatarURL(string email, string size, string defaultImagePath)
{
return GetGravatarURL(email, size) + string.Format("&default={0}", defaultImagePath);
}
private static string EncryptMD5(string Value)
{
System.Security.Cryptography.MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
byte[] valueArray = System.Text.Encoding.ASCII.GetBytes(Value);
valueArray = md5.ComputeHash(valueArray);
string encrypted = "";
for (int i = 0; i < valueArray.Length; i++)
encrypted += valueArray[i].ToString("x2").ToLower();
return encrypted;
}
All we're really doing is creating a URL based on the Gravatar path, an MD5 encrypted version of an e-mail address and some user specified parameters. For the methods, Size is how many pixels the image should be (up to 500) and defaultImagePath is the url to use as an alternate image if the user does not have a Gravatar image. There is a rating parameter also but for my site, I'm setting it to PG always.
This is not all that difficult but hopefully useful. I plan to continue writing examples as I make progress on the web application I mentioned.
June 5, 2008 by
ryan
This may be common knowledge but it was new to me. If you're ever hand mangling control position in a winforms designer you can setup keybindings for Bring to Front Send to Back options that are normally available on the controls context menu. This is really useful if you have layers of controls and you can't always get to the Context.
- Click on Tools -> Options
- Under Environment, Select the Keyboard menu
- Type "Format.BringtoFront" (or "Format.SendtoBack") in the "Show Commands Containing" box
- Choose your shortcut keys
- Press Assign
Thanks to my friend Ross for pointing this out.
June 3, 2008 by
ryan
Settings
Up until now, we've been using NHibernate Query Generator for all of our data access. Although this is a great way to retrieve our data, there is another option we can play around with -- LINQ for NHibernate. To set this up in our existing application (see Part 1, Part 2, Part 3 and Part 4 on creating the ASP.NET MVC Application) we'll first need to grab the code out of subversion https://rhino-tools.svn.sourceforge.net/svnroot/rhino-tools/experiments/NHibernate.Linq/ and build it using MSBuild or Visual Studio. After that we want to add a reference to it in our application.
Simple Code
Next we'll want to update our controller to use Linq for NHibernate instead of NHQG (Service layer would be better place for this type of code but since this is a demo it'll be okay -- for more on using a service layer to handle all the repository code check out Michael Hanney's post on ActiveRecord, NHibernate and ASP.NET MVC). The initial NHGQ code is:
var p = Repository<Product>.FindOne(Where.Product.Title == ID);
Our LINQ for NHibernate query will look like this:
var p = (from item in UnitOfWork.CurrentSession.Linq<Product>()
where item.Title == ID
select item).First();
It's pretty obvious that the Linq code is a bit longer than the NHQG code. Although that in itself is not a bad thing, it may turn some people away. Momentarily, we'll see some scenarios where Linq for NH is very useful.
Paging and Sorting
One nice thing we can easily do with Linq for NHibernate is page and sort our data. If we simply want to get a list of all products it would look like this.
var p = (from item in UnitOfWork.CurrentSession.Linq<Product>()
select item
).ToList()
To page/sort the data it's just a slight addition to the list all code.
int itemsPerPage = 5;
int startIndex = (ID.Value - 1)* itemsPerPage;
var p = (from item in UnitOfWork.CurrentSession.Linq<Product>()
orderby item.Title ascending
select item
).Skip(startIndex).Take(itemsPerPage).ToList();
More Advanced Usage
Kyle Baley's article on Linq for Nhibernate shows a more interesting use for Linq for NHibernate; we can create a generic method that adds query criteria on the fly. This would make our code much more reusable so we're going to go ahead and make a demo class heavily based on these concepts.
public class QueryHandler<T>
{
private IList<linqExpression.Expression<Func<T, bool>>> _criteria;
public QueryHandler()
{
_criteria = new List<linqExpression.Expression<Func<T, bool>>>();
}
public void AddCriteria(linqExpression.Expression<Func<T, bool>> LambdaFunc)
{
_criteria.Add(LambdaFunc);
}
public IList<T> GetList()
{
var query = from item in UnitOfWork.CurrentSession.Linq<T>()
select item;
//Tack on our query Criteria
foreach (var criterion in _criteria)
{
query = query.Where<T>(criterion);
}
return query.ToList();
}
}
Here, we've created a class that has a private list of criteria, a method to add criteria to the list and a method to get the list based on the given criteria. I realize it may be a little intimidating but we can perfom most of our select queries through this method due to the use of Generics.
Updating the controllers to use this functionality is not too difficult. For pages that simply retrieve lists we call the GetList method without specifying any criteria:
var queryHandler = new QueryHandler<Product>();
var p = queryHandler.GetList().Skip(startIndex).Take(itemsPerPage).ToList();
Pass in new lambda expressions to add query criteria
var queryHandler = new QueryHandler<Product>();
queryHandler.AddCriteria(item => item.Title == ID);
var p = queryHandler.GetList().First();
Now we see there are multiple options for interacting with our ActiveRecord Repository. Please let me know of any changes that you would make. I've updated the demo code in Assembla -- http://svn2.assembla.com/svn/NHibernateTest - Standard disclaimer does apply (some of the code is less than ideal but for learning it should be okay).
May 26, 2008 by
ryan
Finally we're to the point where we can see all our hard work come together. We have most of the hard work done but we still have a lot of ground to cover. If you haven't been following along, please check out Setting Up The Assemblies, Configuring the Application and Developing the Model.
Unit of Work
In some of my initial tests with NHibernate and ASP.NET MVC Pattern I kept seeing the benefits of having a Unit of Work or Session Per Request (that is opening and closing the NHibernate session at the begining and end of the http request respectively). To Recap a little, I started to write my own Session Per Request, however, Chad Myers pointed me to the Rhino Commons project which already implemented this. I think it's worthwhile becuase I don't really like putting NHibernate session code in my controller plus as Martin Fowler writes
A Unit of Work keeps track of everything you do during a business transaction that can affect the database. When you're done, it figures out everything that needs to be done to alter the database as a result of your work.
Luckily, with RhinoCommons, it's pretty easy to implement this pattern (check out Ayende's post on this). In a standard web forms application, we would normally create a Global.aspx that inherits UnitOfWorkApplication. Since we're using ASP.NET MVC, however, we don't necessarily want to go that route. As Michael Hanney notes on his post on MVC and Castle we can have our Global.asax inherit from UnitOfWorkApplication.
public class GlobalApplication : UnitOfWorkApplication
{
public override void Application_Start(object sender, EventArgs e)
{
base.Application_Start(sender, e);
RegisterRoutes(RouteTable.Routes);
}
...
}
If you know of another way to do this, please be sure to let me know. Also, the routing still works as it normally would -- we're just running this code first to instantiate the UnitOfWork.
In our controllers we can call our reference our Hibernate repositories and classes without specifying an ISession.
public ActionResult InsertProductGroup(string Title)
{
ProductGroup pg = new ProductGroup();
pg.Title = Title;
Repository<ProductGroup>.Save(pg);
UnitOfWork.Current.Flush();
return RenderView("AddProductGroup");
}
Notice we're still flushing our data -- but it makes the controllers a LOT cleaner. Imagine if we had to instantiate and clean up our session in each controller.
One further thing, the UnitOfWorkApplication supports both short and long conversations. I'm not going to go too much into that but if your application requires keeping objects around for a couple HTTP Requests before saving to the database Long Conversation may be the way to go. See Ayende's Wiki for more on this
NHibernate Query Generator
In the first post of this series we looked at what it takes to setup NHibernate Query Generator (NHQG from here out). Now we get to use it to make some really nice looking code (in a later post, however, we'll be using LINQ to NHibernate). If we've set up the tool as mentioned in the first post (listed earlier), all we have to do is run the tool and make sure the generated code is added to the project.
NHQG lets us use a fluent interface to set filters on our Hibernate queries; this results in code that, in my opinion, is very easy to write and understand later on. If we wanted to Find one Product with a specific title, our code would look something like this:
public ActionResult ViewProduct(string ID)
{
var p = Repository<Product>.FindOne(Where.Product.Title == ID);
if (p != null)
{
return RenderView("DisplayProduct",
p);
}
return RenderView("DisplayProduct");
}
The Where.Product.Title == ID is all from the NHQG autogenerated code. Now we actually have something to show for all our configuration and setup work. Soon, we're going to take a look at using LINQ to NHibernate instead of NHQG. In the meantime, I have checked all the code in to my svn at Assembla. My standard disclaimer on demo code applies here too :) This is just demo code for the sake of example. Some of it is far from ideal but great for learning.
Check out / update with SubVersion from the following location: http://svn2.assembla.com/svn/NHibernateTest
May 22, 2008 by
ryan
We're going to keep moving now that everything is setup (see part one for setup) and configured (see part two for configuration).
First off we are going to create our classes. The original classes and SQL tables are posted below (these may look familiar -- they are from my initial NHibernate post)
Initial Product
public class Product
{
private IList<Product> _RelatedProducts;
private IList<ProductGroup> _ProductGroups;
public Product()
{
_RelatedProducts = new List<Product>();
_ProductGroups = new List<ProductGroup>();
}
public virtual string ID { get; private set; }
public virtual string Title { get; set; }
public virtual string ImagePath { get; set; }
public virtual string Description { get; set; }
public virtual IList<Product> RelatedProducts
{
get { return _RelatedProducts; }
set { _RelatedProducts = value; }
}
public virtual IList<ProductGroup> ProductGroups
{
get { return _ProductGroups; }
set { _ProductGroups = value; }
}
}
Initial ProductGroup
public class ProductGroup
{
public virtual string ProductGroupID { get; set; }
public virtual string Title { get; set; }
public virtual IList<Product> Products { get; set; }
}
SQL Tables
CREATE TABLE [dbo].[SimpleProducts](
[ProductID] [char](32) NOT NULL,
[Title] [nvarchar](50) NOT NULL,
[ImagePath] [nvarchar](300) NULL,
[Description] [nvarchar](500) NULL
)
CREATE TABLE [dbo].[RelatedProductsLookup](
[ProductID] [char](32) NOT NULL,
[RelatedProductID] [char](32) NOT NULL
)
CREATE TABLE [dbo].[ProductsProductGroupsLookup](
[ProductGroupID] [char](32) NULL,
[ProductID] [char](32) NULL
)
CREATE TABLE [dbo].[ProductGroups](
[ProductGroupID] [char](32) NOT NULL,
[Title] [nvarchar](50) NULL
ActiveRecord Classes
In a traditional NHibernate application, we would write usually our mapping files at this time(see my other NHibernate post for more on that). Since we're using the ActiveRecord pattern, however, we can specify all our mappings inline with the classes. It is important to note that this would not be a pure domain because we're placing our mappings inside the model. Warning if you're sensitive to using Attributes this may not be the code for you...
Our classes will now begin with an ActiveRecord attribute over the class; our properties will begin with Property/HasAndBelongsToMany/etc. Please note, for the sake of the example, I'm being extremely verbose with my attributes. If your table/column names match the class/property names, some of the additional info in the attribute is not necessary.
[ActiveRecord(Table="SimpleProducts")]
public class Product
{
private IList<Product> _RelatedProducts;
private IList<ProductGroup> _ProductGroups;
public Product()
{
_RelatedProducts = new List<Product>();
_ProductGroups = new List<ProductGroup>();
}
[PrimaryKey(Column="ProductID", Generator=Castle.ActiveRecord.PrimaryKeyType.UuidHex)]
public virtual string ID { get; private set; }
[Property(NotNull=true, Length=50, Column="Title")]
public virtual string Title {get; set; }
[Property(Length=300, NotNull=false, Column="ImagePath")]
public virtual string ImagePath { get; set; }
[Property(NotNull = false, Length = 500, Column="Description")]
public virtual string Description { get; set; }
[HasAndBelongsToMany(Table="RelatedProductsLookup", ColumnKey="ProductID", ColumnRef="RelatedProductID")]
public virtual IList<Product> RelatedProducts
{
get { return _RelatedProducts; }
set { _RelatedProducts = value; }
}
[HasAndBelongsToMany(Table="ProductsProductGroupsLookup", ColumnKey="ProductID", ColumnRef="ProductGroupID")]
public virtual IList<ProductGroup> ProductGroups
{
get { return _ProductGroups; }
set { _ProductGroups = value; }
}
}
[ActiveRecord(Table="ProductGroups")]
public class ProductGroup
{
[PrimaryKey(Column="ProductGroupID", Generator=Castle.ActiveRecord.PrimaryKeyType.UuidHex)]
public virtual string ProductGroupID { get; set; }
[Property(NotNull=true, Length=50, Column="Title")]
public virtual string Title { get; set; }
[HasAndBelongsToMany(Table="ProductsProductGroupsLookup", ColumnKey="ProductGroupID", ColumnRef="ProductID")]
public virtual IList<Product> Products { get; set; }
}
Repository Object
Another added benefit of using the Castle Active Record library is that we can use the Repository<T> for all of our object persistence. Instead of creating our own implementation of IRepository, we can write code like this to save / retrieve / update objects.
Selecting an object (our product IDs are HEX UUID's so this is not exactly accurate)
var p = Repository<Product>.Get(23);
Saving / Updating
Repository<Product>.Save(p);
The repository is pretty nice -- we can save all of our objects outside of the domain, which makes for a much cleaner design. Next time, we'll be looking at the extremely simple MVC application powered by this model and NHibernate Query Generator. Continue to Part 4
May 19, 2008 by
ryan
After my last post about the unit of work with NHibernate, Chad Myers mentioned that I should take a look at Ayende's Rhino Commons (because the Unit of Work stuff is already being handled). Since I am not a big fan of reinventing the wheel I decided I would give it a shot. There's going to be another post in the near future about how to get Rhino Commons, Castle ActiveRecord and ASP.NET MVC working together but for now, it would be good to make sure all the necessary components are installed on your machine.
- Make sure you have a subversion client -- Tortoise SVN or the command prompt is what I use but any subversion client should be fine.
- If you don't already have Nant installed on your machine download and install that
- Download and build the following (Ayende mentions, the trick is not opening in Visual Studio):
- Next you're going to want to setup the NHibernate Query Generator (we're going to use Linq to NHibernate in a later example but for now get this installed). This should be a part of the Rhino-tools package but if you want you can download the binaries. Then setup the application as an external tool in Visual Studio (my settings are posted below). see James Hollingworth's post for more info
- Command: C:\program files\nhqg\NHQG.exe
- Arguments: /Lang:cs /InputFilePattern:$(BinDir)/ProductModelActiveRecord.dll /OutputDirectory:$(ProjectDir)/Queries /BaseNamespace:Queries
- Initial Directory: $(TargetDir)
- Finally make sure you're running the preview 3 drop of the ASP.NET MVC Framework -- you can get that here from CodePlex
You should now have everything setup. It may be good to take a look at the Exesto application in the rhino-tools\SampleApplications directory to get an introduction to the Rhino-tools / binsor / castle settings that we'll be looking at later on. Finally, if you are not familiar with the ASP.NET MVC Framework take a look
Fredrik Normen's step by step guide. In the next couple of days, I will be posting how to wire these tools together for quick web application development. Stay Tuned.
May 11, 2008 by
ryan
A little earlier on, I had posted a simple example of NHibernate in an MVC application as well as some initial questions I had about NHibernate Session management. In response to my question, Matt Hinze mentioned that the session should be transparent to the controller and posted some links to various articles explaining how to achieve this. I finally had a chance to play around with this type of session management and spent most of my time looking through Billy McCafferty's NHibernate Best practices on CodeProject. As I've mentioned before, I'm very new to NHibernate so some of the things I'm doing may not be ideal.
HTTP Module
First off, I created an HTTP module; this is where the session will be opened and closed (by accessing the session manager class). You'll notice on the Init that event handlers have been added. I'm using the same session manager class that I was using in my last example, however, now it's being initialized / closed here (rather than in each controller action).
public class SessionModule : IHttpModule
{
private ISession _session;
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(OpenSession);
context.EndRequest += new EventHandler(CloseSession);
}
private void OpenSession(object sender, EventArgs e)
{
_session = SessionManager.GetCurrentSession();
}
private void CloseSession(object sender, EventArgs e)
{
_session.Flush();
_session.Close();
}
public void Dispose()
{
_session = null;
}
}
Next, I added this class to the HttpModules section of the web.config
<add name="SessionModule" type="ProductModel.Session.SessionModule"/>
Controller Code
Finally, since the NHibernate Session is being created / closed in the HTTP Module, I no longer have to use the using statement in every controller. Instead, I'm just setting a ISession = to the SessionManagers Current session.
public ActionResult AddProduct(string ID)
{
ISession session = SessionManager.GetCurrentSession();
return RenderView("AddProduct",
new SimpleProductRepository(session).List());
}
Wrapping Up
There's still a bit more I want to explore with this method of Session Management (maybe transactions). Also I would be interested to test out the threading / performance implications of going this route. I've updated the code on my assembla site. If you're interested, you can check it out here http://svn2.assembla.com/svn/NHibernateTest/ (keep in mind it's demo code -- not anything remotely resembling anything I would use in production). I would really appreciate any suggestions / feedback that you may have!
March 26, 2008 by
ryan
UPDATE: Nate Kohari (the author of
Ninject) posted some excellent information in the comments (enough info that it could probably be a post on its own).
Recently
Scott Hanselman compiled a
list of Dependency Injection frameworks for .NET. I
really didn't plan on trying anything new but
Ninject really jumped out
at me (honestly, it was more the reference to ninjas than anything).
After seeing a couple examples, I thought I would check it out in a bit
more detail.
Disclaimer: I've been playing
around with Ninject for all of about 3 hours now ... it's very possible
there's a better way to do some of this stuff :) So I would really
appreciate any feedback.
On to the code
Okay,
for this very contrived example we're going to be building car objects
out of just an engine and a drive type (extremely accurate I know). Just like in my StructureMap example, I'm going to start by creating
the interfaces followed by a default class that we're going to use as
our skeleton car ... the interfaces are pretty basic so no need to
spend too much time on them.
public interface IDriveType
{
string DriveType{ get;}
}
public interface IEngine
{
string EngineType { get; }
}
Both
of the interfaces have only one string property that will be used for
output. A little on the basic side but hey, we're looking at IoC not an
accurate car construction. Next we're going to add all our
implementations of the Engine
class FourCylinder : IEngine
{
public string EngineType
{
get { return "4-Cylinder"; }
}
}
class Rotary : IEngine
{
public string EngineType
{
get { return "Rotary"; }
}
}
class SixCylinder : IEngine
{
public string EngineType
{
get { return "6-Cylinder"; }
}
}
And the drive type implementations...
public class RWD : IDriveType
{
public string DriveType
{
get { return "Rear Wheel Drive"; }
}
}
class FourWD : IDriveType
{
public string DriveType
{
get { return "Four Wheel Drive"; }
}
}
class FWD : IDriveType
{
public string DriveType
{
get { return "Front wheel drive"; }
}
}
And finally the class we're going to use as our base car.
public class BaseAuto
{
private IDriveType _driveType;
private IEngine _engine;
public string DriveType
{
get { return _driveType.DriveType; }
}
public string Engine
{
get { return _engine.EngineType; }
}
[Inject]
public BaseAuto(IDriveType drive, IEngine engine)
{
_driveType = drive;
_engine = engine;
}
}
You'll
notice the
[Inject] attribute above our constructor. This is basically
telling Ninject to toss in an implementation of the IDriveType and
IEngine interfaces to this constructor (more on
Constructor Injection over here).
Now we're on to the
fun stuff. Ninject does not use XML to configure injections. Instead
we're going to use a class called Module to define all that. As the
documentation says, this class should implement IModule but thankfully
(at least for the sake of testing), there's a pre-defined base
implementation called StandardModule that we can extend.
public class BaseModule : StandardModule
{
public override void Load()
{
Bind<IEngine>().To<Rotary>();
Bind<IDriveType>().To<RWD>();
}
}
In
the module definition, we're basically saying when we request an object
from the Kernel (more on that in a sec.) we want the Rotary class in
place of IEngine and the RWD instead of IDriveType. Simple enough, now
lets take a look at the Kernel definition / initial code (I'm using
winforms for the sake of example but you can really go w/ whatever
project type you'd like). A lot of thought went into the naming of the form.
public partial class Form1 : Form
{
private BaseAuto _rx8;
public Form1()
{
InitializeComponent();
IKernel kernel = new StandardKernel(new BaseModule());
_rx8 = new BaseAuto(
kernel.Get<Auto.IDriveType>(),
kernel.Get<Auto.IEngine>()
);
MessageBox.Show("RX8: \n Drive Type: " + _rx8.DriveType +
"\n Engine:" + _rx8.Engine);
}
}
And when we run it...
Just
what we expected! Lets make things a little more interesting ... Say we wanted to add some other cars to our application? We probably
don't want everything to be a rear-wheel drive rotary (Unless you
really like RX-8's -- in that case you can
buy my RX8 *shameless plug*).
Anyways, we're going to accomplish this by changing up our Module a bit
to bind to a different IEngine / IDriveType implementation depending on
the
context.
public class BaseModule : StandardModule
{
public override void Load()
{
Bind<IEngine>().To<Rotary>()
.Only(When.Context.Variable("carType").EqualTo("RX8"));
Bind<IDriveType>().To<RWD>()
.Only(When.Context.Variable("carType").EqualTo("RX8"));
Bind<IEngine>().To<SixCylinder>()
.Only(When.Context.Variable("carType").EqualTo("Jetta"));
Bind<IDriveType>().To<FWD>()
.Only(When.Context.Variable("carType").EqualTo("Jetta"));
Bind<IEngine>().To<FourCylinder>()
.Only(When.Context.Variable("carType").EqualTo("WRX"));
Bind<IDriveType>().To<FourWD>()
.Only(When.Context.Variable("carType").EqualTo("WRX"));
}
}
Not
too bad right? I mean, just looking at the code we can pretty much tell
what's going on due to the Fluent Interface goodness. We can perform
more powerful comparisons on our context variable but for now, this
will work. Next we need to set up our context in the core part of our
application
(where we're instantiating and requesting classes from our Kernel).
public Form1()
{
InitializeComponent();
IKernel kernel = new StandardKernel(new BaseModule());
_rx8 = new BaseAuto(
kernel.Get<Auto.IDriveType>(
With.Parameters.ContextVariable("carType", "RX8")
),
kernel.Get<Auto.IEngine>(
With.Parameters.ContextVariable("carType", "RX8")
)
);
_jetta = new BaseAuto(
kernel.Get<Auto.IDriveType>(
With.Parameters.ContextVariable("carType", "Jetta")
),
kernel.Get<Auto.IEngine>(
With.Parameters.ContextVariable("carType", "Jetta")
)
);
_wrx = new BaseAuto(
kernel.Get<Auto.IDriveType>(
With.Parameters.ContextVariable("carType", "WRX")
),
kernel.Get<Auto.IEngine>(
With.Parameters.ContextVariable("carType", "WRX")
)
);
}
I've added some buttons so we don't get spammed with message boxes on
form load... if we fire this off and click on the various buttons, we
see we're getting the expected results!
All in all, I'm liking Ninject for more than just the name -- I can definitely
see myself using this in small to medium sized apps. Please let me know
what you think...
More info on Ninject:
February 26, 2008 by
ryan
There are a lot of resources on the web about dependency injection and using StructureMap, however, I wanted to write something that was an extremely simple example. This is basically the tip of the iceberg but hopefully it will help someone.
|
We want to make our application very loosely coupled -- to achieve this 'loose coupling' we're going to have several projects in the solution. What this means if we need to change any part of this application later on (we wouldn't want to in this case since its a demo and all), we could do so without impacting everything else. Anyways, we're going to create three class libraries and a WinForms application.
|
|
Next we want to create our main inteface -- this will be under the DisplayMessage Project:
namespace DisplayMessage
{
public interface IDisplayMessage
{
string message();
}
}
The interface defines just one method that, when implemented, will return a string stating what class its coming from. Next, we want to create our two implementation classes (one under Implementation1, the other under Implementation2). Please keep in mind I'm not suggesting to have every class in it's own library -- it's just for the sake of example :)
Implementation1:
public class MessageOne : IDisplayMessage
{
public string message()
{
return "This is a message from Implementation1";
}
}
Implementation2:
public class MessageTwo : IDisplayMessage
{
public string message()
{
return "This is a message from Implementation2";
}
}
Okay that was easy enough, now on to the Forms App. We're first going to add a reference to StructureMap and the project DisplayMessage and create a file called StructureMap.config -- this config file is going to define all of our assemblies. We want to make sure we edit the properties of this file and set the Copy to Output Directory option to "Copy Always." StructureMap will use this file at runtime to get our object references. The config file looks like this:
<?xml version="1.0" encoding="utf-8" ?>
<StructureMap>
<Assembly Name="DisplayMessage" />
<Assembly Name="Implementation1" />
<Assembly Name="Implementation2" />
<PluginFamily Type="DisplayMessage.IDisplayMessage"
Assembly="DisplayMessage"
DefaultKey="MessageOne">
<Plugin Type="Implementation1.MessageOne"
Assembly="Implementation1"
ConcreteKey="MessageOne" />
<Plugin Type="Implementation2.MessageTwo"
Assembly="Implementation2"
ConcreteKey="MessageTwo" />
</PluginFamily>
</StructureMap>
Notice we define a PluginFamily for the IDisplayMessage interface and set the default implementation to be MessageOne (the DefaultKey of PluginFamily references the ConcreteKey of the Plugin). Other than that, this should be pretty straight-forward but if you have any confusion, please check out the StructureMap documentation. Only a couple more things to do before we can run this...
|
Ok, we're going to add 3 buttons to our form -- one for the default IDisplayMessage and one for each implementation. |
Now to add the code...
//Default IDisplayMessage
private void btnDefault_Click(object sender, EventArgs e)
{
IDisplayMessage msg = StructureMap.ObjectFactory.GetInstance<IDisplayMessage>();
System.Windows.Forms.MessageBox.Show(msg.message());
}
//Implementation1
private void btnOne_Click(object sender, EventArgs e)
{
IDisplayMessage msg = StructureMap.ObjectFactory.GetNamedInstance<IDisplayMessage>("MessageOne");
System.Windows.Forms.MessageBox.Show(msg.message());
}
//Implementation2
private void btnTwo_Click(object sender, EventArgs e)
{
IDisplayMessage msg = StructureMap.ObjectFactory.GetNamedInstance<IDisplayMessage>("MessageTwo");
System.Windows.Forms.MessageBox.Show(msg.message());
}
Lets parse this up a little bit...
-
IDisplayMessage msg = StructureMap.ObjectFactory.GetInstance<IDisplayMessage>();
This statement gets the default IDisplayMessage object in the StructureMap.config file. Currently, it will get the same object as getting a named instance of "MessageOne"
-
IDisplayMessage msg = StructureMap.ObjectFactory.GetNamedInstance<IDisplayMessage>("MessageOne");
This statement gets the object associated with the ConcreteKey "MessageOne"
-
IDisplayMessage msg = StructureMap.ObjectFactory.GetNamedInstance<IDisplayMessage>("MessageTwo");
This statement gets the object associated with the ConcreteKey "MessageTwo"
Instead of simply hitting F5, we will need to build the application -- we want to copy the DLL files from Implementation1 and Implementation2 to the bin directory of the forms app and run the executable there. For testing, however, we can add a reference to both projects (this completely defeats the purpose of dependency injection so be sure to remove the references later on) or adjust the output directory of the implementation class libraries to be the same as the Form application's bin directory. Running the application shows that everything is working as expected.
For more information please check out the following links: