It’s frightening just how much pain I have in starting a new application project. Creating an SQL database, deploying a schema, building an ORM access layer, and putting all the pieces into source control to deploy, version, update that database, both on the development SQL Express, and the production SQL Server instances—it takes time. It takes work. The only thing worse than putting in all that effort is doing the cowboy bit with the tools and NOT doing it. Then you have various unsynchronized, untestable, messy databases and deployed versions of code. Oook!
SubSonic 3.0 introduced the SimpleRepository and Auto-Migrations. These Ruby-on-Rails-inspired features, when coupled with SQLite can make your DAL-creation tasks just disappear. Let’s give it a try!
public class SubsonicRepositoryFactory{ public static SimpleRepository GetRepository() { var provider = SubSonic.DataProviders.ProviderFactory.GetProvider("Data Source=|DataDirectory|my.db", "System.Data.SQLite"); var repository = new SimpleRepository(provider, SimpleRepositoryOptions.RunMigrations); return repository; }}
This isn’t strictly necessary…but I hate putting my connection strings in all those angle brackets, and prefer to detect my production/test/development environments in code rather than in XML.
This is good stuff, and it certainly can take some of the pressure off of starting up a new project. With data access and persistence just ‘done’, you can get to logic and value quicker. And you always have the option of upgrading to a more ‘serious’ data layer if you need it later. I’m really curious if SubSonic/SQLite can provide a reasonably performant small-to-midsize backing store to a web service application server. If your applications and various UIs all access their data through the web services, who cares what the persistence storage is, and until that back end is doing enough work to require multiple servers, how much trouble can you get into by caching your objects in memory and persisting them to disk using SQLite?
It’s also worth noting that SimpleRepository doesn’t have any wiring for child collections yet. Rob has said on StackOverflow that he intends to do some work on that, but it’s not in there yet. However, it shouldn’t be too hard to drop some convenience methods on your POCOs (extension methods if you’re worried about polluting your classes) to emit Lists or IEnumerables of linked collections.
Update: The most recent versions of the drivers may have solved these problems. Works for me now.
WARNING! If you plug your Clear Wireless modem into a Macintosh computer with Clear’s driver installed, the modem will be flashed with a firmware that makes it incompatible with Windows. This firmware flash is not reversible. You will need to return your modem to Clear to get a new Windows-compatible one. More details below.
I got connected with Clear Wireless in Portland 4 days ago. So far I’m very happy with the bandwidth. Connected to my laptop, I’m getting 3mbs down and 200kbps up speeds. For mobile, unlimited internet, this is fantastic. Even better, I’m getting 100-160ms packet latencies, which is about one-third of what I was getting on my cellular wireless modem.
I also purchased the Clear Spot WiFi/WWAN router. This little device is really cool, letting me share my WIMAX connection with other computers, my iPhone, or whatever. Even better, it lets me use the WIMAX modem without installing drivers. This is great because the drivers for WWAN modems are terrible….and Clear doesn’t have average quality drivers.
Ok. So there aren’t Windows 7 drivers. Yet. The folks at Clear have told me I can expect to see them on Thursday, when the official launch date happens. In the meantime, install the connection manager from your CD or Driver Thumb Drive(in the box), and do a little googling for instructions on installing the older version of the drivers. Scott Hanselman wrote up something on this. You can also download the 64-bit driver from here for a couple months.
Also, be sure not to unplug the Modem before using the ‘Safely Eject’ feature of Windows….I get a BSOD every time.
The Win7 drivers work. They aren’t shiny or easy to install, but they work. That’s much better than the…
When Clear says they support Mac OS X, they are…well…reaching. Their driver is marked ‘Beta’ and it shows. After the install, the Connection Manager will spend 5 minutes or so just sitting there. No status message, and a red light on the modem. Is it working? Is it doing something? Who knows. After that, it will connect, and you’re set to go. Same good service.
Unless.
If you are running Snow Leopard (Mac OS X 10.6) you get nothing. No error message. No indication of failure. No connection. It just doesn’t work. Saying Windows 7 isn’t released yet is technically true, and I understand why those drivers aren’t out. OS X 10.6 has been out since August 28th. It seems foolish that an early-adopter service like Clear doesn’t support an early-adopter OS. That’s how it is, though. Clear people have told me unofficially that we should see a 10.6 driver ‘by the end of the month’. We’ll see.
The reason for the 5 minute wait when you install your Mac drivers is apparently that the drivers are installing a fresh firmware on your modem. As I stated at the top, this firmware isn’t compatible with the Windows drivers. Nor does Clear have a flash utility available that can downgrade it. You have to return your modem for a replacement. Clear has told me that this should be free, since it’s “Their Fault”. Be nice when you call, and they should hook you up.
Luckily, the Clear Spot can still connect to the modem with it’s new, corrupted, Mac OS (10.5 or earlier)-only firmware. I really like the driver-less mobile internet experience. No need for the hassle of incomplete, incompatible drivers or connection managers. Just Windows 7’s shiny new WiFi connection tool and fast internets.
The Clear Spot (Cradlepoint PHS-300) is a great router, works with non-Clear broadband modems, and is $50 cheaper when you get it from Clear. The battery seems to last a bit more than 3 hours with the network in use. You can get extra batteries and a car charger from Clear, but I think an external Tekkeon battery is probably a better idea. When you get it from 3Gstore, it comes with a USB cable that I’m hoping will let me charge my Clear Spot from my laptop. One less wall wart in my kit bag.
There is a slight glitch. Most of the pictures you’ll see of the Clear Spot will show it with the modem plugged right into it. It turns out this is a bad idea. WiFi and WiMax radios seem to interfere. Make sure you leave the router on Channel 1. I also didn’t get anything approaching full speeds until I used a USB extension cable to put some space between the two devices. Once I did that, speeds were right up there.
Clear’s service is definitely in early-adopter mode, but I had a really excellent experience both with the store staff, and with the folks at technical support. It took me 20 minutes on the phone to figure out my Mac/PC Firmware problem, but the rep was patient and eager to help, and that is an excellent experience these days.
I like my Clear service and I recommend it if you want some better mobile bandwidth.
After a little bit of work with MVC, you get infected with the spirit of clean code and begin to desire even more ways of eliminating repetition. You’ve got partials and html helpers. Still you are hungry. SubControllers are the dish that will fill you up.
There’s a design decision here. The question is why are we designing with subcontrollers? To understand the rationale, let’s look at the qualities of the various sub-view options.
1) Add a reference to MvcContrib.
2) Create a class called StructureMapSubControllerBinder. This is only required if you’re using StructureMap to do your IOC for you. You can use the base SubControllerBinder from MvcContrib, or create your own version for your IOC tool.
public class StructureMapSubControllerBinder : SubControllerBinder { public override object CreateSubController(Type destinationType) { object instance = ObjectFactory.GetInstance(destinationType); if (instance == null) { throw new InvalidOperationException(destinationType + " not registered with StructureMap"); } return instance; } }
3) Make the SubControllerBinder your default binder.
ModelBinders.Binders.DefaultBinder = new StructureMapSubControllerBinder();
<add namespace="MvcContrib"/>
1) Create a ~/Controllers/SubControllers folder. This is completely optional. If you have a bigger project you might want to make multiple subcontrollers folders for different areas.
2) Create a SubController class. The action needs to have the ‘same’ name as the class. It also subclasses from SubController.
using System.Web.Mvc; using MvcContrib; namespace NWIS.Business.Web.Controllers.SubControllers { public class DemoSubController : SubController { public ViewResult Demo() { return View(); } } }
3) Create a View subfolder. Using the ‘Add View’ context menu from the controller won’t work because the ‘sub’ in the class name. Just create the folder yourself. ~/Views/Demo.
4) Create a View. Right click on the ~/Views/Demo folder. Select Add->View. Name the view ‘Demo’. Make it a partial view (.ascx). Go ahead an add some markup to the view. Whatever you like.
1) Add an attribute to your Controller class.
[SubControllerActionToViewDataAttribute]
2) Add the subcontroller as a parameter to the action you want to use it in.
public ViewResult Index(DemoSubController mySubCont)
3) Place the subcontroller output into your view.
<% ViewData.Get<Action>("mySubCont").Invoke(); %>
I was recently asked about who I learn from, so I threw together this list of good places to get new ideas. Most of these are the RSS Links, not the web sites. I use Google Reader to make sure I keep up.
The simplest things seem to be the hardest to find the up-to-date documentation on. Adding a ControllerFactory to MVC is very simple, and making it work with StructureMap is pretty quick, but do you really want to write any of that code? Of course not. Somebody else can do it.
The MVC Contrib project has built all of the code you need to make a number of the popular IoC frameworks work in your project. Unfortunately IoC frameworks update a lot, and the MvcContrib project was finding it pretty hard to keep up with the latest versions when they compiled their builds. So that code is deprecated. Don’t use it.
Here’s how you add StructureMap to build controllers in your MVC project:
Create a StructureMapControllerFactory Class
using System; using System.Web.Mvc; using System.Web.Routing; using StructureMap; namespace MvcContrib.StructureMap { public class StructureMapControllerFactory : DefaultControllerFactory { public override IController CreateController(RequestContext context, string controllerName) { Type controllerType = base.GetControllerType(controllerName); return ObjectFactory.GetInstance(controllerType) as IController; } } }
Add a Method to Global.asax
private void ConfigureIoC() { ControllerBuilder.Current.SetControllerFactory(new StructureMapControllerFactory()); }
Add a call to that method in Application_Start
ConfigureIoC();
….and now we can go out for steak and exotic dancers. Your controllers will have their dependencies injected all nice and purty.
I Love StructureMap! It’s wonderful. What a way to compose your code together easily, precisely, and consolidatedly (!!). When I put a sentence like that together, I wonder if I even know what I’m talking about. The problem with StructureMap, IoC, and dependency injection really seems to be that the jargon for the patterns is so manifestly true that once you learn what the hell you are doing, you are completely unable to stop talking about it in shorthand. And that shorthand makes absolutely no sense to someone who hasn’t absorbed the patterns. Keep plugging, people. Once you do it, you’ll get it. Then you’ll be there and not be able to explain to other people why you’re so right. It’s like being Tom Cruise and needing to explain scientology.
Anyways.
I learned two things today. The first is how to hook all of my concrete generic repository types together with their interfaces. I had been adding a line of configuration for each repository. Now my test code looks like this:
ForRequestedType(typeof(IRepository<>)).TheDefaultIsConcreteType(typeof(ListRepository<>));
And my production code:
ForRequestedType(typeof(IRepository<>)).TheDefaultIsConcreteType(typeof(LlblRepository<>));
That’s easy!
The other thing I learned is that I can happily and easily inject data into my object registry for testing purposes. I have an in-memory repository implementation built. All it needs is data.
public class MemoryDataSource { private Dictionary<Type, IQueryable> data; public MemoryDataSource(Dictionary<Type, IQueryable> data) { this.data = data; } public IQueryable GetQueryable(Type type) { return this.data[type]; } } public class ListRepository<T> : IRepository<T> { private MemoryDataSource source; public ListRepository(MemoryDataSource source) { this.source = source; } public IQueryable<T> GetSource() { return ((IQueryable<T>)source.GetQueryable(typeof(T))); } public void SaveEntity(T entity) { return; } }
Now all I need to do is build a Dictionary<> keyed on the entity object type and fill it up with data. Once I’ve done that, I just pass it into the StructureMap registry like this:
ObjectFactory.Inject<Dictionary<Type, IQueryable>>(dataSource);
Now I can have ObjectFactory construct my object under test and it’s got just the data I need it to have.
ASP.NET MVC Has added a very useful new programming model to developing websites in .NET. There is hype and debate all throughout the interwebs on how great MVC is. And it all can be yours, with nothing but a ‘File->New’ in Visual Studio….unless you already have an ASP.NET application. In which case, you need to do a bit more work.
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.web> <pages> <namespaces> <add namespace="System.Web.Mvc"/> <add namespace="System.Web.Mvc.Ajax"/> <add namespace="System.Web.Mvc.Html" /> <add namespace="System.Web.Routing"/> <add namespace="System.Linq"/> <add namespace="System.Collections.Generic"/> </namespaces> </pages> <compilation> <assemblies> <add assembly="System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/> <add assembly="System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> <add assembly="System.Web.Abstractions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> <add assembly="System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> </assemblies> </compilation> <httpModules> <add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> </httpModules> </system.web> <system.webServer> <validation validateIntegratedModeConfiguration="false"/> <modules runAllManagedModulesForAllRequests="true"> <remove name="ScriptModule" /> <remove name="UrlRoutingModule" /> <add name="ScriptModule" preCondition="managedHandler" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> <add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> </modules> <handlers> <remove name="WebServiceHandlerFactory-Integrated"/> <remove name="ScriptHandlerFactory" /> <remove name="ScriptHandlerFactoryAppServices" /> <remove name="ScriptResource" /> <remove name="MvcHttpHandler" /> <remove name="UrlRoutingHandler" /> <add name="ScriptHandlerFactory" verb="*" path="*.asmx" preCondition="integratedMode" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> <add name="ScriptHandlerFactoryAppServices" verb="*" path="*_AppService.axd" preCondition="integratedMode" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> <add name="ScriptResource" preCondition="integratedMode" verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> <add name="MvcHttpHandler" preCondition="integratedMode" verb="*" path="*.mvc" type="System.Web.Mvc.MvcHttpHandler, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> <add name="UrlRoutingHandler" preCondition="integratedMode" verb="*" path="UrlRouting.axd" type="System.Web.HttpForbiddenHandler, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /> </handlers> </system.webServer> </configuration>
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.IgnoreRoute("{resource}.aspx/{*pathInfo}"); routes.MapRoute( "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = "" } // Parameter defaults ); } protected void Application_Start() { RegisterRoutes(RouteTable.Routes); }
The above is the “standard” routing for a MVC site. It will work, but it might cause you trouble if you want the root of your website to still go to a ‘default.aspx’. Try this line instead:
routes.MapRoute( "Default", "MVC/{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = "" }
<ProjectTypeGuids>{603c0e0b-db56-11dc-be95-000d561079b0};{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
Boy howdy. That was a good chunk of work. But now you have the infinite pleasure of developing in MVC, and your old web site should continue to work under you. Delicious incremental development goodness.
I’m a big believer in re-use. Code I don’t aihave to write is code I don’t have to debug or maintain. When I discovered that I’d really like some notification toast in my Windows Forms enterprise application, I immediately started looking for libraries.
The definitive toast solution for Mac OS is Growl. Growl is a separate application/control panel that runs on your system and manages notifications from all subscribing services on your system. No more overlapping toast. It also provides a lot of filtering, control, and plugin-based extensibility. Did you want your ‘new mail’ notifications to show up on your cell phone as SMS rather than on your desktop? Just do it. Want the toilet to flush when the build fails? Get an Arduino and script it up!
Needless to say I was really chuffed to find the Growl for Windows project. Their application is still in beta, but the developers are very responsive, and they are checking code fixes in within hours of getting bug reports when they can. GfW can provide you notifications for your GMail, Outlook, Visual Studio Builds, and the current song playing in Pandora.
My application installs with NSIS. Adding automatic installation of the Growl client to my installer was very quick. I grabbed the .msi file from the Growl download, and put it within my build tree. Then I added the following snippet to my NSIS script:
Section "Growl Install" SEC01 File "Growl\Windows Deployment.msi" ExecWait 'MSIEXEC.EXE /I "$INSTDIR\Windows Deployment.msi" /QB- ALLUSERS=1' SectionEnd
That’s it for the install. However, I wanted it to uninstall cleanly as well, so I added the following line to the uninstall section:
ExecWait 'MSIEXEC.EXE /x "$INSTDIR\Windows Deployment.msi" /qn'
Two caveats:
First things first, lets establish some interfaces to program to, that way we can swap out implementations for testing and if our needs change.
public interface INotificationMessage { string MessageType { get; } string MessageDescription { get; } string MessageId { get; } string MessageText { get; set; } Image MessageIcon { get; } Action AcceptMessageCallback { get; set; } Action DeclineMessageCallback { get; set; } } public interface INotificationSender { void SendMessage(INotificationMessage message); }
Next, we’ll implement a specific Notification Message type. Each Notification Message type can be instantiated of a specific type so that it’s very easy to add new types as well as very easy to use them. Each type can have an icon of it’s own.
public class ApplicationErrorNotificationMessage : INotificationMessage { public string MessageType { get; private set; } public string MessageDescription { get; private set; } public string MessageId { get; private set; } public string MessageText { get; set; } public Image MessageIcon { get; private set; } public Action AcceptMessageCallback { get; set; } public Action DeclineMessageCallback { get; set; } public ApplicationErrorNotificationMessage() { this.MessageType = "APPLICATION_ERROR"; this.MessageDescription = "Application Error"; this.MessageId = Guid.NewGuid().ToString(); this.MessageIcon = Application.Properties.Resources.AppIcon; } }
It appears that growl caches the message icons, so you might need to restart the Growl process if you go changing your Notification icons.
Finally we have our implementation of INotificationSender which registers with Growl, ensures that it is installed, and ensures that it is running.
public class GrowlNotificationSender : INotificationSender { private GrowlConnector connector; private Image applicationIcon; private string applicationName; private INotificationMessage[] messageTypes; private Dictionary<string, INotificationMessage> sentMessages; public GrowlNotificationSender(INotificationMessage[] messageTypes, Image applicationIcon, string applicationName) { this.connector = new GrowlConnector(); this.messageTypes = messageTypes; this.applicationIcon = applicationIcon; this.applicationName = applicationName; this.sentMessages = new Dictionary<string, INotificationMessage>(); EnsureGrowlIsRunning(); RegisterWithGrowl(); } private void RegisterWithGrowl() { Application thisApplication = new Application(applicationName); thisApplication.Icon = applicationIcon; List<NotificationType> notificationTypes = new List<NotificationType>(); foreach (var messageType in messageTypes) { NotificationType notificationType = new NotificationType(messageType.MessageType, messageType.MessageDescription); notificationType.Icon = messageType.MessageIcon; notificationTypes.Add(notificationType); } this.connector.Register(thisApplication, notificationTypes.ToArray()); this.connector.NotificationCallback += new GrowlConnector.CallbackEventHandler(connector_NotificationCallback); this.connector.EncryptionAlgorithm = Cryptography.SymmetricAlgorithmType.PlainText; } public void EnsureGrowlIsRunning() { Process[] processlist = Process.GetProcesses(); foreach (Process theprocess in processlist) { if (theprocess.ProcessName.Equals("Growl")) { return; } } Detector detector = new Growl.CoreLibrary.Detector(); if (detector.IsAvailable) { System.Diagnostics.Process.Start(detector.InstallationFolder + @"\Growl.exe"); Thread.Sleep(1000); return; } else { throw new FileNotFoundException("Growl not installed?"); } } void connector_NotificationCallback(Response response, CallbackData callbackData) { if (sentMessages[callbackData.Data] != null) { if (callbackData.Result == CallbackResult.CLICK) { if (sentMessages[callbackData.Data].AcceptMessageCallback != null) { sentMessages[callbackData.Data].AcceptMessageCallback.Invoke(); } } else { if (sentMessages[callbackData.Data].DeclineMessageCallback != null) { sentMessages[callbackData.Data].DeclineMessageCallback.Invoke(); } } sentMessages.Remove(callbackData.Data); } } public void SendMessage(INotificationMessage message) { this.sentMessages.Add(message.MessageId, message); CallbackContext callbackContext = new CallbackContext(); callbackContext.Data = message.MessageId; callbackContext.Type = message.MessageType; Notification notification = new Notification(this.applicationName, message.MessageType, message.MessageId, message.MessageDescription, message.MessageText); EnsureGrowlIsRunning(); this.connector.Notify(notification, callbackContext); } }
This implementation keeps track of sent messages, and executes callbacks on them depending on the user’s response. If you don’t want to use one of the callbacks, just leave it undefined.
I haven’t found a way to get Growl to send the CLOSE message rather than the TIMEOUT, but that’s ok, since I treat them the same.
You’d probably want to wire this up with your IOC container, but here’s a manual usage.
INotificationSender sender = new GrowlNotificationSender( new[] { new ApplicationErrorNotificationMessage() }, Moneta.Properties.Resources.Crystal_128_package_network, "My Application" ); Thread.Sleep(1000); ApplicationErrorNotificationMessage message = new ApplicationErrorNotificationMessage { MessageText = "Hi There", AcceptMessageCallback = () => MessageBox.Show("You clicked"), DeclineMessageCallback = () => MessageBox.Show("Pay Attention!") }; sender.SendMessage(message);
It’s worth noting that the first registration of your app may take a moment for Growl to process, and that’s why I have the Sleep() in there. If your app doesn’t intend to immediately send a message after it’s first registration, that’s unnecessary.
So last time, we created this great event aggregator for our Windows Forms applications. Instead of having the code that sends messages directly connected to the code that receives messages, everybody just knows about the event aggregator. This works the same way you don’t need turn-by-turn directions to Fox News Headquarters to mail them a box of dirty diapers.
The catch is that the code so far only works if all the senders and receivers are on the same thread. If they are on different threads, who knows what may happen? (Not me. I failed out of Home-Ec. I don’t even know how you turn cotton flowers into threads, let alone how to make them fit together.)
There is a simple fix, however. The .NET 2.0 SynchronizationContext is used by Windows Forms and can provide an easy-to-use central choke point to manage all of our thread-to-thread communications.
When I was cutting and pasting together this code from the Intarnets, one big problem I had was forgetting to initialize my singleton correctly. It’s really important that you initialize your singleton EventAggregator from SynchronizationContext.Current in the Windows Forms Thread. Setting the thing up right in StructureMap or your IOC Container of choice works just great. Just make sure it gets done. (My mistake also involved letting StructureMap use “new SynchronizationContext()” in initializing my singleton rather than “SynchronizationContext.Current”.) Get it right the first time and you won’t have to do multithread debugging.
public class EventAggregator : IEventAggregator { private readonly SynchronizationContext _context; private readonly List<object> _listeners = new List<object>(); private readonly object _locker = new object(); public EventAggregator(SynchronizationContext context) { _context = context; } #region IEventAggregator Members public void SendMessage<T>(T message) { sendAction(() => all().CallOnEach<IListener<T>>(x => { x.Handle(message); })); } public void AddListener(object listener) { withinLock(() => { if (_listeners.Contains(listener)) return; _listeners.Add(listener); }); } public void RemoveListener(object listener) { withinLock(() => _listeners.Remove(listener)); } #endregion private object[] all() { lock (_locker) { return _listeners.ToArray(); } } private void withinLock(Action action) { lock (_locker) { action(); } } protected virtual void sendAction(Action action) { _context.Send(state => { action(); }, null); } }
Notice the locking. Notice the creation of a copy of the _listeners list into an array for Thread Safety. Most importantly, notice the use of _context.Send(). A few minor changes…but now the whole class is thread safe, and synchronous between threads. Hooray!
I’ve still got lots to cover on this subject. How do you use the eventing in your application architecture. Using Weak References to protect your event system from memory leaks. Sending events to a specific target or set of targets. I’m going to be working on some other projects for a bit, so it may be a while before I write those posts, but hopefully what we’ve covered so far is useful on its own until then.
Digging out the Draft Bucket: This isn’t really a completed post, but rather than leave it in my Draft folder forever, I’m just going to post it. That way it’s infamy will live forever.
There’s a massive summary of design principles in the ASP.NET MVC Forums project introduction. The actual code in the project is opaque to me. I’d need to learn the framework before I’d understand how he’s working on it.
Rocky on TDD, with an explanation on how comprehensive testing means writing more test code than real code. "No one actually does this." "I once watched a TDD (and MVC/MVP) presentation. The speaker wrote several pages of code to build and test a presenter that did a bunch of work. Nice stuff, until you realized that all that could have been done in 1-3 lines of code using data binding. I asked him why he did this rather than using data binding. The answer: you can’t test data binding. I’m afraid my jaw dropped. See, I have a wife and kids. I like to get home and spend time with them. If I can write 3 lines of code, or write 3 pages of code that I need to test and debug, I’m going to pick the 3 lines of code every time"
Authorization system in CSLA.
Polymorphic MV*.
For when I need some new article materials: A Big Hanselman Article on Code Learning Online.
Cohesion and Coupling
Articles on SOLID: