Using IronPython to configure Castle Windsor II
Pysor Series
- Using IronPython to configure Castle Windsor I : the basic functionality
- Using IronPython to configure Castle Windsor II : parameters and references
- Using IronPython to configure Castle Windsor III : arrays and lists
In the last article I introduced a small Castle Windsor configuration tool using IronPython. This tool enabled us to add service implementation in an easer to read way. On the other hand advanced usages like optional and constructer parameters were not possible.
In this article I will continue developing Pysor (As I called it!) to accept parameters. Before introducing the new functionality I will show what are parameters and when and how would you want to use them. For the sake of demonstration I will borrow the demo application from the very good article series from Simone Busoli about Castle Windsor. If you didn’t read it then go read it all and come back.
The developed application is an html title retriever. It downloads an html string and then extract the title tag from it. For downloading the the html document it uses an I Downloader service that accepts an Uri object and downloads it if it can handle the scheme. For Example we have an HtmlDownloader, FileDownloader, etc. The other needed service is ITitleScraper which extract the <title> tag contents.
public interface IFileDownloader { string Download(Uri file); bool SupportsUriScheme(Uri file); } public interface ITitleScraper { string Scrape(string fileContents); }
For the purpose of accelerating the unit tests I changed the downloader to not really download the files but return a fake text instead.
Having the constructor :
public HtmlTitleRetriever(IFileDownloader downloader, ITitleScraper scraper) { AdditionalMessage = ""; Downloader = downloader; Scraper = scraper; }
with AdditionalMessage as a property indicates a short message, that will be concatenated with retrieved tile, we can now configure the container:
add( "parsingScraper" , ITitleScraper, StringParsingTitleScraper) add( "HttpFileDownloader", IFileDownloader, HttpFileDownloader) add( "retriver", HtmlTitleRetriever, HtmlTitleRetriever)
And everything works like expected. Now suppose we want to set the AdditionalMessage for each initiated object. Using xml configuration this could be achieved using a parameters tag containing all parameters in a dictionary-like fashion.
The most appropriate data structure for this purpose in Python would be a hash which is equivalent to a Dictionary in the .Net world.
Because C# in the current version doesn’t support optional or named parameters and because I don’t know whether Python supports method overloading I added a Python method with supply the C# method with default value for missing arguments.
def add(name, service, impl, params={}): addComponent(name, service, impl, params)
Using this new method you can now set the AdditionalMessage value for each object:
add( "retriverWithParam", HtmlTitleRetriever, HtmlTitleRetriever, {'AdditionalMessage': "Test"})
We test it with
[Test] public void CanProvideOptionalParameters() { var obj = container.Resolve<HtmlTitleRetriever>("retriverWithParam"); Assert.AreNotEqual("", obj.AdditionalMessage); }
And of course it works.
Notice that in line 4 we specified the name of the configuration node to use because have added the service HtmlTitleRetriever twice.
The other use of parameters is to specify an implementation for some service to be used. If you have for example another IDownloader implementation, that retrieves files sing the ftp protocol and we want to use this implementation for the constructor. In xml we could do it using ${name} to reference the name of an already registered service. In Pysor we can use this notation as well. But to make it more like a usual program I modified the add function to return a string to be used whenever you need to reference this implementation.
Func<string, Type, Type, IDictionary<object, object> , string> action = (name, service, impl, parameters) => { var pairs = parameters.ToList(); var reg = Component .For(service) .ImplementedBy(impl) .Named(name); if (pairs.Count > 0) { var param = (from pair in pairs select Parameter .ForKey(pair.Key.ToString()) .Eq(pair.Value.ToString()) ).ToArray(); reg = reg.Parameters(param); } container.Register(reg); return "${" + name + "}"; }; //Inject this function into IronPython runtime scope.SetVariable("addComponent", action);
And the Python wrapper function looks now like:
def add(name, service, impl, params={}): return addComponent(name, service, impl, params)
We are now ready to register a retriever that uses a concrete implementation:
ftp = add( "ftp", FtpFileDownloader, FtpFileDownloader) add( "ftpRetriver", HtmlTitleRetriever, HtmlTitleRetriever, {'downloader': ftp})
[Test] public void CanProvideSpecificImplimentationParameters() { var obj = container.Resolve<HtmlTitleRetriever>("ftpRetriver"); Assert.IsInstanceOf<FtpFileDownloader>(obj.Downloader); }
We are finished for now.
To-do's
The updated to-do list is now
- Adding a nicer API for referencing assemblies and importing namespaces.
- Adding parameters to be passed to the constructor.
- Referencing already registered implementation inside the same configuration script.
- Lifestyle management
Source Code
The source code of Pysor is available to download from GitHub
About Me
This blog is kept alive by me, Moukarram Kabbash, a programmer, hobby photographer from Dortmund in Germany.
mouk.github.com