Sunday, December 7, 2008

Castle Windsor factory method support

After using ninject for a short project (some modifications to a Subtext blog) I came back to Windsor and found myself missing ToMethod(). ToMethod() in ninject gives you factory method support, that is, you pass it a Func and the container will call it whenever it needs to instantiate the component.

Now Windsor has had factory support for years, but it requires the factory to be a class, registered as a component in the container. This is ok as it's the most flexible approach, but sometimes all you need is a factory method, and creating a class and then registering it seems a bit overkill. It would be nicer to write something like:

Container.Register(Component.For<HttpServerUtilityBase>()
   .FactoryMethod(() => new HttpServerUtilityWrapper(HttpContext.Current.Server))
   .LifeStyle.Is(LifestyleType.Transient));

With this little extension method, you can do just that:

    public static class ComponentRegistrationExtensions {
        public static IKernel Kernel { private get; set; }

        public static ComponentRegistration<T> FactoryMethod<T, S>(this ComponentRegistration<T> reg, Func<S> factory) where S: T {
            var factoryName = typeof(GenericFactory<S>).FullName;
            Kernel.Register(Component.For<GenericFactory<S>>().Named(factoryName).Instance(new GenericFactory<S>(factory)));
            reg.Configuration(Attrib.ForName("factoryId").Eq(factoryName), Attrib.ForName("factoryCreate").Eq("Create"));
            return reg;
        }

        private class GenericFactory<T> {
            private readonly Func<T> factoryMethod;

            public GenericFactory(Func<T> factoryMethod) {
                this.factoryMethod = factoryMethod;
            }

            public T Create() {
                return factoryMethod();
            }
        }
    }

 

Of course, this needs the FactorySupportFacility to be installed:

Container.AddFacility("factory.support", new FactorySupportFacility());

And since this isn't really a part of the Kernel, it needs a reference to the container's kernel:

ComponentRegistrationExtensions.Kernel = Container.Kernel;

UPDATE 5/7/2009: I submitted a patch which Ayende applied in revision 5650 so this is now part of the Windsor trunk, only instead of FactoryMethod() it's called UsingFactoryMethod()

UPDATE 7/30/2010: as of Windsor 2.5, UsingFactoryMethod() no longer requires the FactorySupportFacility.

Monday, December 1, 2008

Google Chrome caches 301 redirects

Ever since the release of Chrome I've alternating between it and Firefox for casual browsing. At work, I mainly use Chrome for information seeking because of its sheer speed, but nothing beats Firefox + Firebug for web development and javascript debugging. Sometimes, however, I mix my browsers and run some non-javascript stuff on Chrome (of course I test javascript-heavy stuff on all main browsers).

So a couple of days ago I was checking a seemingly innocent piece of code on Chrome, something like:

Response.StatusCode = 301;
Response.RedirectLocation = newUrl;

and I messed up a querystring parameter on newUrl. I fixed it, ran it again, and it still pointed to the wrong URL. Hmm, was I using the right port (sometimes I run multiple branches of different ports)? Yes. So I set a breakpoint on Response.RedirectLocation, and it didn't fire. WTF?! After some more hair-pulling and cursing (spanish is a really wonderful language for cursing, you know, there's even a whole mathematical theory behind it(1)) I ran it on Firefox and it worked as expected. And then it hit me: it is a permanent redirection after all, isn't it? So why not cache it? I ran it on every other browser I had (IE, Safari, Opera), they all behaved like Firefox, the only one caching the redirect was Chrome.

I couldn't believe it, so I wrote a simple "proof": an app that gives you a link, when you click the link it creates a cookie and redirects (301) to another page where it shows the content of the cookie and deletes it. Source code is here (nothing interesting) and you can run it here. Point your browser to http://localhost:12345/. Click the link. You'll see "Original=/first" as the response. Ok, now go back to root. Click the link again. Now, if your browser cached the redirect, you'll see "Original=", since the /first URL wasn't requested again. Go back to root, hit F5 and it clears the cache.

I don't mean to say this is wrong behavior, it's just that it's the first browser I see that implements 301 redirect caching, it's something to be aware of while developing and testing.

(1) For the non-Spanish-speaking readers: it's just a joke ;-)