Css and JavaScript file minification
Performing the minification of your custom JavaScript and Css files is usually a good practice to follow when deploying your website in a production environment, but this usually makes doing ‘on the fly’ and ‘live’ modification to those files nearly impossible due to the very compact form they get.
A common technique you can use when it comes to asp.net web sites is to develop an HttpHandler that handle the minification task on the fly.
A very simple approach is to minify each file on a file per file basis, that is: every request of a .js or a .css file will be mapped to be processed by our handlers, the file read and compressed (using an appropriate library) and then stored in the cache (for any subsequent request) and streamed back to the client.
To perform the minification I decided to use the Yahoo! UI Library: YUI Compressor for .Net, but you can change it to whatever compressor you like.
Here’s the code for the Css minification handler:
public class CssYuiCompressorHandler : IHttpHandler
{
private const int DefaultCacheDuration = 3000;
public bool IsReusable { get { return true; } }
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/css";
string filePath = context.Request.Url.AbsolutePath;
filePath = context.Server.MapPath(filePath);
// if the file is already minified (we use the .min.css naming convention)
// read it as it is and deliver it to the client
if (File.Exists(filePath))
{
if (filePath.EndsWith(".min.css"))
context.Response.WriteFile(filePath);
else
CompressCssAndWriteToResponseStream(context, filePath);
}
else
context.Response.StatusCode = 404;
context.Response.Flush();
context.Response.End();
}
static readonly object FileLock = new object();
private static void CompressCssAndWriteToResponseStream(HttpContext context, string filePath)
{
string requestHash = context.Request.Url.AbsolutePath;
if (context.Cache[requestHash] != null)
{
context.Response.Write((string)context.Cache[requestHash]);
return;
}
lock (FileLock)
{
using (StreamReader sr = new StreamReader(filePath, true))
{
string compressed = CssCompressor.Compress(sr.ReadToEnd());
context.Response.Write(compressed);
context.Cache.Add(requestHash,
compressed,
null,
DateTime.MaxValue,
new TimeSpan(0, DefaultCacheDuration, 0),
System.Web.Caching.CacheItemPriority.Normal,
null);
sr.Close();
}
}
}
}
The JavaScript minification handler is quite similar:
public class JsYuiCompressorHandler : IHttpHandler
{
private const int DefaultCacheDuration = 3000;
public bool IsReusable { get { return true; } }
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "application/x-javascript";
string filePath = context.Request.Url.AbsolutePath;
filePath = context.Server.MapPath(filePath);
// if the file is already minified (we use the .min.js naming convention)
// read it as it is and deliver it to the client
if (File.Exists(filePath))
{
if (filePath.EndsWith(".min.js"))
context.Response.WriteFile(filePath);
else
CompressJsAndWriteToResponseStream(context, filePath);
}
else
context.Response.StatusCode = 404;
context.Response.Flush();
context.Response.End();
}
static readonly object FileLock = new object();
private static void CompressJsAndWriteToResponseStream(HttpContext context, string filePath)
{
string requestHash = context.Request.Url.AbsolutePath; //.GetHashCode().ToString();
if (context.Cache[requestHash] != null)
{
context.Response.Write((string)context.Cache[requestHash]);
return;
}
lock (FileLock)
{
using (StreamReader sr = new StreamReader(filePath, true))
{
string compressed = JavaScriptCompressor.Compress(sr.ReadToEnd());
context.Response.Write(compressed);
context.Cache.Add(requestHash,
compressed,
null,
DateTime.MaxValue,
new TimeSpan(0, DefaultCacheDuration, 0),
System.Web.Caching.CacheItemPriority.Normal,
null);
sr.Close();
}
}
}
}To activate these two handlers you have to modify the web.config file:
<httpHandlers> ... <add verb="*" path="*.css" type="Dexter.Web.HttpHandlers.CssYuiCompressorHandler, Dexter.Web, Version=1.0.0.0, Culture=neutral"/> <add verb="*" path="*.js" type="Dexter.Web.HttpHandlers.JsYuiCompressorHandler, Dexter.Web, Version=1.0.0.0, Culture=neutral"/> </httpHandlers>
This approach allows you to deploy the files as they are and the minification is performed on the server during the first request.
I tested this technique in Dexter (it’s actually working in this blog) and I noticed a good reduction in the size of the custom .css and .js files I had:
for example:
Site.css passed from 36.80 KB to 27.53 KB
shCore.js passed from 19.22 KB to 18.17 KB
customFunction.js passed from 5.44 KB to 3.77 KB
The total reduction in size was something like 32 KB.
In a more advanced solution you can look for a way to not only minify the single files, but also to merge them in a single file in order to minimize the number of requests made to the server.
PrimordialCode is now powered by the open source Dexter Blog Engine
Posted by Guardian in Freetime talking on Monday 08 March 2010 at 11:06 AM
It's time for another new beginning.
Over a month ago I joined the Dexter's Developers Team, cause I felt that the project was indeed good and I liked the idea to participate in developing something I could also have used.
When I entered the project it was missing some features I considered vital for me to switch over:
- The ability to import all my previous data from Wordpress.
- The support for multiple categories for every post.
- A better integration with Windows Live Writer (the primary tool I always used to make my posts) to support hierarchical categories, tags and slugs.
I've worked on all these in my spare time during the last month and now that all of them are implemented (there's still some work to do to improve the import section and integrate it in the blog engine instead of using an external tool), I see no reason to not switch over.
So let's say good-bye to my Wordpress version of the blog that accompanied me during this almost two years of blogging experience and say welcome to the new PrimordialCode powered by Dexter (the actual skin is kindly stolen from Ugo’s blog).
There's still more room for improvements and we have a lots of new features on the horizon to implement.
If you are curious about Dexter go check our feature list and download the source code from our
project page on CodePlex: http://dexterblogengine.codeplex.com/
A big thank to all the guys of the team for making this possible.
JQuery, WCF and the JSON DateTime serialization
Days ago I blogged about how to call a WCF service from a jQuery application to retrieve and send data to the server to realize a small interactive chatting application. Everything was working fine until it came to format any DateTime data passed from the server to the client.
We ended up having a call like this:
var msg3 = { "msg": { "Id": "2", "Sender": "Webpage", "Text": "Sended Text" }, "id": "1" };
$(document).ready(function() {
$.ajax({
type: "POST",
url: serviceUrl + "TestMessageModifyWithSuppliedContent",
// data: "{ \"msg\" : " + JSON.stringify(msg) + ", \"id\" : \"4\"}",
data: JSON.stringify(msg3),
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(data) {
//debugger;
var text = data.d.Timestamp + " - " + data.d.Sender + " - " + data.d.Text;
alert(text);
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
debugger;
alert("Error Occured!");
}
});
});
the output of this code is a Message Box with the following text:
"/Date(1267695086938+0100)/ - Webpage - 3 - Second parameter passed: 1"
Looking at fiddler for the request and the response we have:
POST http://localhost.:58817/Services/ChatService.svc/TestMessageModifyWithSuppliedContent HTTP/1.1
...Request plumbing goes here...
Pragma: no-cache
{"msg":{"Id":"2","Sender":"Webpage","Text":"Sended Text"},"id":"1"}
HTTP/1.1 200 OK
...Response plumbing goes here...
Content-Type: application/json; charset=utf-8
Content-Length: 160
Connection: Close
{"d":{"__type":"Message:#LiveAssistance.Entities","Id":2,"Sender":"Webpage","Text":"3 - Second parameter passed: 1","Timestamp":"\/Date(1267694873788+0100)\/"}}
I was surprised at fist, then a quick research show that this is the way WCF serializes the DateTime object in JSON. In short the first number represent the number of milliseconds in the GMT time zone, regular (non-daylight savings) time since midnight, January 1, 1970. The number may be negative to represent earlier times. The second is the time zone (more info here: Stand-Alone JSON Serialization).
So we have to do something to convert this string representation in a JavaScript Date() object. To do so we can use the ‘dataFilter’ feature of the jQuery ajax() call that allows us to modify and alter the returned JSON string representation before it’s passed on to the parser.
We basically want to change the string “/Date(1267695086938+0100)/” to
“new Date(Date(1267695086938+0100)”; to do so we can rewrite our ajax call like this:
1: $(document).ready(function() {
2: $.ajax({
3: type: "POST",
4: url: serviceUrl + "TestMessageModifyWithSuppliedContent",
5: // data: "{ \"msg\" : " + JSON.stringify(msg) + ", \"id\" : \"4\"}",
6: data: JSON.stringify(msg3),
7: contentType: "application/json; charset=utf-8",
8: dataType: "json",
9: dataFilter: function(data, type) {
10: var d = data.replace(/"\\\/(Date\(.*?\))\\\/"/gi, 'new $1');
11: return d;
12: },
13: success: function(data) {
14: //debugger;
15: var text = data.d.Timestamp.format("yyyy/mm/dd - HH:MM:ss") + " - " + data.d.Sender + " - " + data.d.Text;
16: alert(text);
17: },
18: error: function(XMLHttpRequest, textStatus, errorThrown) {
19: debugger;
20: alert("Error Occured!");
21: }
22: });
23: });
Lines 9 - 12 shows our filtering function with the Regex we use to convert the returned JSON string representation to this one:
FROM:
"{"d":{"__type":"Message:#Entities","Id":2,"Sender":"Webpage","Text":"3 - Second parameter passed: 1","Timestamp":"\/Date(1267696301237+0100)\/"}}"
TO:
"{"d":{"__type":"Message:#Entities","Id":2,"Sender":"Webpage","Text":"3 - Second parameter passed: 1","Timestamp":new Date(1267696301237+0100)}}"
Everthing is now working and the Timestamp field contains a Date object...if you use jQuery 1.3.x...
If (like me) you use jQuery 1.4.x you will get an error from the JSON serializer with an ‘invalid JSON format’ message...and guess...he’s right because “new Date(something)” isn’t a valid representation...so why the hell it all worked before?
It turned out that jQuery 1.3.x used the JavaScript eval() function to internally deserialize objects (so using the method above we used in reality a trick), jQuery 1.4.x relies on the browser capabilities to deserialize JSON streams (in particular it uses the window.JSON object if the browser has support for it).
It does this way mainly for performances and security reasons. So we have 2 ways now to get a Date object from our string representation:
1- process each object with a function that (using the previously pointed regex and the eval() function) convert each date field to the corresponding object.
2- change the way the data are parsed (client-side) and do our own JSON deserialization using eval(), to act this way we need to change the ‘dataType’ returned from ‘json’ to ‘text’ - this way we disable the automatic deserialization - then we have to call the eval() function on the returned and modified data stream:
1: $(document).ready(function() {
2: $.ajax({
3: type: "POST",
4: url: serviceUrl + "TestMessageModifyWithSuppliedContent",
5: // data: "{ \"msg\" : " + JSON.stringify(msg) + ", \"id\" : \"4\"}",
6: data: JSON.stringify(msg3),
7: contentType: "application/json; charset=utf-8",
8: dataType: "text",
9: dataFilter: function(data, type) {
10: var d = data.replace(/"\\\/(Date\(.*?\))\\\/"/gi, 'new $1');
11: return d;
12: },
13: success: function(data) {
14: //debugger;
15: data = eval('(' + data + ')');
16: var text = data.d.Timestamp.format("yyyy/mm/dd - HH:MM:ss") + " - " + data.d.Sender + " - " + data.d.Text;
17: alert(text);
18: },
19: error: function(XMLHttpRequest, textStatus, errorThrown) {
20: debugger;
21: alert("Error Occured!");
22: }
23: });
24: });
Lines 8, 15 and 16 shows the modifications we made. As you can see in line 16 now you can use the Timestamp field as a data object and format it using any JavaScript DateTime formatting library you like.
As a note: using this second method you are obviously loosing in performance and security, cause eval() is slower than the native JSON deserialization, and even worse you are subject to code injection attacks.
Working on Dexter
Posted by Guardian in Freetime talking on Wednesday 17 February 2010 at 11:52 AM
I’m not writing many posts in the last days and the main reason is I’ve actively joined the development of Dexter a new open source blog engine written in ASP.NET MVC, you can find it on Codeplex at this link: http://dexterblogengine.codeplex.com/
When my friend Andrea Balducci, talked to me about this project I was so excited I decided to give it a try...and it was good (there’s always room for improvement, but it was good!).
I do love Wordpress, it’s a great product and works very well, why working on another blog engine then? well the answer is short: I’m a developer and I don’t know how to put my hands on the WP engine...simply cause I hate php and never spent more than a couple of hours trying to learn it.
With Dexter is all another story...C#, ASP.NET MVC, NHibernate, Castle etc etc I feel home and (even better) I have the freedom to make experiments on the core engine too.
Since I joined I focused on looking at the implementation and adding the features I needed first. So what I was working on the past days:
- Improve the import engine to support Wordpress (I’m very close to fix all that I need), you can see a live version of my blog using Dexter at this link: http://dexter.primordialcode.com/
- Adding a better support for Windows Live Writer improving the different Metaweblog APIs support.
I just added the hierarchical visualization of category (no more flattening out) and the ability to post the tags related to each post using WLW (no more need to add them at the body of the message).
Here’s a screenshot of the thing:
What I’m working on now:
- Adding the ability to tie a single Post to more than a single category.
- Improving the import routines.
All the changes are actually in my private Branch and will be merged to the main trunk once all the developers have reviewed them.
Passing JSON serialized objects to a WCF service with jQuery
You can find a lot of posts on the web on the subject, why writing another one then? Simply because despite all the documentation I’ve already found on the web, while trying to put it in action I’ve spent a couple of hours in making that work, so I’ll use this post to recap all the action I made as a reference guide for my future implementations.
The scenario is: we have a WCF service that is able to accept call to methods with complex parameters (like Messages object in a simple web chat project) and we want to be able to use those methods with jQuery with the minimal impact on the service implementation.
WCF Service setup
We consider a very simple implementation, you don’t have to use any ‘strange’ or unusual attribute here:
The message class we are sending back and forth:
/// <summary>
/// a single message in a conversation
/// </summary>
[DataContract]
public class Message : Entity<int>
{
/// <summary>
/// the nickname of who is sending the message
/// </summary>
[DataMember]
public virtual string Sender { get; set; }
/// <summary>
/// this will be assigned when the object get persisted into the storage, not before
/// once assigned it's likely to not be modified again
/// </summary>
[DataMember]
public virtual DateTime Timestamp { get; set; }
[DataMember]
public virtual string Text { get; set; }
}
A test service implementation
/// <summary>
/// service used to serialize/desierialize the data in JSon for JQuery
/// </summary>
[ServiceContract]
public interface IChatService
{
/// <summary>
/// return a simple message
/// </summary>
/// <returns></returns>
[OperationContract]
Message TestMessage();
/// <summary>
/// a test function that takes a message and returns it modified
/// </summary>
/// <param name="msg"></param>
/// <returns></returns>
[OperationContract]
Message TestMessageModify(Message msg);
/// <summary>
/// a test function that get multiple parameters and return a message
/// modified
/// </summary>
/// <param name="msg"></param>
/// <param name="id"></param>
/// <returns></returns>
[OperationContract]
Message TestMessageModifyWithSuppliedContent(Message msg, int id);
}
[AspNetCompatibilityRequirements( RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed )]
public class ChatService : IChatService
{
public Message TestMessage()
{
return new Message() { Id = 1, Sender = "Test", Text = "1 - Text message.", Timestamp = DateTime.Now };
}
public Message TestMessageModify(Message msg)
{
msg.Text = "2 - Modified message";
msg.Timestamp = DateTime.Now;
return msg;
}
public Message TestMessageModifyWithSuppliedContent(Message msg, int id)
{
msg.Text = "3 - Second parameter passed: " + id;
msg.Timestamp = DateTime.Now;
return msg;
}
}
The only thing needed is the ‘AspNetCompatibilityRequirements’ attribute.
All the magic is done in the WCF configuration section:
1: <system.serviceModel>
2: <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
3: <services>
4: <service name="RemoteAssistance.Services.Impl.ChatService"
5: behaviorConfiguration="DebugJSonBehavior">
6: <endpoint address=""
7: binding="webHttpBinding"
8: behaviorConfiguration="DebugJSonBehavior"
9: contract="RemoteAssistance.Services.IChatService">
10: <identity>
11: <dns value="localhost" />
12: </identity>
13: </endpoint>
14: <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
15: </service>
16: </services>
17: <behaviors>
18: <endpointBehaviors>
19: <behavior name="DebugJSonBehavior">
20: <enableWebScript />
21: </behavior>
22: </endpointBehaviors>
23: <serviceBehaviors>
24: <behavior name="DebugJSonBehavior">
25: <serviceMetadata httpGetEnabled="true" />
26: <serviceDebug httpHelpPageEnabled="true" includeExceptionDetailInFaults="true" />
27: </behavior>
28: </serviceBehaviors>
29: </behaviors>
30: </system.serviceModel>
To activate JSON serialization and deserialization for an end point we have to supply some information:
- in line 7 we set the binding to ‘webHttpBinding’, this is the main actor the inject the JSON DataContract Serializer into the WCF stack.
- in line 8 we set a specific endpoint behavior for this end point
- lines 18-22 enable the endpoint behavior that makes it possible to consume the service from ASP.NET AJAX web pages.
Consuming the web service is the real ‘tricky’ point, calling a function without parameter is extremely easy, you can use code like this:
1: $(document).ready(function() {
2: $.ajax({
3: type: "POST",
4: url: "http://localhost:58829/ChatService.svc/TestMessage",
5: data: "{}",
6: contentType: "application/json; charset=utf-8",
7: dataType: "json",
8: success: function(data) {
9: //debugger;
10: alert(data.d.Text);
11: },
12: error: function(XMLHttpRequest, textStatus, errorThrown) {
13: debugger;
14: alert("Error Occured!");
15: }
16: });
17: });
Things get a bit ‘complicated’ when you actually have to pass parameters (special tanks here go to my fellow Alkampfer for helping me find the solution). To pass a parameter successfully you have to supply as the data argument (line 5) a string that is the JSON representation of a JS object which properties matches the parameter name of the function you are calling, the parameters’ values are the actual values that will be sent to the server. If the parameter itself is a complex object (that is a class) you need to supply the JSON representation of the object, Let’s use an example to clarify:
if we want to call the TestMessageModify(Message msg) function we need to supply ad data parameter a JS object made like this:
1: var msg2 = { "msg": { "Id": "2", "Sender": "Webpage", "Text": "Sended Text"} };
We’re defining an object that contains a ‘msg’ property (same name of the function parameter) which in turn contains another object (defined by the same properties that our Message object have, to allow JSON deserialization to work). What we need to pass to the ‘data’ argument of the function call is the string representation of object:
'{"msg":{"Id":"2","Sender":"Webpage","Text":"Sended Text"}}'
To have the JSON representation of JavaScript object we can use an external library like this one: http://www.JSON.org/json2.js
So, to call TestMessageModify(Message msg) and TestMessageModifyWithSuppliedContent(Message msg, int id), we can use the following code:
var msg2 = { "msg": { "Id": "2", "Sender": "Webpage", "Text": "Sended Text"} };
$(document).ready(function() {
$.ajax({
type: "POST",
url: "http://localhost:58829/ChatService.svc/TestMessageModify",
data: JSON.stringify(msg2),
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(data) {
alert(data.d.Text);
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
debugger;
alert("Error Occured!");
}
});
});
var msg3 = { "msg": { "Id": "2", "Sender": "Webpage", "Text": "Sended Text"}, "id" : "1" };
$(document).ready(function() {
$.ajax({
type: "POST",
url: "http://localhost:58829/ChatService.svc/TestMessageModifyWithSuppliedContent",
data: JSON.stringify(msg3),
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(data) {
alert(data.d.Text);
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
debugger;
alert("Error Occured!");
}
});
});
Castle Windsor enabling XML configuration files in Silverlight
Posted by Guardian in Castle Windsor Silverlight on Friday 22 January 2010 at 12:07 PM
When a version of Castle Windsor able to run in Silverlight was released I started to play with it, basically because I have a lot of code that use it inside my line of business infrastructure framework. I’m working on a WPF/Silverlight solution and I’ve developed a framework that work in both the environments.
One of the biggest thing I miss in every ‘Silverlight-enables’ IoC system I tried is the ability to read and parse XML configuration files.
The current release does not allow Silverlight to parse XML configuration file, because all the parser is based on classes that are in the System.Xml namespace and that weren’t included in the Silverlight runtime.
The good news is Silverlight has a very good support for Linq To Xml, so I decided to do a ‘test of feasibility’ and switched the current implementation from using the XmlDom to Linq To Xml.
I have to admit it was more complex than I thought at start (mainly because I had to understand how the parser was written), here are the steps I followed:
- Download the ‘Inversion of control’ portion the Castle Windsor trunk an check the project to find the classes to modify: the good news is: all of them are inside a single folder ‘Castle.Windsor\Configuration\Interpreters’
- Start with the .Net solution and replace the usages of the ‘old’ XmlDom classes (XmlDocument, XmlNode, etc...) with the new one from System.Xml.Linq (XDocument, XElement, XNode...). Beware it’s not that easy!
Both these systems act in slightly and sometime subtle different ways so I had to make ‘heavy’ modifications to all the class of the parser. - Keep modifying and fixing the XmlInterpreter and all the other classes until all the tests for the .Net solution pass. At this stage the major modification were the introduction of a LinqToXmlConfigurationDeserializer to replace the standard one and a LinqExtensions class that contains extensions methods to mimic functionalities present in System.Xml.* classes and not directly exposed by Linq classes.
- Open up the Silverlight solution, include the new files and verifies it compiles...here some more minor modification were needed to exclude a couple of portions of code that really aren’t available in Silverlight.
Good! It compiles! - Let’s see an overview of all the files that were modified:
All the files we touched are inside the Configuration/Interpreters folders, no other section was modified. - Let’s create a very simple Silverlight Test Application and check it on the ‘Field of Glory’:
[TestClass]
public class Tests
{private string config = "<configuration><components>" +
"<component id=\"child\" " +"service=\"SilverlightTest.Domain.IChild, SilverlightTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\" " +"type=\"SilverlightTest.Domain.Child, SilverlightTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\" />" +"<component id=\"parent\" " +"service=\"SilverlightTest.Domain.IParent, SilverlightTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\" " +"type=\"SilverlightTest.Domain.Parent, SilverlightTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\" />" +"</components></configuration>";[TestMethod]
public void ComponentRegistrationString()
{WindsorContainer container = new WindsorContainer(new XmlInterpreter(new StaticContentResource(config)));
// resolve the objectIParent p = container.Resolve<IParent>();
Assert.IsNotNull(p);
Assert.IsInstanceOfType(p, typeof(Parent));Assert.IsNotNull(p.ChildConstructorDependency);
Assert.IsInstanceOfType(p.ChildConstructorDependency, typeof(Child));Assert.IsNotNull(p.ChildProperty);
Assert.IsInstanceOfType(p.ChildProperty, typeof(Child));}
}
and let’s run it:
Cool! It works: you can now feed up the configuration even from an XML file to the Silverlight version of Castle Windsor too (you can his file from a database a web service or download it directly..that’s up to you).
What’s left to do: as I said before this whole thing started as an experiment and the code is not clean, not optimized nor checked for memory leaks. Plus actually I modified the parser classes and ‘lost’ the previous implementation (I was lazy and I didn’t wanted to write some new tests...I just reused the old ones :D). Some more work is needed to create a full new implementation (parallel to the old one) and write some additional tests for it.
Actually I see only a potential problem and that is how to deal with configuration file inclusion: we should be able to inject an external handler that tells to the processor how to retrieve the resource (we cannot have access to physical files directly (except for those on the Isolated Storage)...so this feature still need some more thinking.
Plus the actual code it’s not very clean and contains a couple of hacks I’m not very proud of and would like to get rid of :D
I think that a good move can be to create a new ‘Castle.Windsor.Configuration’ project and move the configuration code there.
Here is the full source code of this sample if you want to download and try it, it contains the full trunk portion of the Inversion Of Control section in Castle Windsor:
I will submit this code to the Castle Dev Team and see what they think about it.

Recent Comments