Click here to Skip to main content
15,878,852 members
Articles / Programming Languages / C#

Having fun with Griffin.Container

Rate me:
Please Sign up or sign in to vote.
5.00/5 (4 votes)
16 Aug 2012CPOL13 min read 32.9K   166   13   11
An inversion of control container with modules, decorators, commands, domain events and more.

Griffin.Container is a somewhat performant  Inversion of control container. It's goal is not to be the fastest container nor the one with the most feature rich API.

Instead I've tried to take a convenience over configuration approach. You'll probably have to design  your applications better instead of using a lot of container features Wink | <img src=. For more information about best practices read my previous IoC / Dependency injection article.  

The goal with this article is to show you the powers of Griffin.Container and it's hopefully easy API. A quick note on the samples zipfile: The samples are complimentary to the article. Both read the article and download the zip to get the most out of everything. 

Let's start with an easy hello world project. Create a new console project and run "install-package griffin.container" from the package manager console. Then add the following code: 

C#
public class Hello
{
	public void World()
	{
		Console.WriteLine("Hello world");
	}
}

public class Program
{
	public static void Main(string[] args)
	{
		// setup.
		var registrar = new ContainerRegistrar();
		registrar.RegisterConcrete<Hello>(Lifetime.Transient);
		var container = registrar.Build();
		
		var hello = container.Resolve<Hello>();
		hello.World();
		
		Console.WriteLine("Press enter to quit");
		Console.ReadLine();
	}
}

That's it.

The container will create an object of the type Hello when you invoke Resolve(). You will get a new instance each time you call resolve. That's what Transient means. Let's illustrate that:

C#
public class Hello
{
	private static int Counter;
	private int _myId;
	
	public Hello()
	{
		_myId = Counter++;
	}
	
	public void World()
	{
		Console.WriteLine("Hello world " + _myId);
	}
}

public class Program
{
	public static void Main(string[] args)
	{
		// setup.
		var registrar = new ContainerRegistrar();
		registrar.RegisterConcrete<Hello>(Lifetime.Transient);
		var container = registrar.Build();
		
		container.Resolve<Hello>().World();
		container.Resolve<Hello>().World();
		container.Resolve<Hello>().World();
		container.Resolve<Hello>().World();

		Console.WriteLine("Press enter to quit");
		Console.ReadLine();
	}
}

That will display:

Hello world 0
Hello world 1
Hello world 2
Hello world 3

Since we invoked Resolve() four times. If we want the same instance every time we can instead say that the "Hello" class is Singleton. That requires only one line of code change:

C#
registrar.RegisterConcrete<Hello>(Lifetime.Singleton);
Now run the code again and your see that "Hello world 0" will be repeated four times.

Limited lifetime / Scoping / Cleanup

Another important aspect of the container is to be able to dispose classes when they have been used. Let's change the Hello class so that it implements IDisposable.
C#
public class Hello : IDisposable
{
	// [...]
	
	public void Dispose()
	{
		Console.WriteLine("I'm being disposed like trash :(");
	}
}
The container uses something called scopes for this. A scope simply means that the container keeps track of all created objects that have a limited lifetime. The actual lifetime ends when you dispose the scope. A scope is used like this:
C#
using (var scope = container.CreateChildContainer())
{
	var hello = scope.Resolve<Hello>();
}
Nothing happens with the Hello objects since we have not marked the class as scoped. Let's do that:
C#
registrar.RegisterConcrete<Hello>(Lifetime.Scoped);
That's all. Full example:
C#
public class Hello : IDisposable
{
	private static int Counter;
	private int _myId;
	
	public Hello()
	{
		_myId = Counter++;
	}
	
	public void World()
	{
		Console.WriteLine("Hello world " + _myId);
	}
	
	public void Dispose()
	{
		Console.WriteLine("I'm being disposed like trash :(");
	}
}

public class Program
{
	public static void Main(string[] args)
	{
		// setup.
		var registrar = new ContainerRegistrar();
		registrar.RegisterConcrete<Hello>(Lifetime.Scoped);
		var container = registrar.Build();
		
		using (var scope = container.CreateChildContainer())
		{
			scope.Resolve<Hello>().World();
			scope.Resolve<Hello>().World();
			scope.Resolve<Hello>().World();
			scope.Resolve<Hello>().World();
		}
		
		Console.WriteLine("Press enter to quit");
		Console.ReadLine();
	}
}

