View Full Version : Using relative paths to assemblies
steinard
04-17-2007, 02:58 PM
Hi!
We want to DI assemblies into our enterprise configuration (that encapsulates Spring.Net) without neccessarily referencing all these assemblies in the startup project. Is there a standard way of doing this (without having a buildsystem copying these dlls into your lib folder)?
Example:
<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net"
xmlns:db="http://www.springframework.net/database">
<description>IoC of assemblies into the main application entry.</description>
<object id="EfmModelAssemblyRef"
type="Efm.Model.EFM_ModelAssembly, EFM_ModelImpl"
factory-method="GetInstance"
location="../../libs????" />
Thanks,
Steinar.
Bruno Baia
04-17-2007, 03:37 PM
Hi,
What about using the GAC ?
- Bruno
steinard
04-17-2007, 08:05 PM
I would rather not use GAC, and I have consciously avoided GAC so far. If it was for a customer installation, then maybe...
I just thought it natural that something like this to exist for Spring's DI container. By separating interfaces and implementations into different assemblies, it is really convenient to use DI to plug in the implementation assembly(-ies) you want through configuration while the different projects are only allowed to know about the interfaces. There is no need for the startup project or even the configuration construct encapsulating the Spring.Net configuration to have these different assemblies referenced.
Thanks,
Steinar.
Erich Eichinger
04-17-2007, 08:38 PM
Hi,
Spring does - and will not - add functionality going beyond the capabilities of the underlying .NET framework. Thus we will always rely on assembly loading mechanisms provided by the CLR-runtime.
If you need to load assemblies from locations outside GAC or your application's probing path, I recommend looking into e.g. the implementation of the nunit2 nant-task. The effort they are doing there to bypass standard assembly loading will almost certainly make you search for easier solutions relying on standard mechanisms ;-)
cheers,
Erich
Erich Eichinger
04-17-2007, 08:58 PM
Additional note: If you sign your assemblies, you can load them from almost anywhere - of course you have to care about code access security related issues.
It should be easy to write an IObjectFactoryPostProcessor that will call Assembly.Load() on a list of assembly paths (of course you can use IResource abstractions for this).
<object id="myAssemblyLoader" type="MyAssemblyLoader">
<constructor-arg>
<list>
<value>http://host/path/myassembly.dll</value>
...
</list>
</constructor-arg>
</object>
-Erich
steinard
04-17-2007, 09:58 PM
Hi!
Thanks again Erich,
I just thought I should ask before I create a class like DynamicAssemblyLoader and configurate this one through spring config by injecting the paths to the assemblies to load (or even better in some situations, from URLs). And yes, I do sign all the assemblies ;)
Earlier today I thought that I could use this simple solution in such a class...
...
// This will be executed at application startup
foreach(string key in dict.Keys)
{
//Load the assembly from the specified path.
string path = dict[key];
Assembly assembly = Assembly.LoadFrom(path);
modelAssembliesDict.Add(key, assembly);
}
...
// Method that can be used by presentation layer to
// aquire an instance implementing provided interface
// type
public object CreateInstance(Type _interface)
{
Type t = FindImplementingType(_interface);
return Activator.CreateInstance(t);
}
protected Type FindImplementingType(Type _interface)
{
//Select what assembly to search through...how you would do this
// depends on what you based your key on. I like basing it on the
// contracts assembly's full name.
string key = _interface.Assembly.FullName;
Assembly assembly = modelAssembliesDict[key];
return FindImplentationInAssembly(_interface, assembly);
}
But I wonder, shouldn't assembly injection, (without registering the assemblies into gac/placing them in searchable folders which is almost similar to adding them as a reference to the startup project) from any given path, also be of interest for Spring's DI container. Not that it is hard to write something workable on my own, but I thought that this is just another aspect of DI.
Thanks,
Steinar.
Erich Eichinger
04-18-2007, 05:30 AM
Hi,
Thanks for your hint, you are right. After writing my post yesterday I came to the conclusion, that the loading mechanism could be of value.
But: I strongly recommend against a solution like your "FindImplementingType". You may run into hard to debug problems with that if you have another version of the same assembly in your application path. Than those assemblies (and contained types) will be treated as distinct.
Spring's type resolving already searches all assemblies loaded into the current AppDomain. There's no need for an additional mechanism. Thus all you have to do is to ensure, that the required assemblies get loaded into the current AppDomain.
cheers,
Erich
steinard
04-18-2007, 06:51 AM
Hi!
But: I strongly recommend against a solution like your "FindImplementingType". You may run into hard to debug problems with that if you have another version of the same assembly in your application path. Than those assemblies (and contained types) will be treated as distinct.
Good point, I guess avoiding GAC may have helped me then, we haven't had any trouble so far with finding the implementation of a type. Another thing, by always pointing to the assembly to use, and never being able to directly reference it, how could you get into versioning trouble?
Cheers,
Steinar.
Erich Eichinger
04-18-2007, 07:11 AM
Hi,
By always pointing to the assembly to use, and never being able to directly reference it, how could you get into versioning trouble?
There are a couple of possibilities:
- The assembly's might move but you overlook to update an objectdefinition
- Type A from your assembly is loaded from location A and Type B from your
assembly is loaded from location B and Type A references Type B.
- A Developer not being aware of your policy creates some
unintended direct reference.
If you really can ensure to not compiletime-reference the assembly over the application's lifetime and all assemblylocations within your objectdefinitions will always be updated synchronously, I guess don't run into troubles. But I prefer staying on the safe side and use well-documented mechanisms instead of providing my own, proprietary ones. This makes life a lot easier ;-)
cheers,
Erich
steinard
04-18-2007, 12:21 PM
Hi!
Thanks for the valuable inputs. It is always interesting to see what others think about this approach. I'll keep your be-aware points in mind, but I think the trade offs of not allowing programmers in one layer of the application to change the implementations of another layer is very important (as time constraints and time to market, for several years, have proved at least for us how refactoring debt increases due to shortcuts and thus shortening the lifetime of various applications (mainly becasue of low code quality)). Building this framework will hopefully separate concerns much better, enforece DDD and TDD. This will be a big change to the organization (along with introducing Spring.Net, AOP, NHibernate while using abstract programming techniques).
Until now none of the things you've mentioned have happened or been a problem. This might be because we are only 5 persons working with this framework, but I know that things will get messier when 20 more programmers are using it by this fall. Hopefully I've been able to reduce complexities so much by then (in the framework) that the programmers will be able to focus on business logic and related business problems, while technical infrastructural problems are all solved by the framework. I plan to introduce persons in pairs doing pair programming along with additional education, so they should know about the policies. But of course, allowing for more dynamic behaviour also increases complexity...
Anyway, I guarantee that all internal assemblies are located in the same folder, they are all always synchronously updated (by MS-Build), and if a programmer references an internal assembly in it's project directly, that assembly will be copied into the projects bin or bin/debug folder. So if type A references type B, then this will not be a problem if B's assembly can be resolved (and this is an infrastructural concern that we take care of in the framework and only done once). If this is not the case, then our reference policy has been broken, but by monitoring the different VS solutions, it is easy to identify when and/or if this happens. So far this has not been an issue.
Currently loading assemblies from any configured location works as outlined in a previous post, but in addition it should be possible to have Spring.Net create an instance of an object definition where the assembly location will be specified in the configuration and will not exist in any default execution/app paths. So now I wonder which classes in Spring.Net I need to subclass to be able to do this. I really think this should be possible.
<object id="UserService" type="Efm.Service.UserService, Efm.Service"
location="${path}\Efm.Service.dll"
factory-method="GetInstance"
/>
Any input on how I can get started on this is highly appreciated.
Thanks,
Steinar.
Mark Pollack
04-18-2007, 06:16 PM
Hi guys,
The suggestion by Erich to create an implementation of an IObjectFactoryPostProcessor that calls Assembly.Load would seem to work. My initial reaction is along the lines of Erich's, I get a very bad feeling about adding a location element to the xml config (that seems too fine grained) or introducing custom mechanisms. The poster child use-case for this is a 'plug-in' model, where a-priori different organizations will be contributing to the same 'start-up' project....but I'm not proposing anything along those lines (as one might expect looking at the eclipse model) at the moment...
Mark
Erich Eichinger
04-18-2007, 06:49 PM
Hi Steinar,
If you really want to implement custom xml-attributes for your specific needs I suggest you define your own xml-namespace and write a special parser for it.
A good starting point for investigating how this is accomplished are Spring.Remoting.RemotingConfigParser from Spring.Services and Spring.Data.DatabaseConfigParser from Spring.Data assembly. You then need to register your parser by either declaring it in your config's <parsers> section or by calling Spring.Objects.Factory.Xml.XmlParserRegistry.Regis terParser(...) at your application's entry point (before parsing starts!)
hope this helps,
Erich
steinard
04-18-2007, 07:45 PM
Hi!
Thanks for all input. I just thought of a really simple way of achieving what I want. As I can easily get hold of the various assemblies, then I could just follow up with something like this:
...
//args from config file...
public object CreateInstance(string type, string assembly)
{
Assembly assembly = ResolveAssembly(assembly);
return resolvedAssembly.CreateInstance(type);
}
...
And I guess this could be used in the configuration to declare objects I want to DI without subclassing or modifying any Spring.Net related classes.
Thanks,
Steinar.
Erich Eichinger
04-18-2007, 08:10 PM
Hi,
I still think u r on a dangerous way. I'd put it the other way round: Declare your objects as you are used to:
<object id="myobj" type="MyNamespace.MyClass, MyAssembly">
...
</object>
and implement an IObjectFactoryProcessor (to force your resolver being instantiated before any other object) that registers for the event AppDomain.AssemblyResolve and resolves assemblynames from a given list:
class MyAssemblyResolver : IObjectFactoryPostProcessor
{
// IObjectFactoryPostProcessor implementations are irrelevant for this class
private IDictionary _assemblyList;
public IDictionary Assemblies
{
set { _assemblyList = value; }
}
public MyAssemblyResolver()
{
AppDomain.CurrentDomain.AssemblyResolve +=
new ResolveEventHandler(OnAssemblyResolve);
}
Assembly OnAssemblyResolve(object sender, ResolveEventArgs args)
{
object assembly = _assemblyList[args.Name];
if (assembly == null) return null; // not found
if (assembly is string)
{ // resolve path to real assembly!
assembly = Assembly.LoadFrom((string)assembly);
_assemblyList[args.Name] = assembly;
}
return assembly;
}
}
and declare it in your config as
<object id="myAssemblyResolver" type="MyNamespace.MyAssemblyResolver">
<property name="AssemblyList">
<dictionary>
<entry key="MyAssembly" value="http://host/myassembly.dll" />
</dictionary>
</property>
</object>
This ensures:
*) you can declare your objects as you are used to - no custom attributes
*) same assemblyname will always be resolved to the same assembly
cheers,
Erich
Bruno Baia
04-18-2007, 10:02 PM
Hi,
Is there a difference with the discussion we got here :
http://forum.springframework.net/showthread.php?p=3704#post3704
- Bruno
steinard
04-18-2007, 11:42 PM
Hi!
Yes, you are right Erich. Your example is a cleaner implementation and one big sign is that I do not have to "bend" the configuration to make it call methods on my AssemblyResolver class in the xml definition.
Another thing, the documentation says:
An object factory post-processor is a class that implements the Spring.Objects.Factory.Config.IObjectFactoryPostPr ocessor interface. It is executed manually (in the case of the IObjectFactory) or automatically (in the case of the IApplicationContext) to apply changes of some sort to an entire ObjectFactory, after it has been constructed. By implementing this interface, you will receive a callback after the all the object definitions have been loaded into the IoC container but before they have been instantiated.
Looking at your example Erich, I wonder how the ResolveEventArgs object can have the correct assembly name? Will the assembly name somehow be passed to the post-processor before the IoC container tries to instantiate each object definition? I do understand that you can do something before the IoC container tries to instantiate the object definition but I do not understand how the implementor of IObjectFactoryPostProcessor receives the needed information to do the right thing. I thought that the search for the needed assembly would happen when the container tries to instantiate the object definition...
I guess I will be working with this tomorrow after noon ;)
Cheers,
Steinar.
Erich Eichinger
04-19-2007, 06:50 AM
Hi,
The "AssemblyResolve" event is raised by the CLR runtime as soon as an assemblyname needs to be resolved but can't be found by standard mechanisms (searching GAC and local app path).
The connection between your objectdefinition and AssemblyResolve is the "type" you specifiy in your objectdefinition:
<object id=".." type="MyNamespace.MyClass, MYASSEMBLY">
...
If MYASSEMBLY can't be resolved by the runtime, the AssemblyResolve event is raised with ResolveEventArgs.Name="MYASSEMBLY".
This has at least 2 implications:
*)
It means that you have to specifiy your typenames with their assemblynames - which I consider a good practice anyway as the assemblyname is an integral part of a typename in .net.
*)
AssemblyResolve is called with exactly the name you specify with your type. Thus you might write "MyType, blabla" and return "System.Web" from your AssemblyResolve handler if you want to.
-> no name, version, culture etc. handling is done automatically in this case. You need to do this manually. A simple solution is to store only the "name" part of the AssemblyName in your lookup dictionary.
cheers,
Erich
Erich Eichinger
04-19-2007, 07:14 AM
Hi,
Is there a difference with the discussion we got here :
http://forum.springframework.net/showthread.php?p=3704#post3704
these threads are related but not necessarily the same. Of course the "MyAssemblyResolver" I proposed here might help for resolving Generics parameters. But this requires that Generics parameters can be (and will be) specified including their typename:
type="AppCore2.PersistenceLayer.Ora.ClsOraProductClass<A ppCore2.BusinessObject.ClsBOProductClass, MYASSEMBLY> , AppCore2"
I don't know, if this is already possible. But - as I said - I consider specifying the assemblyname as part of the typename good practice as it is an integral part of the full typename in .NET. Thus we must ensure that this is possible in any case.
The main issue of the thread you mention was about the *loading order* of assemblies - which is an approach I would strongly discourage.
cheers,
Erich
steinard
04-19-2007, 08:03 AM
Hi!
Thank you very much for the explanation Erich. I'll try to put this in my own words to see if I've understood you correctly:
By registering for an AssemblyResolve event, then this event will be raised whenever the assembly cannot be resolved by the runtime (if the assembly could not be found in GAC or local paths). This has nothing to do with the IObjectFactoryPostProcessor interface.
The IObjectFactoryPostProcessor interface is simply ensuring that the MyAssemblyResolver class will be created first and have the needed information to retrieve assemblies before the IoC container tries to instantiate any other object definitions.
Thanks,
Steinar.
Erich Eichinger
04-19-2007, 08:10 AM
Gotcha! ;)
cheers,
Erich
vBulletin® v3.7.3, Copyright ©2000-2009, Jelsoft Enterprises Ltd.