Autofac module registration tree across layers - c#

So I have a typical three tiered application layered as below
DAL -> Repository -> Business -> Web.UI/API
I have been reading this article about registering dependencies by centralizing them via modules.
The web layer only has a reference to Business which only has a reference to the Repo which only has a reference to the lowest DAL layer. In this topology since the UI/API layer knows nothing about the Repository and has no reference to it, I can't register the modules in the Repository in the UI/API layer. Similarly I can't register the modules present in the DAL in the Business layer. What I want to do is start the registration process in the top most layer which then sets off a cascading effect of registrations in subsequent layers.
Typically what this would look like is each layer exposing a RegisterAllModules method and somehow trigger the RegisterAllModules method from the layer below it. Has something like this been done? Or is there another way to do this? At this point I don't know if I should roll my own logic out as I mentioned here above, since I don't know if there is a documented way to do something like this or not. Thoughts on how to best go forward here is what I am looking for.
Thanks.

Mmmm... I don't know if what follows is a proper response, but I'm going to try to give you the tools for a solution that suits your exact requirementes.
have you looked into json/xml module configuration? You do not need to know the assemblies through cross reference, you just need to know the name of the assemblies in app.config (or web.config). E.g: you can register one module for Repositories in the Repo assembly and one module for Business services in the Business.dll. This completely removes the need of cross-referencing the various assemblies (for Module scanning, you will still need references for method calls, but that is expected anyway). See here for details: http://docs.autofac.org/en/latest/configuration/xml.html#configuring-with-microsoft-configuration
if you want to enforce no call is done from (say) UI to Repo, you can leverage the "Instance Per Matching Lifetime Scope" function (see http://docs.autofac.org/en/latest/lifetime/instance-scope.html#instance-per-matching-lifetime-scope). You can use that registration method in order to enforce a Unit-of-work approach. E.g: a Repository can only be resolved in a "repository" LifetimeScope, and only Business components open scopes tagged "repository".
an alternative approach to tagged scopes is in using the "Instance per Owned<>" pattern. In this way, each Business service would require an Owned<Repository>.
Something like:
var builder = new ContainerBuilder();
builder.RegisterType();
builder.RegisterType().InstancePerOwned();
AFAICT, a correct approach would be to register the components through Modules, referenced by the Json/Xml config, and each Module should target specific LifetimeScopes.
When you a class calls the underlying layer, it should open a new LifetimeScope("underlying layer").
I will elaborate further, if you want advice on implementation strategies.
Best,
Alberto Chiesa
Edit:
I didn't knew the "composition root" meaning. Well, thanks for the info!
I favor a SIMPLE configuration file (be it the .config file or a separate .json or .xml file), because I feel that a list of modules to be imported is simpler done through a list than through a class. But this is opinion.
What is not an opinion is that you can import modules from assembly that are not referenced by the "Composition Root" assembly, in a simple and tested way.
So, I would go for Modules for every component registration, but for a textual configuration file for Module registration. YMMV.
Now, let me show you an example of the Unit of Work pattern that I'm using in many live projects.
In our architecture we make heavy use of a Service Layer, which holds responsibility for opening connections to the db and disposing them when finished, etc.
It's a simpler design than what you're after (I prefer shallow other than deep), but the concept is the same.
If you are "out" of the Service Layer (e.g. in an MVC Controller, or in the UI), you need a ServiceHandle in order to access the Service layer. The ServiceHandle is the only class that knows about Autofac and is responsible for service resolution, invocation and disposal.
The access to the Service Layer is done in this way:
non service classes can require only a ServiceHandle
invocation is done through _serviceHandle.Invoke(Func)
Autofac injects the ready to use handles via constructor injection.
This is done through the use of BeginLifetimeScope(tag) method, and registering services (in a module) in this way:
// register every service except for ServiceBase
Builder.RegisterAssemblyTypes(_modelAssemblies)
.Where(t => typeof(IService).IsAssignableFrom(t) && (t != typeof(ServiceBase)))
.InstancePerDependency();
// register generic ServiceHandle
Builder.RegisterGeneric(typeof(ServiceHandle<>))
.AsSelf()
.AsImplementedInterfaces()
.InstancePerDependency();
And registering every shared resource as InstancePerMatchingLifetimeScope("service")
So, an example invocation would be:
... in the constructor:
public YourUiClass(ServiceHandle<MyServiceType> myserviceHandle)
{
this._myserviceHandle = myserviceHandle;
}
... in order to invoke the service:
var result = _myserviceHandle.Invoke(s => s.myServiceMethod(parameter));
This is the ServiceHandle implementation:
/// <summary>
/// Provides a managed interface to access Model Services
/// </summary>
/// <typeparam name="TServiceType">The Type of the parameter to be managed</typeparam>
public class ServiceHandle<TServiceType> : IServiceHandle<TServiceType> where TServiceType : IService
{
static private readonly ILog Log = LogManager.GetLogger(typeof(ServiceHandle<TServiceType>));
private readonly ILifetimeScope _scope;
/// <summary>
/// True if there where Exceptions caught during the last Invoke execution.
/// </summary>
public bool ErrorCaught { get; private set; }
/// <summary>
/// List of the errors caught during execution
/// </summary>
public List<String> ErrorsCaught { get; private set; }
/// <summary>
/// Contains the exception that was thrown during the
/// last Invoke execution.
/// </summary>
public Exception ExceptionCaught { get; private set; }
/// <summary>
/// Default constructor
/// </summary>
/// <param name="scope">The current Autofac scope</param>
public ServiceHandle(ILifetimeScope scope)
{
if (scope == null)
throw new ArgumentNullException("scope");
_scope = scope;
ErrorsCaught = new List<String>();
}
/// <summary>
/// Invoke a method to be performed using a
/// service instance provided by the ServiceHandle
/// </summary>
/// <param name="command">
/// Void returning action to be performed
/// </param>
/// <remarks>
/// The implementation simply wraps the Action into
/// a Func returning an Int32; the returned value
/// will be discarded.
/// </remarks>
public void Invoke(Action<TServiceType> command)
{
Invoke(s =>
{
command(s);
return 0;
});
}
/// <summary>
/// Invoke a method to be performed using a
/// service instance provided by the ServiceHandle
/// </summary>
/// <typeparam name="T">Type of the data to be returned</typeparam>
/// <param name="command">Action to be performed. Returns T.</param>
/// <returns>A generically typed T, returned by the provided function.</returns>
public T Invoke<T>(Func<TServiceType, T> command)
{
ErrorCaught = false;
ErrorsCaught = new List<string>();
ExceptionCaught = null;
T retVal;
try
{
using (var serviceScope = GetServiceScope())
using (var service = serviceScope.Resolve<TServiceType>())
{
try
{
retVal = command(service);
service.CommitSessionScope();
}
catch (RollbackException rollbackEx)
{
retVal = default(T);
if (System.Web.HttpContext.Current != null)
ErrorSignal.FromCurrentContext().Raise(rollbackEx);
Log.InfoFormat(rollbackEx.Message);
ErrorCaught = true;
ErrorsCaught.AddRange(rollbackEx.ErrorMessages);
ExceptionCaught = rollbackEx;
DoRollback(service, rollbackEx.ErrorMessages, rollbackEx);
}
catch (Exception genericEx)
{
if (service != null)
{
DoRollback(service, new List<String>() { genericEx.Message }, genericEx);
}
throw;
}
}
}
catch (Exception ex)
{
if (System.Web.HttpContext.Current != null)
ErrorSignal.FromCurrentContext().Raise(ex);
var msg = (Log.IsDebugEnabled) ?
String.Format("There was an error executing service invocation:\r\n{0}\r\nAt: {1}", ex.Message, ex.StackTrace) :
String.Format("There was an error executing service invocation:\r\n{0}", ex.Message);
ErrorCaught = true;
ErrorsCaught.Add(ex.Message);
ExceptionCaught = ex;
Log.ErrorFormat(msg);
retVal = default(T);
}
return retVal;
}
/// <summary>
/// Performs a rollback on the provided service instance
/// and records exception data for error retrieval.
/// </summary>
/// <param name="service">The Service instance whose session will be rolled back.</param>
/// <param name="errorMessages">A List of error messages.</param>
/// <param name="ex"></param>
private void DoRollback(TServiceType service, List<string> errorMessages, Exception ex)
{
var t = new Task<string>
service.RollbackSessionScope();
}
/// <summary>
/// Creates a Service Scope overriding Session resolution:
/// all the service instances share the same Session object.
/// </summary>
/// <returns></returns>
private ILifetimeScope GetServiceScope()
{
return _scope.BeginLifetimeScope("service");
}
}
Hope it helps!

Related

autofac and owned instances

I have been reasing about owned instances and I have a need to set one up.
http://docs.autofac.org/en/latest/advanced/owned-instances.html
I have 2 methods in the same class that use this owned instance. I have set it up like this:
private readonly Func<SessionDetails, Owned<ITroposUnitOfWork>> _paramatizedTroposUnitOfWork;
My unit of work class constructor looks like this:
/// <summary>
/// Used for creating manual sessions
/// </summary>
/// <param name="sessionDetails">The details of the session to be created</param>
public TroposUnitOfWork(SessionDetails sessionDetails)
{
// Throw if we don't supply any details
ThrowIf.ArgumentIsNull(() => sessionDetails);
// Start the session
StartSession(sessionDetails);
}
So, my understanding is that if I use a using block, then the unit of work will be disposed of at the end of the call. But it isn't.
Like I mentioned before, I have 2 methods that use this owned instance. They are:
/// <summary>
/// Creates the Tropos user
/// </summary>
/// <param name="model">The user to be created</param>
/// <param name="password">The password to set</param>
private async Task CreateTroposUserAsync(User model, string password)
{
// If there is no password, throw an error
ThrowIf.ArgumentIsNull(() => password);
// Get our master user
var user = await base.FindByNameAsync(model.Master);
// If we have no master user, throw an error
if (user == null) throw new ObjectNotFoundException();
// Create our session details
var sessionDetails = _troposSession.Value.Create(user);
// User our new user
using (var troposUnitOfWork = _paramatizedTroposUnitOfWork(sessionDetails))
{
try
{
// Create our tropos user service
var userService = new TroposUserService(troposUnitOfWork.Value);
// Create our user
var transaction = userService.Create(model);
// Save our changes (Don't throw an error if the user already exists)
troposUnitOfWork.Value.RunTransaction(transaction);
} catch (Exception ex)
{
// Display human readable messages
throw new Exception(ex.Message);
}
}
// Sets the new users password
SetTroposPassword(model, password);
// Update the flag
model.HasTroposLogin = true;
}
And the other one is:
/// <summary>
/// Sets the tropos password
/// </summary>
/// <param name="model">The user that needs the password setting</param>
/// <param name="password"></param>
private void SetTroposPassword(User model, string password)
{
// Create our session details
var sessionDetails = _troposSession.Value.Create(model.UserName);
// Create our anonymous session
using (var troposUnitOfWork = _paramatizedTroposUnitOfWork(sessionDetails))
{
// Create our tropos user service
var userService = new TroposUserService(troposUnitOfWork.Value);
// Set our password
var transaction = userService.ChangePassword(password);
// Save our changes
troposUnitOfWork.Value.RunTransaction(transaction);
}
}
The first method does invoke the second method, but outside the using block. I put a breakpoint in the TroposUnitOfWork dispose method and it only gets hit once. The constructor is only hit once too.
Does anyone know why?
We need to see the initialization of _paramatizedTroposUnitOfWork.
What class has the CreateTroposUserAsync method? We need to see the constructor of that class. I suppose your overall goal is to get a unit of work implementation.
The reason why you only hit constructor once, might be due to the lifetime you used when registering. If its Owned. Then the two methods probably execute in the same lifetime scope and the dependency is only resolved once. Or put differently the _paramatizedTroposUnitOfWork(sessionDetails) returns the same instance.
I've solved similar problem using decorators and factories
public interface IEventHandlerFactory<in TNotification> where TNotification
: class, IAsyncNotification
{
IAsyncNotificationHandler<TNotification> Create( ILifetimeScope
lifetimeScope );
}
public class EventHandlerFactory<TNotification, TEventHandler> :
IEventHandlerFactory<TNotification>
where TNotification : class, IAsyncNotification
where TEventHandler : class, IAsyncNotificationHandler<TNotification>
{
public IAsyncNotificationHandler<TNotification> Create( ILifetimeScope
lifetimeScope )
{
return lifetimeScope.ResolveNamed<TEventHandler>( "EventHandler" )
as IAsyncNotificationHandler<TNotification>;
}
}
full .net fiddle is here https://dotnetfiddle.net/fw4IBw

Reporting on code execution and design patterns?

First of all I wanted to thank all of you for your continuous contributions to the Stack Overflow community! I've been a member of Stack Overflow for years and have come to rely on your input more so than any other source online. Though I try to participate and answer members' questions whenever I can, every once in a while I find myself stuck and in need of help.
Speaking of which I have an unusual code problem. I am writing an API library in C# that needs to be able to be called from WPF/Windows Forms application, but also from within Unit Test code.
The issue is that I need to be able to report (in Excel) on whether each method of the library executed properly when the API is called from within a WPF/windows forms application, along some other metadata and optionally a return type.
When the code is consumed within Unit Tests I don't really care about the reporting, but I do need to be able to produce an Assert on whether the API call executed properly or not.
For instance, if in a Unit Test we have an Test Initialize portion, one of the API calls may be to create a Domain User for the test method to use. Another one may also create a Domain Group, so that the user has proper group membership.
To accomodate the consumption of the API from WPF/WinForms, I've been rewriting every function in the API to return a OperationStep type, with the hopes that when all API calls have executed I would have an IEnumerable<OperationStep> which I can write to a CSV file.
So the question is is there an easier way of achieving what I have done so far? The reporting is extremely tedious and time consuming to code, considering that the API library consists of hundreds of similar methods. Samples are described bellow:
OperationStep<PrincipalContext> createDomainConnectionStep = DomainContext.Current.GetPrincipalContext(settings.DomainInfo);
OperationStep<UserPrincipal> createDomainUserStep = DomainContext.Current.CreateUser(createDomainConnectionStep.Context, settings.TestAccountInfo.Username, settings.TestAccountInfo.Password);
OperationStep<GroupPrincipal> createDomainGroupStep = DomainContext.Current.CreateGroup(createDomainConnectionStep.Context, settings.TestAccountInfo.UserGrupName);
Where the DomainContext is a singleton object whose functionality is to connect to the domain controller and create a user, group, and associate the user to a group.
Note that both the second and the third method call require the output of the first, and therefore warranting the need for having the public T Context within the OperationResult object as described bellow.
The OperationStep object consists of the following properties which are inherited by the IOperation interface with the exception of the public T Context.
public class OperationStep<T> : IOperation
{
/// <summary>
/// Denotes the Logical Name of the current operation
/// </summary>
public string Name { get; set; }
/// <summary>
/// Denotes the stage of execution of the current operation: Setup, Execution, Validation, Cleanup
/// </summary>
public OperationStage Stage { get; set; }
/// <summary>
/// Denotes whether the test step completed properly or failed.
/// </summary>
public OperationResult Result { get; set; }
/// <summary>
/// Denotes the return type of the test method.
/// </summary>
public T Context { get; set; }
/// <summary>
/// Denotes any other relevant information about the test step
/// </summary>
public string Description { get; set; }
/// <summary>
/// If the test step result is failed, this should have the stack trace and the error message.
/// </summary>
public string Error { get; set; }
}
The method calls themselves are a bit bloated and tedious but here is a sample.
public class DomainContext
{
private static volatile DomainContext currentContext;
private static object synchronizationToken = new object();
/// <summary>
/// default ctor.
/// </summary>
private DomainContext() { }
/// <summary>
/// Retrieves the Current DomainContext instance.
/// </summary>
public static DomainContext Current
{
get
{
if (currentContext == null)
{
lock (synchronizationToken)
{
if (currentContext == null)
{
currentContext = new DomainContext();
}
}
}
return currentContext;
}
}
/// <summary>
/// Establishes a connection to the domain.
/// </summary>
/// <param name="domainInfo"></param>
/// <returns></returns>
public OperationStep<PrincipalContext> GetPrincipalContext(DomainInfo domainInfo)
{
OperationStep<PrincipalContext> result = new OperationStep<PrincipalContext>();
result.Name = "Establish Connection to Active Directory";
result.Result = OperationResult.Success;
result.Stage = OperationStage.Setup;
result.Description = string.Format("Domain Name: {0}, Default Containter: {1}", domainInfo.FQDN, domainInfo.Container);
try
{
ContextType contextType = this.GetContextType(domainInfo.DomainType);
PrincipalContext principalContext;
try
{
principalContext = new PrincipalContext(contextType, domainInfo.FQDN, domainInfo.Container);
}
catch
{
throw new Exception("Unable to establish connection to Active Directory with the specified connection options.");
}
if (principalContext != null)
{
bool authenticationResult = principalContext.ValidateCredentials(domainInfo.Username, domainInfo.Password);
if (!authenticationResult)
{
throw new Exception("Unable to authenticate domain admin user to Active Directory.");
}
result.Context = principalContext;
result.Result = OperationResult.Success;
}
}
catch(Exception ex)
{
result.Error = ex.Message;
result.Result = OperationResult.Failure;
}
return result;
}
}
When all method calls have executed theoreticaly I should have an IEnumerable<IOperation> which in the case of a win form I can write in a csv file (to be viewed in MS Excel) or in the case of a unit test I can simply omit the extra info and ignore (other than the method executed successively and the T Context property).
If I understood you correctly - all that OperationSteps are here only for logging. Then why not enable simple .NET logging? Log needed info where it is convenient for you. You can use TraceSource with DelimetedTraceListener to write to .csv file. More than that. You can move logging logic to Strategy class and override its logging methods in your unit test so that instead of logging you call Assert methods.

MEF under IIS is not loading parts

I have problem with loading mef parts under iis. The load method looks like this:
private void LoadPlugins(string path)
{
var aggregateCatalog = new AggregateCatalog();
var directoryCatalogExe = new DirectoryCatalog(path, "*.exe");
aggregateCatalog.Catalogs.Add(directoryCatalogExe);
var container = new CompositionContainer(aggregateCatalog);
container.ComposeParts(this);
}
The method works perfectly in console application or in cassini. Under iis the parts count is 0 - no error, no exception in event log, nothing...
I have completely no idea what is going on. The path is 100% correct.
I would agree with #stakx assesment. I use a different approach to container creation to make it more environment agnostic. I create an interface:
/// <summary>
/// Defines the required contract for implementing a composition container factory.
/// </summary>
public interface ICompositionContainerFactory
{
#region Methods
/// <summary>
/// Creates an instance of <see cref="CompositionContainer"/>.
/// </summary>
/// <returns>An instance of <see cref="CompositionContainer"/>.</returns>
CompositionContainer CreateCompositionContainer();
#endregion
}
With a default implementation (which works in console apps, service hosts):
public class DefaultCompositionContainerFactory : ICompositionContainerFactory
{
#region Methods
/// <summary>
/// Creates an instance of <see cref="CompositionContainer"/>.
/// </summary>
/// <returns>
/// An instance of <see cref="CompositionContainer"/>.
/// </returns>
public CompositionContainer CreateCompositionContainer()
{
var domain = AppDomain.CurrentDomain;
string path = domain.BaseDirectory;
// Use the base directory from where the application is running.
var catalog = new DirectoryCatalog(path);
// Create the container.
var container = new CompositionContainer(catalog);
return container;
}
#endregion
}
And a web specific implementation:
public class WebCompositionContainerFactory : ICompositionContainerFactory
{
#region Methods
/// <summary>
/// Creates an instance of <see cref="CompositionContainer"/>.
/// </summary>
/// <returns>
/// An instance of <see cref="CompositionContainer"/>.
/// </returns>
public CompositionContainer CreateCompositionContainer()
{
string path = HttpRuntime.BinDirectory;
// Use the base directory from where the application is running.
var catalog = new DirectoryCatalog(path);
// Create the container.
var container = new CompositionContainer(catalog);
return container;
}
#endregion
}
Which I wire up through configuration.
The other thing to consider is that you are passing *.exe as your catalog filter, are you using executable assemblies in your web application?
One possible cause of this might be a wrong value for path.
For example, you should not assume that the current directory will be your code's "bin" directory, so passing "." might be a bad idea.
If that is what you're doing, try specifying a path based on Assembly.GetExecutingAssembly().Location:
// using System.IO;
// using System.Reflection;
string binPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
LoadPlugins(binPath);
The difference when you are running in a console app or cassini and IIS is the security context.
When running the console app or cassini, the security context is the logged on user, which is you.
When running under IIS the security context is the identity of the application pool, which by default is NETWORK SERVICE.
It is probably that your MEF parts are in a directory that NETWORK SERVICE does not have access too.

Fluent configuration of multiple registrations

I have two objects that should be registered together, and also share the same lifetime scoping. I would like to provide a registration extension to encapsulate this and retain the registration fluency, but I need some help. Here's the situation I'm in:
public static IRegistrationBuilder<?, ?, ?>
RegisterChannel<T>(this ContainerBuilder builder, Func<IComponentContext, ChannelFactory<T>> #delegate)
{
// channelfactory and sharedchannel should have same lifetime configuration
var channelfactoryreg = builder.Register(c => #delegate(c));
var sharereg = builder.RegisterType<Wcf.SharedChannel<T>>();
// is it possible to combine them and return?
return ???;
}
How do I fill in the blanks so that I can write (e.g.) builder.RegisterTwo().SingleInstance()? Is it possible to directly or indirectly "union" two IRegistrationBuilderTLAR objects, so that configuring the result configures all the underlying registrations, or is there another way to do this?
More generally: is there a primer out there for working with the Autofac internals?
Thanks for your time.
You should use a module to encapsulate registrations that make sense together.
IMHO, you should call .SingleInstance() in both registration. If they are two services they should be configured twice. If they share more commonalities in registration, you could register them with Assembly Scanning.
I find that I make increasing use of the IRegistrationSource interface for grouping together any related registrations. In your case, they would enable you to use the same registration code for an arbitrary set of types:
public class MyRegistrationSource : IRegistrationSource
{
/// <summary>
/// Gets a value indicating whether the registrations provided by this source are 1:1 adapters on top
/// of other components (I.e. like Meta, Func or Owned.)
/// </summary>
public bool IsAdapterForIndividualComponents
{
get
{
return false;
}
}
/// <summary>
/// Retrieve registrations for an unregistered service, to be used
/// by the container.
/// </summary>
/// <param name="service">The service that was requested.</param>
/// <param name="registrationAccessor">A function that will return existing registrations for a service.</param>
/// <returns>
/// Registrations providing the service.
/// </returns>
public IEnumerable<IComponentRegistration> RegistrationsFor(
Service service, Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor)
{
var swt = service as IServiceWithType;
if (swt != null)
{
// Register requested service types that pass some test or other
if (swt.ServiceType.HasAttribute<SomeAttribute>(true) ||
typeof(SomeType).IsAssignableFrom(swt.ServiceType))
{
var registration = RegistrationBuilder.ForType(swt.ServiceType)
.InstancePerDependency()
.CreateRegistration();
yield return registration;
}
}
}
}
Gosh, I hate answering my own Q, but all suggested alternatives seemed off-point or too complicated. Nick's comment came the closest to engaging, but only succeeded in scaring me off Autofac internals entirely ;). I investigated, but that was overkill for my situation.
I ended up splitting the difference (as implied in my comment above), abandoning the fluent interface, but still allowing for flexible continuation of the registration by accepting the config into the method itself:
public static void RegisterSharedChannel<T>(this ContainerBuilder builder, Func<IComponentContext, ChannelFactory<T>> #delegate,
Action<Autofac.Builder.IRegistrationBuilder<object, Autofac.Builder.IConcreteActivatorData, Autofac.Builder.SingleRegistrationStyle>> config)
{
builder.Register(c => c.Resolve<Wcf.ISharedChannel<T>>().GetChannel()).ExternallyOwned();
//would be really nice to be able to retain the fluency of the interface, but: http://stackoverflow.com/questions/8608415/fluent-configuration-of-multiple-registrations
//this should suffice for now...
var facreg = builder.Register(c => #delegate(c));
var sharereg = builder.RegisterType<Wcf.SharedChannel<T>>().AsImplementedInterfaces();
config(facreg);
config(sharereg);
}
The calling syntax isn't as pretty (e.g.:
builder.RegisterSharedChannel(c => BuildChannelFactory(...), r => r.SingleInstance());
but all (most of?) the flexibility is still there.

Best logging approach for composite app?

I am creating a Composite WPF (Prism) app with several different projects (Shell, modules, and so on). I am getting ready to implement logging, using Log4Net. It seems there are two ways to set up the logging:
Let the Shell project do all of the actual logging. It gets the reference to Log4Net, and other projects fire composite events to let the Shell know that it needs to log something. Those projects fire the events only for levels where logging is turned on in the Shell's app.config file (DEBUG, ERROR, etc), so as not to degrade performance.
Give each project, including modules, a Log4Net reference, and let the project do its own logging to a common log file, instead of sending messages to the Shell for logging.
Which is the better approach? Or, is there another approach that I should consider? Thanks for your help.
The simplest approach to logging in Prism is to override the LoggerFacade property in your Bootstrapper. By overridding the LoggerFacade, you can pass in an instance of any Logger you want with any configuration needed as long as the logger implements the ILoggerFacade interface.
I've found the following to work quite well for logging (I'm using the Enterprise Libary Logging block, but applying something similar for Log4Net should be straight forward):
Create a Boostrapper in your Shell:
-My Project
-Shell Module (add a reference to the Infrastructure project)
-Bootstrapper.cs
Create a Logging Adapter in your Infrastructure project, i.e.:
-My Project
-Infrastructure Module
-Adapters
-Logging
-MyCustomLoggerAdapter.cs
-MyCustomLoggerAdapterExtendedAdapter.cs
-IFormalLogger.cs
The MyCustomLoggerAdapter class will be used to override the 'LoggerFacade' property in the Bootstrapper. It should have a default contstructor that news everything up.
Note: by overriding the LoggerFacade property in the Bootstrapper, you are providing a logging mechanisim for Prism to use to log its own internal messages. You can use this logger throughout your application, or you can extend the logger for a more fully featured logger. (see MyCustomLoggerAdapterExtendedAdapter/IFormalLogger)
public class MyCustomLoggerAdapter : ILoggerFacade
{
#region ILoggerFacade Members
/// <summary>
/// Logs an entry using the Enterprise Library logging.
/// For logging a Category.Exception type, it is preferred to use
/// the EnterpriseLibraryLoggerAdapter.Exception methods."
/// </summary>
public void Log( string message, Category category, Priority priority )
{
if( category == Category.Exception )
{
Exception( new Exception( message ), ExceptionPolicies.Default );
return;
}
Logger.Write( message, category.ToString(), ( int )priority );
}
#endregion
/// <summary>
/// Logs an entry using the Enterprise Library Logging.
/// </summary>
/// <param name="entry">the LogEntry object used to log the
/// entry with Enterprise Library.</param>
public void Log( LogEntry entry )
{
Logger.Write( entry );
}
// Other methods if needed, i.e., a default Exception logger.
public void Exception ( Exception ex ) { // do stuff }
}
The MyCustomLoggerAdapterExtendedAdapter is dervied from the MyCustomLoggerAdapter and can provide additional constructors for a more full-fledged logger.
public class MyCustomLoggerAdapterExtendedAdapter : MyCustomLoggerAdapter, IFormalLogger
{
private readonly ILoggingPolicySection _config;
private LogEntry _infoPolicy;
private LogEntry _debugPolicy;
private LogEntry _warnPolicy;
private LogEntry _errorPolicy;
private LogEntry InfoLog
{
get
{
if( _infoPolicy == null )
{
LogEntry log = GetLogEntryByPolicyName( LogPolicies.Info );
_infoPolicy = log;
}
return _infoPolicy;
}
}
// removed backing code for brevity
private LogEntry DebugLog... WarnLog... ErrorLog
// ILoggingPolicySection is passed via constructor injection in the bootstrapper
// and is used to configure various logging policies.
public MyCustomLoggerAdapterExtendedAdapter ( ILoggingPolicySection loggingPolicySection )
{
_config = loggingPolicySection;
}
#region IFormalLogger Members
/// <summary>
/// Info: informational statements concerning program state,
/// representing program events or behavior tracking.
/// </summary>
/// <param name="message"></param>
public void Info( string message )
{
InfoLog.Message = message;
InfoLog.ExtendedProperties.Clear();
base.Log( InfoLog );
}
/// <summary>
/// Debug: fine-grained statements concerning program state,
/// typically used for debugging.
/// </summary>
/// <param name="message"></param>
public void Debug( string message )
{
DebugLog.Message = message;
DebugLog.ExtendedProperties.Clear();
base.Log( DebugLog );
}
/// <summary>
/// Warn: statements that describe potentially harmful
/// events or states in the program.
/// </summary>
/// <param name="message"></param>
public void Warn( string message )
{
WarnLog.Message = message;
WarnLog.ExtendedProperties.Clear();
base.Log( WarnLog );
}
/// <summary>
/// Error: statements that describe non-fatal errors in the application;
/// sometimes used for handled exceptions. For more defined Exception
/// logging, use the Exception method in this class.
/// </summary>
/// <param name="message"></param>
public void Error( string message )
{
ErrorLog.Message = message;
ErrorLog.ExtendedProperties.Clear();
base.Log( ErrorLog );
}
/// <summary>
/// Logs an Exception using the Default EntLib Exception policy
/// as defined in the Exceptions.config file.
/// </summary>
/// <param name="ex"></param>
public void Exception( Exception ex )
{
base.Exception( ex, ExceptionPolicies.Default );
}
#endregion
/// <summary>
/// Creates a LogEntry object based on the policy name as
/// defined in the logging config file.
/// </summary>
/// <param name="policyName">name of the policy to get.</param>
/// <returns>a new LogEntry object.</returns>
private LogEntry GetLogEntryByPolicyName( string policyName )
{
if( !_config.Policies.Contains( policyName ) )
{
throw new ArgumentException( string.Format(
"The policy '{0}' does not exist in the LoggingPoliciesCollection",
policyName ) );
}
ILoggingPolicyElement policy = _config.Policies[policyName];
var log = new LogEntry();
log.Categories.Add( policy.Category );
log.Title = policy.Title;
log.EventId = policy.EventId;
log.Severity = policy.Severity;
log.Priority = ( int )policy.Priority;
log.ExtendedProperties.Clear();
return log;
}
}
public interface IFormalLogger
{
void Info( string message );
void Debug( string message );
void Warn( string message );
void Error( string message );
void Exception( Exception ex );
}
In the Bootstrapper:
public class MyProjectBootstrapper : UnityBootstrapper
{
protected override void ConfigureContainer()
{
// ... arbitrary stuff
// create constructor injection for the MyCustomLoggerAdapterExtendedAdapter
var logPolicyConfigSection = ConfigurationManager.GetSection( LogPolicies.CorporateLoggingConfiguration );
var injectedLogPolicy = new InjectionConstructor( logPolicyConfigSection as LoggingPolicySection );
// register the MyCustomLoggerAdapterExtendedAdapter
Container.RegisterType<IFormalLogger, MyCustomLoggerAdapterExtendedAdapter>(
new ContainerControlledLifetimeManager(), injectedLogPolicy );
}
private readonly MyCustomLoggerAdapter _logger = new MyCustomLoggerAdapter();
protected override ILoggerFacade LoggerFacade
{
get
{
return _logger;
}
}
}
Finally, to use either logger, all you need to do is add the appropriate interface to your class' constructor and the UnityContainer will inject the logger for you:
public partial class Shell : Window, IShellView
{
private readonly IFormalLogger _logger;
private readonly ILoggerFacade _loggerFacade;
public Shell( IFormalLogger logger, ILoggerFacade loggerFacade )
{
_logger = logger;
_loggerFacade = loggerFacade
_logger.Debug( "Shell: Instantiating the .ctor." );
_loggerFacade.Log( "My Message", Category.Debug, Priority.None );
InitializeComponent();
}
#region IShellView Members
public void ShowView()
{
_logger.Debug( "Shell: Showing the Shell (ShowView)." );
_loggerFacade.Log( "Shell: Showing the Shell (ShowView).", Category.Debug, Priority.None );
this.Show();
}
#endregion
}
I don't think you need a separate module for the logging policy. By adding the logging policies to your infrastructure module, all other modules will get the required references (assuming you add the infrastructure module as a reference to your other modules). And by adding the logger to your Boostrapper, you can let the UnityContainer inject the logging policy as needed.
There is a simple example of uisng Log4Net on the CompositeWPF contrib project on CodePlex as well.
HTH's
I finally got back to this one, and it turns out the answer is really pretty simple. In the Shell project, configure Log4Net as a custom logger. The Prism Documentation (Feb. 2009) explains how to do that at p. 287). The Shell project is the only project that needs a reference to Log4Net. To access the logger (assuming all modules are passed a reference to the Prism IOC container), simply resolve ILoggerFacade in the IOC container, which will give you a reference to your custom logger. Pass a message to this logger in the normal manner.
So, there is no need for any eventing back to the Shell, and no need for modules to have Log4Net references. Holy mackerel, I love IOC containers!
The problem with LoggerFacade, suggested above, is that the non prism parts of your app wouldn't know about it. Logger IMHO needs to be more low level and more universally accessible than just within the Composite framework.
My suggestion is, why not just rely on standard Debug/Trace and implement your own TraceListener. This way it will work well for both Prism/nonPrism parts. You can achieve desired level of flexibility with this.
Having separate logger configurations for each module might turn into problems at deployment. Remember that a power user or administrator may completely change the target of your logging, redirecting to a database or to a central repository aggregated logging service (like my company's one). If all separate modules have separate configurations, the power user/admin has to repeat the configuration for each module (in each .config file, or in each module's section in the main app.config), and repeat this every time a change in location/formatting occurs. And besides, given that the appenders are added at run time from configuration and there may be appenders you don't know anything about at the moment, someone may use an appender that locks the file and result in conflict between the app modules. Hsving one single log4.net config simplifies administration.
Individual modules can still be configure as for the needs of each one, separately (eg. INFO for DB layer, ERROR for UI layer). Each module would get the logger by asking for its own type: LogManager.GetLogger(typeof(MyModule); but only the Shell will configure the logger (eg. call XmlConfigurator.Configure), using its own app.config.

Categories

Resources