You'll see that four objects will be created and disposed for you. Scoping is very useful for request/reply style of applications (for instance web applications or web services) since everything will be created and cleaned up automatically for you. No more need to manage state manually.

A design decision that I made with Griffin.Container is to never allow you to create scoped objects from the main container like this:

C#
public class Program
{
	public static void Main(string[] args)
	{
		// setup.
		var registrar = new ContainerRegistrar();
		registrar.RegisterConcrete<Hello>(Lifetime.Scoped);
		var container = registrar.Build();
		
		var hello = container.Resolve<Hello>();
	}
}
That example will throw an exception since you have configured Hello to be scoped but creating it in the main container which will effectivly make it a singleton. That's a dormant bug. The Hello class could have used a TCP connection or a database connection. Those may not be open for ever, hence the class would fail for all subsequent calls when the connection is dropped (since the connection state is not managed because the class was not designed to be long lived).

Working with interfaces

When starting using an IoC container you'll probably want to start using interfaces more frequent too. With a container you'll never have to care about more than the class contract (which usually is an interface). That is, you'll never have to know how or when you should create and dispose an object. The container takes care of that for you. Let's let Hello implement one more interface:
C#
// I print state, get it? muahahaha.
public interface IPrintState
{
	void PrintState(TextWriter writer);
}

public class Hello : IDisposable, IPrintState
{
	private static int Counter;
	private int _myId;

	public Hello()
	{
		_myId = Counter++;
	}

	public void World()
	{
		Console.WriteLine("Hello world " + _myId);
	}

	public void PrintState(TextWriter writer)
	{
		writer.WriteLine("Hello #" + _myId + " is alive and kicking!");
	}

	public void Dispose()
	{
		Console.WriteLine("I'm being disposed like trash :(");
	}
}

public class SomeOther : IPrintState
{
	public void PrintState(TextWriter writer)
	{
		writer.WriteLine("I do something else");
	}
}
The great thing with keeping interfaces small and well defined is that they are easy to reuse and implement. The following snippet is all which is required to print the state from all classes that  implements the interface:
C#
public class Program
{
	public static void Main(string[] args)
	{
		var registrar = new ContainerRegistrar();
		registrar.RegisterConcrete<Hello>(Lifetime.Transient);
		registrar.RegisterConcrete<SomeOther>(Lifetime.Transient);
		var container = registrar.Build();

		// Go through all classes that implements IPrintState
		var sb = new StringBuilder();
		var writer = new StringWriter(sb);
		foreach (var stateWriter in container.ResolveAll<IPrintState>())
		{
			stateWriter.PrintState(writer);
		}
		Console.WriteLine(sb.ToString());


		Console.WriteLine("Press enter to quit");
		Console.ReadLine();
	}
}
Do note that you do not have to do anyting special to register Hello as an implementation of IPrintState. The method registrar.RegisterConcrete<hello>()</hello> will register the class as all non .NET interfaces.

Dependency injection

The previous examples did only show you how you can create objects and control the object lifetimes. That's the inversion of control part. However, IoC containers are also useful for dependency injection. That is to identify and pass all dependencies to your classes.

This is usually done using constructor injection (which also is the only supported method in Griffin.Container). Constructor injection is useful since you directly can identify which dependencies a class has.

C#
public class UserService
{
	public UserService(IUserRepository repository)
	{
	}
}
That class tells us that the user service needs a data source to load/store changes that it makes to the users. The following snippet to the same thing:
C#
public class UserService
{
	public UserService()
	{
		_userRepository = new UserRepository("MyConnectionString");
	}
}

The difference is that the first example is not tied to a specific implementation (data source). We can also control the lifetime of the repository independently of the user service instances. We can for instance let all UserService instances share the same repository instance to be able to do caching inside it.

As for the registrations, you do not have to do anything special to take advantage of dependency injection. The container handles it automatically for you. Remember to prefer interfaces over classes as dependencies. And try to keep them as small as possible to make the code more flexible and easier to refactor. 

Best practice 

Depend on abstractions (interfaces) instead of concretes (classes). Your application get's easier to refactor. Try to divide your interfaces into smaller pieces. For instance the IUserRepository could be IUserQueries and IUserStorage. They could however still be implemented by the same class (but the queries could be moved, cached etc later on without having to modify the dependent code).

Registrations

The registration process is where all containers differs. I recommend that you do not register every single class manually (by invoking one of the registrar.RegisterXXX() methods). Sure. You should avoid change/refactor code (be SOLID). But in reality there are a very few of us which can write code so that no refactoring is required. Instead we do change our classes. Those changes will affect the registrations. The best way to keep them in sync is to manage the registrations (lifetime) from the classes themselves. 

