You can read my article at CodeProject to get the details about the interfaces I use to define data access services. In short, I have a ISession interface that is both an Abstract Factory and a Facade to methods that persist and retrieve objects.
The pattern is similar to the Service locator pattern as defined by Martin Fowler.
The data access assembly contains classes that implement the ISession interface and the interfaces for data access for the single entities. Instead of referencing this dll directly in my application model assemblies the file location is specified in the app.config file:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="DALDllFilePath"
value="C:\PathToTheDalDll\Dal.dll"/>
</appSettings>
</configuration>
Then reflection can be used to load the assembly and instantiate the ISession concrete implementation without adding a reference to the assembly containing it. This approach, though, can be expensive in terms of performance, since instantiating object and calling methods through reflection can be substantially slower then doing it directly in code.
We can get around performance issues by performing reflection just once by using a singleton object. Doing reflection just in the static constructor ensures that it is done only once at the first reference to the singleton.
We also need a static method that returns an object implementing the ISession interface.
So we need to initialize and store in the singleton an instance of a factory object implementing the interface shown below.
public interface ISessionFactory
{
ISession GetSession();
}
Having an object implementing this interface in the singleton instance enables us to define a static method that executes GetSession() and returns the ISession concrete implementation to the caller.
The static constructor will load the assembly located at the path retrieved from the config file, search for a class implementing ISessionFactory, instantiating it and storing it in a private field (sessionFactory). The code will be something like:
internal class sessionDI
{
private static readonly sessionDI instance = new sessionDI();
ISessionFactory sessionFactory;
static sessionDI()
{
}
private sessionDI()
{
Assembly dalSessionAssembly = Assembly.LoadFile(
System.Configuration.ConfigurationSettings.AppSettings
["DALDllFilePath"]);
Type[] allTypes = dalSessionAssembly.GetTypes();
foreach (Type type in allTypes)
{
if (type.IsClass && !type.IsAbstract)
{
Type iSessionImplementer
= type.GetInterface("ISessionFactory");
if (iSessionImplementer != null)
{
this.sessionFactory =
dalSessionAssembly.CreateInstance(type.FullName)
as ISessionFactory;
}
}
}
if (this.sessionFactory == null)
throw new ApplicationException("Configuration error");
}
internal static ISession GetSession()
{
return instance.sessionFactory.GetSession();
}
}
The GetSession() singleton static method will simply call the GetSession() method of the sessionFactory object singleton instance loaded at singleton construction.
Might be worth looking at Castle or Unity for something like this, too?
ReplyDeleteI like this approach using simple config node add key="DALDllFilePath"
ReplyDeletevalue="C:\PathToTheDalDll\Dal.dll"
but it has a minor/major (depending on project type) flaw. You do not specify full assembly name, therefore I assume, you load any dll that is in C:\PathToTheDalDll\ folder and has name Dal.dll. I can imagine someone simply browsing dll content (via VS Object Browser or any other tool) or disassembling it to see what kind of interfaces are implemented and classes inherited. Than that person may create its own dll and put it in that place to get access to your internal application data etc. Of course, you can avoid this obfuscating oryginal dll and treating that dll as suspicious one, giving it only necessary, minimum amount of data.
Anyway its better to use either full assembly name, with digital signature and use one private key to sign all your dll's, so any potential code injection will fail, due to lack of that key. You have that key, so you can sign any dll you want to inject post-compile into you application. If someone is about to create such plugin, you can give him that key, but you have control on what is going to be plugged in your application.
WolfMoon, thanks for your comment. The security concerns you talk about are real, especially if the context in which the application is deployed is not secure. The solutions you suggest are worth looking at. I work on line of business apps that are deployed on servers that are (or at least should be) secure, so those type of concerns are somewhat overlooked, we think that someone else takes care of it (sys admins) but maybe we are wrong.
ReplyDeleteAbout the first comment: I tried Unity, but I needed more control and simplicity. I also had some trouble at doing conditional dependency.
ReplyDeleteNot addressed in the code is concept of "context" for the newly created object. That is, default ctors are usually of minimal use except for deserialization purposes (where serialized properties will be set explicitly by reflection after creation). One can argue that a well-constructed interface should carefully segregate instantiation from initialization (e.g. define a "Init" method in the interface and invoke that). In our case, not only must the loaded class implement the interface but must also provide a well-known ctor:
ReplyDelete// access the class type
Type classType = null;
string sinkAssembly = null;
try
{
sinkAssembly = configReader.AppSetting(sinkAssemblyKey);
}
catch
{
} //try
if (!string.IsNullOrEmpty(sinkAssembly))
{
if (System.IO.File.Exists(sinkAssembly))
{
try
{
System.Reflection.Assembly sinkAssemblyObj = System.Reflection.Assembly.LoadFrom(sinkAssembly);
classType = sinkAssemblyObj.GetType(className);
}
catch
{
// nothing to do here, we'll try numerous methods
} //try
} //if
} //if
if (classType == null)
{
try
{
// try using full type name (good for GAC locations)
classType = Type.GetType(className);
}
catch (Exception ex)
{
throw new ApplicationException(
"Error retrieving user-defined class type '" + className +
"' for logger entry " + configPrefix + ": " + ex.Message,
ex
);
} //try
} //if
if (classType == null)
{
// try again just using the class name as simple type from currently executing assembly
classType = System.Reflection.Assembly.GetExecutingAssembly().GetType(
className, false, true
);
if (classType == null)
{
// last chance--try from the entry assembly
classType = System.Reflection.Assembly.GetEntryAssembly().GetType(
className, false, true
);
} //if
} //if
if (classType == null)
{
throw new ApplicationException(
"Error locating user-defined class type '" + className +
"' for logger entry " + configPrefix
);
} //if
// must implement the ILoggerSink interface
if (!typeof(ILoggerSink).IsAssignableFrom(classType))
{
throw new ApplicationException(
"User-defined logger sink '" + className + "' (" + classType.FullName +
") does not appear to implement required interface " +
typeof(ILoggerSink).FullName +
" for logger entry " + configPrefix
);
} //if
// must provide a public ctor that accepts the listed parameters. the
// third argument is passed as the classArgs parsed from the config file.
System.Reflection.ConstructorInfo classCtorInfo = null;
try
{
classCtorInfo = classType.GetConstructor(
new Type[] {
typeof(Logger),
typeof(ConfigReaders.IConfigReaderProvider),
typeof(StandardLoggerLevelMgr),
typeof(string)
}
);
}
catch (Exception ex)
{
throw new ApplicationException(
"User-defined logger sink '" + className + "' (" + classType.FullName +
") does not appear to provide required constructor matching (" +
typeof(Logger).FullName + ", " +
typeof(ConfigReaders.IConfigReaderProvider).FullName + ", " +
typeof(StandardLoggerLevelMgr).FullName + ", " +
typeof(string).FullName + ") for logger entry " + configPrefix + ": " + ex.Message,
ex
);
} //try
if (classCtorInfo == null)
{
throw new ApplicationException(
"User-defined logger sink '" + className + "' (" + classType.FullName +
") does not appear to provide required constructor matching (" +
typeof(Logger).FullName + ", " +
typeof(ConfigReaders.IConfigReaderProvider).FullName + ", " +
typeof(StandardLoggerLevelMgr).FullName + ", " +
typeof(string).FullName + ") for logger entry " + configPrefix + ": [no exception generated]"
);
} //if
// create the object
object userLoggerSink = null;
try
{
userLoggerSink = classCtorInfo.Invoke(
new object[] { this, configReaderProvider, mgr, classCtorArgs });
}
catch (Exception ex)
{
throw new ApplicationException(
"For logger entry " + configPrefix +
": Error creating instance of user-defined logger sink '" +
className + "' (" + classType.FullName + "): " + ex.Message,
ex
);
} //try
if (userLoggerSink == null)
{
throw new ApplicationException(
"For logger entry " + configPrefix +
": Failed creating instance of user-defined logger sink '" +
className + "' (" + classType.FullName + "): [no exception generated]"
);
} //if
Giorgio,
ReplyDeletePlease enlighten me, as to why do you need such a dynamic environment. I almost see the wheel being re-invented. In .NET 2.0 and above there is something called a provider. Your pattern verry similar to it. Unless you are creating an foundation for sowftware to create software, your pattern is not diferent from what is native to .NET 2.0 and on.
Andy, I'm not really sure I understood your comment... I'm instantiating my factory object just one time in the constructor of the helper singleton class. Also the stripping of indenting that blogger does really doesn't help reading code...
ReplyDeleteAnonymous looking for enlightenment.. really I'm not a "guru" that can do that :-) I just wanted a way to easily move out of code the reference to the assembly implementing data access. Anyway, I'm not aware of the provider solution in .net you talk about, I heard about MEF and it seems interesting, but maybe more for implementing plug in functionality. My solution is just the simpler way I came out with and in the shortest amount of time.
I think the provider pattern should do the trick. If there already is a pattern within the native framework why add a new one.
ReplyDeleteFurthermore if you stick with this solution please make your own configuration section by using System.Configuration. I don't think this kind of dynamic injection settings belong in the appsettings section.
To learn Dot Net Training in Chennai Dot Net Training in Chennai LINQ, you should have through understanding on Func Delegate Dot net Training Institutes in Chennai Dot Net Training Institutes in Chennai . To Know Func Delegates, you should know delegates. .Net Training in Chennai .Net Training in Chennai . To know delegates, you should get trained in Anonymous method and anonymous objects. .net training online India
ReplyDeleteDependency Injection by configuration ASP.NET MVC Training ASP.NET MVC Training The pattern is similar to the Service locator pattern as defined by Martin Fowler. ASP.NET MVC Online Training MVC Online Training class implementing ISessionFactory Online MVC Training India Chennai Online MVC Training India
ReplyDelete