We have started using Ninject version 2 as our IoC container along with the extension for resolving by naming conventions. We are also using log4net for our logging.
What I would like is for Ninject to log all the dependencies it has found and what will resolve them to, preferably on the application start-up.
I have found the logging extension, but can't find documentation or examples on how to use it to get this.
Edit:
Since it was requested here is the class that logs the default bindings on startup, using log4net
public class DefaultBindingGeneratorWithLogging : IBindingGenerator
{
private readonly IKernel kernel;
/// <summary>
/// Initializes a new instance of the <see cref="DefaultBindingGeneratorWithLogging"/> class.
/// </summary>
/// <param name="kernel">The kernel.</param>
public DefaultBindingGeneratorWithLogging(IKernel kernel)
{
this.kernel = kernel;
}
/// <summary>
/// Creates the bindings for a type.
/// </summary>
/// <param name="type">The type for which the bindings are created.</param>
/// <param name="bindingRoot">The binding root that is used to create the bindings.</param>
/// <returns>
/// The syntaxes for the created bindings to configure more options.
/// </returns>
public IEnumerable<IBindingWhenInNamedWithOrOnSyntax<object>> CreateBindings(Type type, IBindingRoot bindingRoot)
{
if (type.IsInterface || type.IsAbstract)
{
yield break;
}
Type interfaceForType = type.GetInterface("I" + type.Name, false);
if (interfaceForType == null)
{
yield break;
}
var log = kernel.Get<ILog>();
if (!(kernel.GetBindings(interfaceForType).Any()))
{
bindingRoot.Bind(interfaceForType).To(type).InTransientScope();
if (log.IsInfoEnabled && !String.IsNullOrWhiteSpace(interfaceForType.FullName))
{
log.InfoFormat("Mapping {0} -> {1}", type.FullName, interfaceForType.FullName);
}
}
else
{
log.InfoFormat("Didn't map {0} -> {1} mapping already exists", type.FullName, interfaceForType.FullName);
}
}
}
You can probably achieve what you are trying to do by creating your own instance of 'ActivationStrategy'. Here is one that I was using to monitor activation / deactivation:
public class MyMonitorActivationStrategy : ActivationStrategy
{
private ILogger _logger;
public override void Activate(Ninject.Activation.IContext context, Ninject.Activation.InstanceReference reference)
{
if(reference.Instance is ILogger)
{
_logger = (ILogger)reference.Instance;
}
_logger.Debug("Ninject Activate: " + reference.Instance.GetType());
base.Activate(context, reference);
}
public override void Deactivate(Ninject.Activation.IContext context, Ninject.Activation.InstanceReference reference)
{
_logger.Debug("Ninject DeActivate: " + reference.Instance.GetType());
base.Deactivate(context, reference);
}
}
You wire this when you create your kernel.
protected override IKernel CreateKernel()
{
var kernel = new StandardKernel();
kernel.Components.Add<IActivationStrategy, MyMonitorActivationStrategy>();
kernel.Load<AppModule>();
return kernel;
}
Hope this helps.
Bob
Related
My ASP.NET application runs fine in local instance of VS 2012 but not in VS 2015. It is throwing
Systems.Collections.Generic.KeyNotFoundException occurred in mscorlib.dll but not handled in user code
The application also runs fine when deployed on the server but issue occurs only in local machine - NHibernate session key being null and breaks on the following line.
var sessionInitializer = map[sessionFactory]
My code is pasted below and I am following similar implementation as in the following link (I have everything up until the IWindsorInstaller section from the link) -
http://nhibernate.info/blog/2011/03/02/effective-nhibernate-session-management-for-web-apps.html
public class LazyNHWebSessionContext : ICurrentSessionContext
{
private readonly ISessionFactoryImplementor factory;
private const string CurrentSessionContextKey =
"NHibernateCurrentSession";
public LazyNHWebSessionContext(ISessionFactoryImplementor factory)
{
this.factory = factory;
}
/// <summary>
/// Retrieve the current session for the session factory.
/// </summary>
/// <returns></returns>
public ISession CurrentSession()
{
Lazy<ISession> initializer;
var currentSessionFactoryMap = GetCurrentFactoryMap();
if (currentSessionFactoryMap == null ||
!currentSessionFactoryMap.TryGetValue(factory, out initializer))
{
return null;
}
return initializer.Value;
}
/// <summary>
/// Bind a new sessionInitializer to the context of the sessionFactory.
/// </summary>
/// <param name="sessionInitializer"></param>
/// <param name="sessionFactory"></param>
public static void Bind(Lazy<ISession> sessionInitializer, ISessionFactory sessionFactory)
{
var map = GetCurrentFactoryMap();
map[sessionFactory] = sessionInitializer;
}
/// <summary>
/// Unbind the current session of the session factory.
/// </summary>
/// <param name="sessionFactory"></param>
/// <returns></returns>
public static ISession UnBind(ISessionFactory sessionFactory)
{
var map = GetCurrentFactoryMap();
var sessionInitializer = map[sessionFactory];
map[sessionFactory] = null;
if (sessionInitializer == null || !sessionInitializer.IsValueCreated) return null;
return sessionInitializer.Value;
}
/// <summary>
/// Provides the CurrentMap of SessionFactories.
/// If there is no map create/store and return a new one.
/// </summary>
/// <returns></returns>
private static IDictionary<ISessionFactory, Lazy<ISession>> GetCurrentFactoryMap()
{
//var contextItem = HttpContext.Current.Items[CurrentSessionContextKey];
if (HttpContext.Current == null) return null;
var currentFactoryMap = (IDictionary<ISessionFactory, Lazy<ISession>>)
HttpContext.Current.Items[CurrentSessionContextKey];
if (currentFactoryMap == null)
{
currentFactoryMap = new Dictionary<ISessionFactory, Lazy<ISession>>();
HttpContext.Current.Items[CurrentSessionContextKey] = currentFactoryMap;
}
return currentFactoryMap;
}
}
My silverlight project has references to the RIA Services Silverlight Client and my .web application has access to RIAServices.Server.
I can't seem to find a good tutorial to learn how to connect these. Once I understand how to get the data from my NumberGenerator method. I can pick it up from there. I have three questions that I need some help with.
First, am I setting up this project correctly? I never done a project with RIA before.
Secondly what reference do I need to use ServiceContract(), FaultContract, and OperationContract? Most examples show that they are in the System.ServiceModel library. In this case with using the external library RIA Services Silverlight Client that is not the case. It throws a error saying I am missing a reference. What other libraries would have those three in it?
My last question is what URI do you use in the SystemDomainContext? Where I found the code at used this MyFirstRIAApplication-Web-EmployeeDomainService.svc but in my case I don't have anything that has a .svc extension. Would I use the .xap or .aspx?
Here is my Silverlight code
Here is my NumberGenerator Silverlight UserControl Page Code
public partial class NumberGenerator : UserControl
{
public NumberGenerator()
{
InitializeComponent();
GenerateNumber();
}
private void GenerateButton_Click(object sender, RoutedEventArgs e)
{
GenerateNumber();
}
private void GenerateNumber()
{
int result = 0
SystemClientServices systemclientservices = SystemClientServices.Instance;
result = systemclientservices.GenerateNumber();
NumberLabel.Content = result;
}
}
Here is my System Client Services class
private readonly static SystemClientServices _instance = new SystemClientServices();
private SystemDomainContext _domainContext = new SystemDomainContext();
private SystemClientServices() { }
public static SystemClientServices Instance
{
get
{
return _instance;
}
}
public int GenerateNumber()
{
//Code goes here to get the information from the Domainservices
LoadOperation load = this._domainContext.Load(this._domainContext.GetNumberGeneratorQuery(), LoadBehavior.KeepCurrent, false);
return Convert.ToInt32(load.Entities);
}
Here is my NumberGenerator class on the silverlight local project
public sealed partial class NumberGenerator : Entity
{
private static readonly NumberGenerator _instance = new NumberGenerator();
public int NumberGenerated { get; set; }
public NumberGenerator()
{
NumberGenerated = 0;
}
public static NumberGenerator Instance
{
get
{
return _instance;
}
}
}
Here is System Domain Conext class on the silverlight local project
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel.DomainServices;
using System.ServiceModel.DomainServices.Client;
using System.ServiceModel.DomainServices.Client.ApplicationServices;
using System.ServiceModel.Web;
using System.ServiceModel;
public sealed partial class SystemDomainConext : DomainContext
{
#region Extensibility Method Definitions
/// <summary>
/// This method is invoked from the constructor once initialization is complete and
/// can be used for further object setup.
/// </summary>
partial void OnCreated();
#endregion
/// <summary>
/// Initializes a new instance of the <see cref="EmployeeDomainContext"/> class.
/// </summary>
public SystemDomainConext() : this(new WebDomainClient<ISystemDomainServiceContract>(new Uri("MyFirstRIAApplication-Web-EmployeeDomainService.svc", UriKind.Relative)))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="EmployeeDomainContext"/> class with the specified service URI.
/// </summary>
/// <param name="serviceUri">The EmployeeDomainService service URI.</param>
public SystemDomainConext(Uri serviceUri) : this(new WebDomainClient<ISystemDomainServiceContract>(serviceUri))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="EmployeeDomainContext"/> class with the specified <paramref name="domainClient"/>.
/// </summary>
/// <param name="domainClient">The DomainClient instance to use for this DomainContext.</param>
public SystemDomainConext(DomainClient domainClient) : base(domainClient)
{
this.OnCreated();
}
/// <summary>
/// Gets the set of <see cref="Employee"/> entity instances that have been loaded into this <see cref="EmployeeDomainContext"/> instance.
/// </summary>
public EntitySet<NumberGenerator> GeneratedNumber
{
get
{
return base.EntityContainer.GetEntitySet<NumberGenerator>();
}
}
/// <summary>
/// Gets an EntityQuery instance that can be used to load <see cref="Employee"/> entity instances using the 'GetEmployee' query.
/// </summary>
/// <returns>An EntityQuery that can be loaded to retrieve <see cref="Employee"/> entity instances.</returns>
public EntityQuery<NumberGenerator> GetNumberGeneratorQuery()
{
this.ValidateMethod("GetGeneratedNumber", null);
return base.CreateQuery<NumberGenerator>("GetNumberGenerator", null, false, true);
}
/// <summary>
/// Creates a new EntityContainer for this DomainContext's EntitySets.
/// </summary>
/// <returns>A new container instance.</returns>
protected override EntityContainer CreateEntityContainer()
{
return new NumberGeneratorDomainContextEntityContainer();
}
/// <summary>
/// Service contract for the 'EmployeeDomainService' DomainService.
/// </summary>
[ServiceContract()]
public interface ISystemDomainServiceContract
{
/// <summary>
/// Asynchronously invokes the 'GetEmployee' operation.
/// </summary>
/// <param name="callback">Callback to invoke on completion.</param>
/// <param name="asyncState">Optional state object.</param>
/// <returns>An IAsyncResult that can be used to monitor the request.</returns>
[FaultContract(typeof(DomainServiceFault), Action="http://tempuri.org/EmployeeDomainService/GetEmployeeDomainServiceFault", Name="DomainServiceFault", Namespace="DomainServices")]
[OperationContract(AsyncPattern=true, Action="http://tempuri.org/EmployeeDomainService/GetEmployee", ReplyAction="http://tempuri.org/EmployeeDomainService/GetEmployeeResponse")]
[WebGet()]
IAsyncResult GetGeneratedNumber(AsyncCallback callback, object asyncState);
/// <summary>
/// Completes the asynchronous operation begun by 'BeginGetEmployee'.
/// </summary>
/// <param name="result">The IAsyncResult returned from 'BeginGetEmployee'.</param>
/// <returns>The 'QueryResult' returned from the 'GetEmployee' operation.</returns>
QueryResult<NumberGenerator> EndGetGeneratedNumber(IAsyncResult result);
/// <summary>
/// Asynchronously invokes the 'SubmitChanges' operation.
/// </summary>
/// <param name="changeSet">The change-set to submit.</param>
/// <param name="callback">Callback to invoke on completion.</param>
/// <param name="asyncState">Optional state object.</param>
/// <returns>An IAsyncResult that can be used to monitor the request.</returns>
[FaultContract(typeof(DomainServiceFault), Action="http://tempuri.org/EmployeeDomainService/SubmitChangesDomainServiceFault", Name="DomainServiceFault", Namespace="DomainServices")]
[OperationContract(AsyncPattern=true, Action="http://tempuri.org/EmployeeDomainService/SubmitChanges", ReplyAction="http://tempuri.org/EmployeeDomainService/SubmitChangesResponse")]
IAsyncResult BeginSubmitChanges(IEnumerable<ChangeSetEntry> changeSet, AsyncCallback callback, object asyncState);
/// <summary>
/// Completes the asynchronous operation begun by 'BeginSubmitChanges'.
/// </summary>
/// <param name="result">The IAsyncResult returned from 'BeginSubmitChanges'.</param>
/// <returns>The collection of change-set entry elements returned from 'SubmitChanges'.</returns>
IEnumerable<ChangeSetEntry> EndSubmitChanges(IAsyncResult result);
}
internal sealed class NumberGeneratorDomainContextEntityContainer : EntityContainer
{
public NumberGeneratorDomainContextEntityContainer()
{
this.CreateEntitySet<NumberGenerator>(EntitySetOperations.Edit);
}
}
}
Here is my System Domain Services class that is on the Silverlight.web
[EnableClientAccess()]
public class SystemDomainServices : DomainService
{
private NumberGenerator numberGenerator = NumberGenerator.Instance;
public int NumberGenerate()
{
return numberGenerator.NumberGenerated;
}
}
Here is the NumberGenerator class on the silverlight.web
private static readonly NumberGenerator _instance = new NumberGenerator();
[Key]
public int NumberGenerated { get; set; }
public NumberGenerator()
{
NumberGenerated = GenerateNumber();
}
public static NumberGenerator Instance
{
get
{
return _instance;
}
}
public int GenerateNumber()
{
string db_date = "";
int db_num = 0;
string todaysdate = "";
int temp_num = db_num;
int result = 0;
using (SqlConnection connection = new SqlConnection(DBConnectionString))
{
connection.Open();
using (SqlCommand command = new SqlCommand("SELECT * FROM table", connection))
{
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
db_date = reader.GetString(0);
db_num = (int)(reader.GetSqlInt32(1));
todaysdate = DateTime.Now.ToString("yMMdd");
temp_num = db_num;
}
reader.Close();
}
if (todaysdate != db_date)
{
using (SqlCommand dateUpate = new SqlCommand("UPDATE table SET tsDate='" + todaysdate + "' WHERE tsDate='" + db_date + "'", connection))
{
dateUpate.ExecuteNonQuery();
}
db_num = 0;
db_date = todaysdate;
}
db_num++;
using (SqlCommand numUpate = new SqlCommand("UPDATE table SET tsNum='" + db_num + "' WHERE tsNum='" + temp_num + "'", connection))
{
numUpate.ExecuteNonQuery();
}
result = Convert.ToInt32(db_date + db_num.ToString().PadLeft(3, '0'));
connection.Close();
connection.Dispose();
}
return result;
}
}
The answer to question two you might be have to go to Tools at the top, NuGet Package Manager, Package Management Console, and then type the following command to install the package for those three functions.
PM> Install-Package RIAServices.Silverlight.DomainDataSource
I have the following setup: A console application, and a services project with a range of services.
I am trying to use Windsor Castle to install specific services in the application, depending on their namespace in the application.
I have got the following to work fine in my application:
Container.Install(Castle.Windsor.Installer.Configuration.FromXmlFile("components.config"));
However, I have trouble getting the "register all service with specific namespace to work".
In a web application I have made previously, I got the following code to work. However, it seems that when I use Container.Register in my console application, I cannot resolve the services later on.
Code I've previously used in a web application:
// All services in service DLL
var assembly = Assembly.LoadFrom(Server.MapPath("~/bin/LetterAmazer.Business.Services.dll"));
;
Container.Register(
Classes.FromAssembly(assembly)
.InNamespace("LetterAmazer.Business.Services.Services")
.WithServiceAllInterfaces());
Container.Register(
Classes.FromAssembly(assembly)
.InNamespace("LetterAmazer.Business.Services.Services.FulfillmentJobs")
.WithServiceAllInterfaces());
Container.Register(
Classes.FromAssembly(assembly)
.InNamespace("LetterAmazer.Business.Services.Services.PaymentMethods.Implementations")
.WithServiceAllInterfaces());
// All factories in service DLL
Container.Register(
Classes.FromAssembly(assembly)
.InNamespace("LetterAmazer.Business.Services.Factory")
.WithServiceAllInterfaces());
Container.Register(Component.For<LetterAmazerEntities>());
When I use the Container.Register code in my console application, I cannot do the following:
orderService = ServiceFactory.Get<IOrderService>();
fulfillmentService = ServiceFactory.Get<IFulfillmentService>();
As I get a "service could not be resolved".
The code is run in a BackgroundService class, with the following code:
public void Start()
{
logger.Info("Starting Background Service...");
Container.Install(Castle.Windsor.Installer.Configuration.FromXmlFile("components.config"));
ISchedulerFactory scheduleFactory = new StdSchedulerFactory();
scheduler = scheduleFactory.GetScheduler();
scheduler.Start();
ThreadStart threadStart = new System.Threading.ThreadStart(ScheduleJobs);
new Thread(threadStart).Start();
logger.Info("DONE!");
}
So to sum it all up: how do register all services / classes with a specific namespace, in an external DLL, in a console application?
EDIT:
ServiceFactory.cs:
public class ServiceFactory
{
private static ServiceFactory instance = new ServiceFactory();
private IWindsorContainer container = new WindsorContainer();
private ServiceFactory()
{
}
/// <summary>
/// Gets the container.
/// </summary>
/// <value>The container.</value>
public static IWindsorContainer Container
{
get { return instance.container; }
}
/// <summary>
/// Gets this instance.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static T Get<T>()
{
return instance.container.Resolve<T>();
}
/// <summary>
/// Gets all this instance.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static IList<T> GetAll<T>()
{
return instance.container.ResolveAll<T>();
}
/// <summary>
/// Gets the specified name.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="name">The name.</param>
/// <returns></returns>
public static T Get<T>(string name)
{
return (T)instance.container.Resolve<T>(name);
}
/// <summary>
/// Gets all the specified name.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="name">The name.</param>
/// <returns></returns>
public static IList<T> GetAll<T>(string name)
{
return instance.container.ResolveAll<T>(name);
}
}
EDIT-2
If I try to make a resolve in the BackgroundService, after registering the services to my container, I get a:
An unhandled exception of type 'Castle.MicroKernel.ComponentNotFoundException' occurred in Castle.Windsor.dll
Additional information: No component for supporting the service LetterAmazer.Business.Services.Domain.Orders.IOrderService was found
However, I can see that in debug, the IOrderService exists in the "All services" in my container.
EDIT-3
public interface IOrderService
{
Order Create(Order order);
Order Update(Order order);
void UpdateByLetters(IEnumerable<Letter> letters);
List<Order> GetOrderBySpecification(OrderSpecification specification);
Order GetOrderById(int orderId);
Order GetOrderById(Guid orderId);
void Delete(Order order);
List<OrderLine> GetOrderLinesBySpecification(OrderLineSpecification specification);
void ReplenishOrderLines(Order order);
}
EDIT-4: Order service
Note: some of the implementation has been cut
public class OrderService : IOrderService
{
private IOrderFactory orderFactory;
private LetterAmazerEntities repository;
private ILetterService letterService;
private ICustomerService customerService;
public OrderService(LetterAmazerEntities repository,
ILetterService letterService,
IOrderFactory orderFactory, ICustomerService customerService)
{
this.repository = repository;
this.letterService = letterService;
this.orderFactory = orderFactory;
this.customerService = customerService;
}
public Order Create(Order order)
{
DbOrders dborder = new DbOrders();
foreach (var orderLine in order.OrderLines)
{
var dbOrderLine = setOrderline(orderLine);
dborder.DbOrderlines.Add(dbOrderLine);
}
dborder.Guid = Guid.NewGuid();
dborder.OrderCode = GenerateOrderCode();
dborder.OrderStatus = (int)OrderStatus.Created;
dborder.DateCreated = DateTime.Now;
dborder.DateUpdated = DateTime.Now;
dborder.PaymentMethod = "";
dborder.CustomerId = order.Customer != null ? order.Customer.Id : 0;
Price price = new Price();
price.PriceExVat = order.CostFromLines();
price.VatPercentage = order.Customer.VatPercentage();
order.Price = price;
dborder.Total = order.Price.Total;
dborder.VatPercentage = order.Price.VatPercentage;
dborder.PriceExVat = order.Price.PriceExVat;
repository.DbOrders.Add(dborder);
repository.SaveChanges();
return GetOrderById(dborder.Id);
}
public Order GetOrderById(Guid orderId)
{
DbOrders dborder = repository.DbOrders.FirstOrDefault(c => c.Guid == orderId);
if (dborder == null)
{
throw new ItemNotFoundException("Order");
}
var lines = repository.DbOrderlines.Where(c => c.OrderId == dborder.Id).ToList();
var order = orderFactory.Create(dborder, lines);
return order;
}
public Order GetOrderById(int orderId)
{
DbOrders dborder = repository.DbOrders.FirstOrDefault(c => c.Id == orderId);
if (dborder == null)
{
throw new ItemNotFoundException("Order");
}
var lines = repository.DbOrderlines.Where(c => c.OrderId == orderId).ToList();
var order = orderFactory.Create(dborder, lines);
return order;
}
public void Delete(Order order)
{
var dborder = repository.DbOrders.FirstOrDefault(c => c.Id == order.Id);
repository.DbOrders.Remove(dborder);
repository.SaveChanges();
}
public void DeleteOrder(Order order)
{
var dborder = repository.DbOrders.FirstOrDefault(c => c.Id == order.Id);
repository.DbOrders.Remove(dborder);
repository.SaveChanges();
}
#endregion
}
I think you can do the same thing in a console application too. You just have to create the assembly instance. Considering that the assembly is in the same folder, this should work:
Assembly assembly = Assembly.LoadFrom("MyAssembly.dll");
Container.Register(
Classes.FromAssembly(assembly)
.InNamespace("MyNamespace")
.WithServiceAllInterfaces());
Consider the sample code below consisting of a Class Library design and an executable Program using the library.
namespace AppLib
{
/// <summary>
/// Entry point for library. Stage manages all the actors in the logic.
/// </summary>
class StageApp
{
/// <summary>
/// Setting that is looked up by different actors
/// </summary>
public int SharedSetting { get; set; }
/// <summary>
/// Stage managing actors with app logic
/// </summary>
public IEnumerable<Actor> Actors { get { return m_actors.Where(x => x.Execute() > 40).ToArray(); } }
private List<Actor> m_actors = new List<Actor>();
}
/// <summary>
/// An object on the stage. Refers to stage (shared)settings and execute depending on the settings.
/// Hence actor should have reference to stage
/// </summary>
class Actor
{
private StageApp m_StageApp;
private int m_Property;
/// <summary>
/// An actor that needs to refer to stage to know what behavior to execute
/// </summary>
/// <param name="stage"></param>
public Actor(StageApp stage)
{
m_StageApp = stage;
m_Property = new Random().Next();
}
/// <summary>
/// Execute according to stage settings
/// </summary>
/// <returns></returns>
public int Execute()
{
return m_StageApp.SharedSetting * m_Property;
}
}
}
namespace AppExe
{
using AppLib;
class Program
{
static void Main(string[] args)
{
StageApp app = new StageApp();
app.SharedSetting = 5;
// Question: How to add actor to stage?
foreach (var actor in app.Actors)
Console.WriteLine(actor.Execute());
}
}
}
Question
Stage and Actor have circular dependency and seems bad to me.
For example, how should we add actors to stage?
If I let user to create new Actor() themselves,
then they must keep on supplying the Stage.
If I give Actor() an internal constructor and make Stage a factory,
then I lose some of the flexibility for users to do making inherited Actors.
If I make Stage a singleton, then I can only have one set of SharedSetting.
In case the user wants more than one Stage in his AppExe, then it cannot be done.
Is there anyway to redesign the architecture so as to avoid the problems above?
If your functionality is not limited by sharing the StageApp settings between actors, but also will be some other logic. For example when you need to know parent StageApp from Actor and vice versa. I preffer to implement it in this way:
namespace AppLib
{
/// <summary>
/// Entry point for library. Stage manages all the actors in the logic.
/// </summary>
class StageApp
{
/// <summary>
/// Setting that is looked up by different actors
/// </summary>
public int SharedSetting { get; set; }
/// <summary>
/// Stage managing actors with app logic
/// </summary>
public IEnumerable<Actor> Actors { get { return m_actors.Where(x => x.Execute() > 40).ToArray(); } }
private List<Actor> m_actors = new List<Actor>();
public int TotalActorsCount
{
get
{
return m_actors.Count;
}
}
public void AddActor(Actor actor)
{
if (actor == null)
throw new ArgumentNullException("actor");
if (m_actors.Contains(actor))
return; // or throw an exception
m_actors.Add(actor);
if (actor.Stage != this)
{
actor.Stage = this;
}
}
// we are hiding this method, to avoid because we can change Stage only to another non null value
// so calling this method directly is not allowed
internal void RemoveActor(Actor actor)
{
if (actor == null)
throw new ArgumentNullException("actor");
if (!m_actors.Contains(actor))
return; // or throuw exception
m_actors.Remove(actor);
}
}
/// <summary>
/// An object on the stage. Refers to stage (shared)settings and execute depending on the settings.
/// Hence actor should have reference to stage
/// </summary>
class Actor
{
private StageApp m_StageApp;
private int m_Property;
public StageApp Stage
{
get
{
return m_StageApp;
}
set
{
if (value == null)
{
throw new ArgumentNullException("value");
}
if (m_StageApp != value)
{
if (m_StageApp != null) // not a call from ctor
{
m_StageApp.RemoveActor(this);
}
m_StageApp = value;
m_StageApp.AddActor(this);
}
}
}
/// <summary>
/// An actor that needs to refer to stage to know what behavior to execute
/// </summary>
/// <param name="stage"></param>
public Actor(StageApp stage)
{
Stage = stage;
m_Property = new Random().Next();
}
/// <summary>
/// Execute according to stage settings
/// </summary>
/// <returns></returns>
public int Execute()
{
return m_StageApp.SharedSetting * m_Property;
}
}
}
namespace AppExe
{
using AppLib;
class Program
{
static void Main(string[] args)
{
StageApp app = new StageApp();
app.SharedSetting = 5;
StageApp anotherApp = new StageApp();
anotherApp.SharedSetting = 6;
// actor is added to the stage automatically after instantiation
Actor a1 = new Actor(app);
Actor a2 = new Actor(app);
Actor a3 = new Actor(anotherApp);
Console.WriteLine("Actors in anotherApp before moving actor:");
Console.WriteLine(anotherApp.TotalActorsCount);
// or by calling method from StageApp class
anotherApp.AddActor(a1);
Console.WriteLine("Actors in anotherApp after calling method (should be 2):");
Console.WriteLine(anotherApp.TotalActorsCount);
// or by setting Stage through property
a2.Stage = anotherApp;
Console.WriteLine("Actors in anotherApp after setting property of Actor instance (should be 3):");
Console.WriteLine(anotherApp.TotalActorsCount);
Console.WriteLine("Actors count in app (should be empty):");
Console.WriteLine(app.TotalActorsCount);
}
}
}
It allows to you to manipulate with object relationships transparently, but requires a little bit mor code to implement.
How about adding a new class "ActorRole" that defines the behaviour of the actor in each Stage. It lets you decouple Actor and Stage from each other, so you can instantiate both independently (through a factory for example) and then combine them creating ActorRole objects that configure your stages. This combinations can be made using a Builder pattern if it is needed.
If you need to dynamically change your actor behaviour, you can use a Strategy pattern based on the ActorRole class, so depending on the Stage, you can assign to the actor different concrete implementations of its behaviour.
I would solve it by using Func instead of passing in the Stage to the Actor. Like this:
namespace AppExe
{
using AppLib;
class Program
{
static void Main(string[] args)
{
StageApp app = new StageApp();
app.CreateActor();
app.SharedSetting = 5;
foreach (var actor in app.Actors)
Console.WriteLine(actor.Execute());
}
}
}
namespace AppLib
{
class StageApp
{
public int SharedSetting { get; set; }
public IEnumerable<Actor> Actors { get { return m_actors.Where(x => x.Execute() > 40).ToArray(); } }
private List<Actor> m_actors = new List<Actor>();
public void CreateActor()
{
m_actors.Add(new Actor(Executed));
}
private int Executed(int arg)
{
return SharedSetting * arg;
}
}
class Actor
{
private int m_Property;
private Func<int, int> m_executed;
public Actor(Func<int, int> executed)
{
m_executed = executed;
m_Property = new Random().Next();
}
public int Execute()
{
return m_executed(m_Property);
}
}
}
I totally agree with you that circular references is not fun :).
You could also solve this using events, but I like passing functions like callback.
Here's the situation:
/// <summary>
/// A business logic class.
/// </summary>
public class BusinessClassWithInterceptor : BusinessClass, IBusinessClass
{
/// <summary>
/// Initializes a new instance of the <see cref="BusinessClassWithoutInterceptor"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
public BusinessClassWithInterceptor(Logger logger)
: base(logger)
{
}
/// <summary>
/// Displays all cows.
/// </summary>
public void DisplayAllCows()
{
this.Logger.Write("Displaying all cows:");
var repository = new CowRepository();
foreach (CowEntity cow in repository.GetAllCows())
{
this.Logger.Write(" " + cow);
}
}
/// <summary>
/// Inserts a normande.
/// </summary>
public void InsertNormande(int id, string name)
{
this.DisplayAllCows();
var repository = new CowRepository();
repository.InsertCow(new CowEntity { Id = id, Name = name, Origin = CowOrigin.Normandie });
}
}
With castle windsor, this class is configured to be intercepted with this interceptor:
/// <summary>
/// Interceptor for logging business methods.
/// </summary>
public class BusinessLogInterceptor : IInterceptor
{
/// <summary>
/// Intercepts the specified invocation.
/// </summary>
/// <param name="invocation">The invocation.</param>
public void Intercept(IInvocation invocation)
{
Logger logger = ((IBusinessClass)invocation.InvocationTarget).Logger;
var parameters = new StringBuilder();
ParameterInfo[] methodParameters = invocation.Method.GetParameters();
for (int index = 0; index < methodParameters.Length; index++)
{
parameters.AppendFormat("{0} = {1}", methodParameters[index].Name, invocation.Arguments[index]);
if (index < methodParameters.Length - 1)
{
parameters.Append(", ");
}
}
logger.Format("Calling {0}( {1} )", invocation.Method.Name, parameters.ToString());
invocation.Proceed();
logger.Format("Exiting {0}", invocation.Method.Name);
}
}
The issue takes place during the call to InsertNormande.
The call to InsertNormande is well intercepted, but the call to DisplayAllCows in InsertNormande is not intercepted...
It really bothers me.
Is there a way to achieve interception in this scenario ?
I don't think there's an easy way of doing it... method calls that are internal to the class can't be intercepted, since they don't go through the proxy.
You could achieve logging of all methods by other means, such as an AOP framework like PostSharp