Best Practice 

Use the [Component] attribute where it's possible. It's both visible in sandcastle generated documentation and makes it easier to refactor the application. Try to use the same lifetime for all classes if possible (i.e. do not specify it in the attribute but in the LoadComponents() line). 

[Component] attribute 

To make the Hello class a scoped you can just write:
C#
[Component(Lifetime.Scoped)]
public class HelloWorld
{
	// ....
}
And register it in the registrar with:
C#
registrar.RegisterComponents(Lifetime.Scoped, Assembly.GetExecutingAssembly());
Notice three things:
  1. RegisterComponents will register all classes in an assembly thas has been decorated with the [Component] attribute.
  2. We specify a lifetime in the [Component] attribute. That explicitly tells us that the class must have a specific lifetime.
  3. We specify an additional lifetime in the RegisterComponents() attribute that will be used for all classes which have not a lifetime specified in [Component].
Example:
C#
[Component(Lifetime = Lifetime.Scoped)]
public class Hello
{
	private static int Counter;
	private int _myId;

	public Hello()
	{
		_myId = Counter++;
	}

	public void World()
	{
		Console.WriteLine("Hello world " + _myId);
	}
}

[Component]
public class MyTransient
{
	private static int Counter;
	private int _myId;

	public MyTransient()
	{
		_myId = Counter++;
	}

	public void Print()
	{
		Console.WriteLine("Transient Id " + _myId);
	}
}

public class Program
{
	public static void Main(string[] args)
	{
		var registrar = new ContainerRegistrar();
		registrar.RegisterComponents(Lifetime.Transient, Assembly.GetExecutingAssembly());
		var container = registrar.Build();

		using (var scope = container.CreateChildContainer())
		{
			// should print the same id
			scope.Resolve<Hello>().World();
			scope.Resolve<Hello>().World();
			scope.Resolve<Hello>().World();

			// should print three different ids
			scope.Resolve<MyTransient>().Print();
			scope.Resolve<MyTransient>().Print();
			scope.Resolve<MyTransient>().Print();
		}

		Console.WriteLine("Press enter to quit");
		Console.ReadLine();
	}
}
The same registration line (the call to RegisterComponents()) should work for most of your own classes.

Custom registrations

Sometimes you need more finegraned control over the registration process. A nhibernate/EF4/RavenDB connection is a typical example. A registration for RavenDb would look like this:
C#
registrar.RegisterService<IDocumentSession>(container => _documentStore.OpenSession(), Lifetime.Scoped);
The registration interface in Griffin.Container looks like this:
C#
public interface IContainerRegistrar
{
	IEnumerable<ComponentRegistration> Registrations { get; }
	
	// Component attribute
	void RegisterComponents(Lifetime defaultLifetime, string path, string filePattern);
	void RegisterComponents(Lifetime defaultLifetime, params Assembly[] assemblies);
	
	// Modules, will come back to those
	void RegisterModules(string path, string filePattern);
	void RegisterModules(params Assembly[] assemblies);
	
	// Register classes (as themselves if no interfaces is implemented, or as all non-dotnet interfaces)
	void RegisterConcrete<TConcrete>(Lifetime lifetime) where TConcrete : class;
	void RegisterConcrete(Type concrete, Lifetime lifetime);

	// Register an interface using your own factory method
	void RegisterService<TService>(Func<IServiceLocator, TService> factory, Lifetime lifetime);
	void RegisterService(Type service, Func<IServiceLocator, object> factory, Lifetime lifetime);
	
	// Register using a specific 1-1 mapping
	void RegisterType<TService, TConcrete>(Lifetime lifetime) where TService : class where TConcrete : TService;
	void RegisterType(Type service, Type concrete, Lifetime lifetime);

	// Register a previously created instance (an object which already exists)
	void RegisterInstance<TService>(TService instance) where TService : class;
	void RegisterInstance(Type service, object concrete);
}
A link to the online documentation can be found at the bottom of this article.

RegisterComponents

Used to register all classes which has been tagged with the [Component] attribute. It can either scan one/more specified assemblies or load/scan assemblies in a specific folder.

RegisterModules

Modules are a great way to move the registration process from one single location into several and by doing so allow each module in your system to be in charge of it's own registrations. That makes your application easier to mantain and it gives you an better overview of what each module registers. I'll come back to modules later.

