If you are just joining, you might want to start from the beginning: http://www.damonpayne.com/2008/09/14/RunTimeIsDesignTimeForAGT0.aspx
I’m about to steal some ideas that are in common use, ideas from PRISM, Castle Windsor, the Visual Studio design-time environment, and a thousand other things. I’ll briefly explain some of the core concepts here.
In a well designed Windows Forms application, one would have a proper separation of concerns. Suppose my form was for place orders for big purple couches. I would have a CouchOrderForm that implements ICouchOrderView with very little logic: populating values or potential values only, perhaps with Data Binding, and event handlers. These event handlers would delegate the actual work to a CouchOrderPresenter of some kind. CouchOrderPresenter would probably use to a CouchOrderDataAccess class or perhaps a CouchBussinessProcess class to accomplish the necessary tasks, and update the UI using a combination of data binding and events. I know people who would frown upon this as “primitive”, but even getting this far would be miles ahead of the average .NET project I saw as a consultant.
For testability, this design accomplishes a fair bit of concern-separating. I can test almost my entire program without messing around with “how do I test the logic that’s in the Form?” For example, I have often created a unit test class that implements the ICouchOrderView interface and does asserts in the various callback. This means that a database and whatever other “real” components are required must be present and that might be very Heavy for unit test time. If my code was written to use ICouchDataAccess instead of CouchOrderDataAccess, it’s suddenly much easier to mock out parts of my framework. I might also be able to supply different ICouchDataAccess implementations at run time if that makes sense for my program – though I think this need is often horribly misunderstood and over used by many advocates.
When I originally encountered this idea we called it the Service Locator Pattern in Java. The term “service” is a loaded word. I used to ask interview questions along the lines of “What is a Class? Now what is an object? Now what is a Component? Now what is a Service?” I would change terminology here to say “module” or “strategy”, but Service is the commonly known semantic of our times, so I’ll use service to talk about program chunks that are essentially managed components. My Component Container/Service Locator will be a ServiceManager. It might be useful to have some common behaviors for these components, so IService will be the base interface for components.
When I add these things to the IoC project, I notice that when I choose to Add à new item, “Interface” is not an option for Silverlight projects. Doesn’t MSFT want us to take Silverlight seriously?
The idea is that the ServiceManager will figure out what classes are available when a component asks for an IFoo implementation. I’ve seen lots of fascinating ways of doing this in Full Framework projects. Config files, loading every assembly in a certain directory, manually resolving at program startup, and others. In Silverlight, the AppManifest.xml file contains a “Deployment.Parts” tag, which contains all the assemblies that get deployed with your app. Still, I don’t seem to be able to call Assembly.Load() on these, so for now I’m going to go with manual resolution at program startup.
Generics make getting started on a service locator a breeze:
public class ServiceManager
{
static ServiceManager()
_services = new Dictionary<Type, object>();
}
private static Dictionary<Type, object> _services;
/// <summary>
/// Pass in a list of strong types and determine what service interfaces they provide to us:
/// </summary>
/// <param name="types"></param>
public static void Manage(List<Type> types)
Type baseServiceType = typeof(IService);
foreach (Type t in types)
Type[] interfaces = t.GetInterfaces();
foreach (Type iface in interfaces)
if(iface != baseServiceType)
Type svcInterface = iface.GetInterface("DamonPayne.AGT.IoC.IService", false);
if (null != svcInterface)//We know this interface extends IService
_services.Add(iface, ObtainDefaultInstance(t));
/// Since an object could implement more than one interface, we may have in in here under multiple keys
/// <param name="t"></param>
/// <returns></returns>
protected static object ObtainDefaultInstance(Type t)
object o = null;
var q = from val in _services.Values.AsQueryable<object>()
where val.GetType().IsAssignableFrom(t)
select val;
o = q.FirstOrDefault<object>();
if (null == o)
Type[] noArg = new Type[0];
o = t.GetConstructor(noArg).Invoke(null);
return o;
public static TServiceType Resolve<TServiceType>()
TServiceType svc = default(TServiceType);
Type t = typeof(TServiceType);
if (_services.ContainsKey(t))
svc = (TServiceType)_services[t];
else
throw new NotSupportedException(string.Format("A provider for {0} was not found", t));
return svc;
Now I need something to use to test this out. I think I might want a Logging service of some kind to display messages to me, so I’ll create an ILogService interface in the IoC project:
public interface ILogService: IService
void Debug(string message);
And I’ll need to provide an implementation somewhere. For now I’ll add a folder/namespace to the Layout project and create a simple do-nothing implementation. In the Application_Startup event handler I can now start setting things up. I create a MemoryLogger and:
private void Application_Startup(object sender, StartupEventArgs e)
this.RootVisual = new Page();
List<Type> s = new List<Type>
typeof(MemoryLogger)
};
ServiceManager.Manage(s);
ILogService logger = ServiceManager.Resolve<ILogService>();
//success!
Now I’m starting to get somewhere. I’m planning on creating an implicit way to get these services later. There seems to be two schools of thought here: setting services on serviced components using a property or field, and supplying all the necessary services in the constructor. For my next step, though, I want to get some things on the screen to help visualize how this is going to fit together.
My notion of a Region Manager is stolen directly from “Composite Application Guidance for WPF”. We can abstract out the idea of a Window and various views and evolve the idea of how the UI should function over time. I know that I will have the notion of a View, which is docked into a Region, which is managed by a RegionManager of some kind. First, we think about these interactions and come up with a first try at interfaces. Just to start getting some things on the screen, a View will be just this:
public interface IView
/// Grid, etc. extends canvas
Canvas LayoutRoot { get; }
I’m not sure that I need an explicit implementation of a Region, so my IRegionManager consists of the following:
public interface IRegionManager : IService
UIElement RootVisual { get; }
List<string> RegionNames { get; }
void AddView(IView v);
void AddView(IView v, string regionName);
IView GetView(string regionName);
List<IView> Views { get; }
The IRegionManager implementation does what I need it to do for now. Clearly, our Page is a suitable class for serving as the Region Manager. In the last article I envisioned three regions, but now that I’ve got my logging idea I’ll extend it to four. I need a region for a Toolbox, Designer Surface, Property Grid, and Message Console. Implementing the IRegionManager interface and supplying some default content to be sure I’m laying things out the way I want, the XAML and runtime application looks like so:
<UserControl x:Class="DamonPayne.HTLayout.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="800" Height="600">
<Canvas x:Name="LayoutRoot" Background="White">
<Grid x:Name="_regionArea" Background="Red" Width="800" Height="600">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Canvas x:Name="Toolbox" Grid.Row="0" Grid.Column="0" Width="Auto">
<TextBlock>I am toolbox</TextBlock>
</Canvas>
<Canvas x:Name="DesignSurface" Grid.Row="0" Grid.Column="1" Width="Auto">
<TextBlock>I am design surface</TextBlock>
<Canvas x:Name="PropertyGrid" Grid.Row="0" Grid.Column="2" Width="Auto">
<TextBlock>I am property grid</TextBlock>
<Canvas x:Name="MessageConsole" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3">
<TextBlock>I am the message console</TextBlock>
</Grid>
The IRegionManager implementation is incredibly straightforward and in the source code at the end of this article. Applying some refactoring we get the ServiceManager to create the Page for us and the application startup now looks like this:
typeof(MemoryLogger),
typeof(Page)
IRegionManager mgr = ServiceManager.Resolve<IRegionManager>();
RootVisual = mgr.RootVisual;
The next thing to decide is how IViews get loaded into our IRegionManager on startup. It would be nice if I could use the same mechanism for IService wire-up, but this article is getting a bit long already. For now, I’m going to create the visual design for the message console to set up the next article. I create a Controls folder/namespace to hold my application specific UI components and put a MessageConsoleView in. This component will be a UserControl that implements IView. As soon as I implemented IView, however, I see that LayoutRoot was a poor choice of name since it forced an explicit interface implementation, so I refactor to IView.VisualRoot instead. I also see that when I said “Canvas” was a good root because Canvas and Grid derived, I meant “UserControl”, so I refactor that too.
Now I have something to think about for the next article. The MessageConsoleView will obviously need to get at my ILogService implementation…
Now that I’ve got some code in here, I should decide on how I’m going to share that. The Creative Commons license seems to fit my goals. I’m not super up to date on open source license wording, but this one will do for now, in code-snippet form:
<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets
xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<CodeSnippet Format="1.0.0">
<Header>
<Title>
Creative Commons License
</Title>
</Header>
<Snippet>
<Code Language="CSharp">
<![CDATA[
/*
Creative Commons - attribution-noncommerical-share alike 3.0 Unported
You are free to copy, distribute, and transmit this work.
You are free to adapt the work.
Under the following conditions:
-You must attribute the author(Damon Payne, http://www.damonpayne.com) for this work but not in a way
that suggests the author endorses you or your use of the work.
-You may not use this work for commercial purposes
-If you alter, transform, or build upon this work, you are free to distribute the work under the same or similar license to this one.
*/
]]>
</Code>
</Snippet>
</CodeSnippet>
</CodeSnippets>
If anyone out there wants to provide input on this license, please do.
Source code throug this article: DamonPayne.AGT[1].zip (538.15 KB)
Remember Me
a@href@title, strike
Powered by: newtelligence dasBlog 2.0.7226.0
Disclaimer The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.
© Copyright 2010, Damon Payne
E-mail