RegisterConcrete 

RegisterConcrete will register the class as itself in the container:
C#
public class MyClass
{
}

registrar.RegisterConcrete<MyClass>();
// [...]

container.Resolve<MyClass>();
or as implemented interfaces (if there are any and that they are not .NET framework interfaces).
C#
public class MyClass : IEnumerable<Item>, IMyService
{
}

registrar.RegisterConcrete<MyClass>();
// [...]

// works
container.Resolve<IMyService>();

// wont work
contianer.Resolve<IEnumerable<Item>>();

RegisterService

Register an service (typically an interface) using a custom factory method. Typically only used to register external dependencies (such as db libraries) in the container.

RegisterType

Register a specific service (interface) to a specific concrete (class). Typically only used to register external dependencies (such as db libraries) in the container.

RegisterInstance

Register a singleton which has already been created somewhere.

Modules

Griffin.Container allows you to use modules to move the registration responsibility from a single location to each module/package in your application. All you need is to create a new class and let it inherit IContainerModule.

Image 2 

C#
public class UserRegistrations : IContainerModule
{
	public void Register(IContainerRegistrar registrar)
	{
		registrar.RegisterConcrete<UserService>(Lifetime.Scoped);
		registrar.RegisterConcrete<UserCache>(Lifetime.Singleton);
		registrar.RegisterConcrete<UserRepository>(Lifetime.Scoped);
	}
}
Next you have to load all modules:
C#
public class Program
{
	public static void Main(string[] args)
	{
		var registrar = new ContainerRegistrar();
		
		// will load all modules from all assemblies which starts with "MyApp."
		registrar.RegisterModules(Environment.CurrentDirectory, "MyApp.*.dll");
		
		var container = registrar.Build();
	}
}

Plugin architectures becomes obsolete (unless you have to be able to disable/unload plugins) with this technique. Simply drop a new assembly in the app folder and restart the application.

Best practice

Use a composition root in every project, use it to call registrar.RegisterComponents() for the current assembly (i.e put a ContainerModule in the root namespace of the project). 

Domain events

Griffin.Container has built in support for domain events (handled within the same process). Domain events means that you send an event when something have happened. For instance UserCreated, AccountLocked, ReplyPosted etc. Those events enables you to create loosely coupled applications. You could for instance subscribe on the event ReplyPosted to be able to send out email notifcations (without having to change the class(es) that created/saved the reply).

The difference between the event model in .NET and the event model in Griffin.Container is that the latter is loosely coupled.  

Image 3

Subscribing

Subscribing is easy. Simply let any class implement IHandlerOf<T>:
C#
[Component]
public class ReplyEmailNotification : IHandlerOf<ReplyPosted>
{
	ISmtpClient _client;
	IUserQueries _userQueries;
	
	public ReplyEmailNotification(ISmtpClient client, IUserQueries userQueries)
	{
		_client = client;
		_userQueries = userQueries;
	}
	
	public void Invoke(ReplyPosted e)
	{
		var user = _userQueries.Get(e.PosterId);
		_client.Send(new MailMessage(user.Email, "bla bla"));
	}
} 

Dispatching

Domain events are dispatched using the DomainEvent class. The actual domain event can be any class, there are no restrictions. I do however recommend that you treat them as DTO's.
C#
public class UserCreated
{
	public UserCreated(string id, string displayName)
	{
	}
}

public class UserService
{
	public void Create(string displayName)
	{
		//create user
		// [...]
		
		// fire the event.
		DomainEvent.Publish(new UserCreated(user.Id, user.DisplayName));
	}
}

Best practice 

Avoid attaching domain objects/models to the events, but try to treat the domain events as immutable DTO's instead. It makes it a lot easier if you have to scale your application later on. You can always load the correct domain model through the repositories/queries when you receive the domain event. 

Commands 

Griffin.Container do also have a built in command pattern implementation. I've chosen to seperate the actual handling (ICommandHandler<T>) from the command class (ICommand). That enables us to handle the command anywhere. You can for instance start by handling all commands in the same server/process but later on route some commands to a different server. 

Always try to create one command per use case instead of creating small commands which you chain. Chained commands will most likely lead to a mess when the application grows.  

As I mention before we can scale the application by moving the command handling to different processes. To enable that we have to treat all commands as asynchronous. That means two things:

  1. A command can never return something.
  2. Don't expect the command to have been executed just because ICommandDispatcher.Dispatch() has returned.

Instead wait for the domain events and use those for the extra processing.

Image 4

Setup 

The command dispatcher do not have a default implementation. Instead we have to assign one. There is a built in one which is called ContainerDispatcher. As you might have guessed that it uses the IoC container to find the correct handler.
C#
CommandDispatcher.Assign(new ContainerDispatcher(myContainer));

However, since commands typically are executed from services, controllers etc I do recommend that you register the ICommandDispatcher implementation in the container and get it through dependency injection. (although the dispatcher could be considered to be an infrastructure component, up for discussion). 

A command 

A command is a regular .NET class which is used to transfer the command and it's arguments to a command handler. 

Example command:  

C#
public class CreateUser
{
	public CreateUser(string userName)
	{
		if (userName == null) throw new ArgumentNullException("userName");
		
		UserName = userName;
	}
	
	public string UserName { get; private set; }
	public string DisplayName { get; set; }
}
Notice that the userName is passed in the constructor (and has a private setter) while DisplayName can be set through the property. That indicates that the UserName is mandatory while the DisplayName is optional. Always try to follow this pattern since it makes easier to tell which information is required.

Handling a command

A command is handled by classes that implement the IHandlerOf<T> interface:
C#
public class CreateUserHandler : IHandlerOf<CreateUser>
{
	IUserRepository _repos;
	
	public CreateUserHandler(IUserRepository userRepository)
	{
		_repos = userRepository;
	}
	
	public void Invoke(CreateUser cmd)
	{
		var user = _repos.Create(cmd.UserName, cmd.DisplayName);
		
		DomainEvent.Dispatch(new UserCreated(user));
	}
}
In this example I used a repository, but it could have been vanilla ADO.NET or a webservice. It really doesn't matter since no code is dependent on the implementation.

Invoking a command

Simple:
C#
CommandDispatcher.Dispatch(new CreateUser("arne"));
I do recommend that you register the dispatcher in the container and take it as a dependency in your own classes. It makes the intent more clear (even though it can be considered to be a infrastructure dependency).
C#
public class SampleService
{
	ICommandDispatcher _dispatcher;
	
	public SampleService(ICommandDispatcher dispatcher)
	{
		if (dispatcher == null) throw new ArgumentNullException("dispatcher");
		
		_dispatcher = dispatcher;
	}
	
	public void ProcessStuff()
	{
		// [...]
		
		_dispatcher.Invoke(new StoreResult(someResult));
	}
}

public class Program
{
	public static void Main(string[] args)
	{
		var registrar = new ContainerRegistrar();
		registrar.RegisterConcrete<ContainerDispatcher>(Lifetime.Singleton);
		registrar.RegisterConcrete<SampleService>(Lifetime.Transient);
		var container = registrar.Build();
	}
}

Decorators

The decorator pattern allows us to "decorate" a class to give it more functionality. Griffin.Container let's you create decorators by implementing the interface IInstanceDecorator:
C#
public interface IInstanceDecorator
{
    void PreScan(IEnumerable<Type> concretes);
    void Decorate(DecoratorContext context);
}

It can be a bit tedious to do it on your own. You can therefore use the Griffin.Container.Interception nuget package for the container to make it a bit easier.

Let's log all method calls for every single class that we have registered in the container. Might sound hard, but it's quite simple:

Start by running install-package griffin.container.interception from the package manager console. Then enter the following code: 

C#
public class ConsoleLoggingDecorator : CastleDecorator
{
    public override void PreScan(IEnumerable<Type> concretes)
    {
    }

    protected override IInterceptor CreateInterceptor(DecoratorContext context)
    {
        return new LoggingInterceptor();
    }
}

public class LoggingInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        var args = string.Join(", ", invocation.Arguments.Select(x => x.ToString()));
        Console.WriteLine("{0}({1})", invocation.Method.Name, args);
        
        invocation.Proceed();
    }
}  

Do note that all intercepted classes must have virtual methods for it to work (limitation by the Castle.Proxy). 

Register the decorator in your container:

C#
container.AddDecorator(new ConsoleLoggingDecorator());
Let's try it with our repository:
C#
container.Resolve<IUserRepository>().Get(10);

The console prints:

Get(10) 

Be aware of that decorators hurt performance. But not as much as you would think (google performance benchmarks castle proxy).

Built in decorators  

There are one decorator built into the Interception package.   

Exception logger 

This decorator will intercept all classes and log all exceptions together with the method name and all argument values.

Sample output:

SampleApplication.Users.UserService.Get(10) threw an exception:
  (exception info would be here if this were not an article, which it is) 

The exception will of course still be thrown. It's up to you to handle it.  

(The decorator where just added to illustrate how you can create your own) 

Decorator summary

Combining command handlers and decorators can be really useful. You can for instance validate all commands using DataAnnotations with a decorator before the real handler is invoked. That's validation in one decorator instead of in every command handler. You could also create a command handler decorator which takes care of the database transaction.

Extending the container

I've avoided extension methods like the pest in the core since extension methods prevent extension through inheritance (which you all know is one of the foundations of OOP). The core itself has been divided into three interfaces.

Feel free to check the source code when reading this section, since it will make a lot more sense then. 

The first on is the IContainerRegistrar. It's purpose is to expose the registration interface used when register services and concretes in the container. That's it's only responsibility. Everything registered is exposed by the Registrations property.

The next interface is the IContainerBuilder. It's purpose is to analyze all registrations and create build plans (IBuildPlan) for each service. The build plan is used to create/access all classes that implements a service. To do that it stores an IInstanceStrategy for each concrete/class. The stategy decides when it's time to create a new instance and when it should fetch it from the storage.

The final part is the service location. The core interface is IServiceLocator which contains the basic resolution methods. It's inherited by IParentContainer which adds the CreateChildContainer() method and by the IChildContainer interface which also inherits IDisposable

Framework libraries 

There are also some framework libraries which you can use for your favorite framework. 

Feel free to contribute with more packages. 

Summary 

I hope that you have enjoyed this article and that you will try out the container. 

Do not forget to download the sample code since it takes you step-by-step through the features of griffin.container.

The code is also available at github: 

License

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


Written By
Founder 1TCompany AB
Sweden Sweden

Comments and Discussions

 
QuestionIs there any examples of the MVC4 parts? Pin
jobs7004-Nov-13 4:12
jobs7004-Nov-13 4:12 
AnswerRe: Is there any examples of the MVC4 parts? Pin
jgauffin9-Jan-14 9:04
jgauffin9-Jan-14 9:04 
GeneralMy vote of 5 Pin
Manfred Rudolf Bihy17-Aug-12 3:00
professionalManfred Rudolf Bihy17-Aug-12 3:00 
GeneralRe: My vote of 5 Pin
jgauffin17-Aug-12 4:31
jgauffin17-Aug-12 4:31 
QuestionThanks for introducing me to Griffin.Container! 5+ Pin
Manfred Rudolf Bihy16-Aug-12 23:29
professionalManfred Rudolf Bihy16-Aug-12 23:29 
Thanks for this article! I've looked at some IoC, DPI containers, but somehow this one escaped my attention.
I'm a bit wary though that all configuration in your examples was in code. I personal use Spring.NET since I actually started out with Java, so I already knew that container. What I like about Spring IoC,DPI is that you can write XML configuration files and thus build up a complete application at the time when an object is requested from the container. By adhering to the principle of programming against interfaces, it easy to exchange parts of your application by merely rewriting the XML configuration file. There's no need to recompile the complete application.
To make a long story short: "Is there a way to also have that kind of configuration per file in Griffin.Container?"

Cheers,

Manfred

"With sufficient thrust, pigs fly just fine."
Ross Callon, The Twelve Networking Truths, RFC1925


AnswerRe: Thanks for introducing me to Griffin.Container! 5+ Pin
jgauffin16-Aug-12 23:38
jgauffin16-Aug-12 23:38 
GeneralRe: Thanks for introducing me to Griffin.Container! 5+ Pin
Manfred Rudolf Bihy17-Aug-12 1:20
professionalManfred Rudolf Bihy17-Aug-12 1:20 
GeneralRe: Thanks for introducing me to Griffin.Container! 5+ Pin
jgauffin17-Aug-12 1:38
jgauffin17-Aug-12 1:38 
GeneralRe: Thanks for introducing me to Griffin.Container! 5+ Pin
Manfred Rudolf Bihy17-Aug-12 3:08
professionalManfred Rudolf Bihy17-Aug-12 3:08 
Questiondysfunctional link in your article ? Pin
BillWoodruff16-Aug-12 15:11
professionalBillWoodruff16-Aug-12 15:11 
AnswerRe: dysfunctional link in your article ? Pin
jgauffin16-Aug-12 18:19
jgauffin16-Aug-12 18:19 

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.