Dependency injection in Apache Ignite.NET service - c#

Consider Apache Ignite.NET cluster that provides service grid.
There is a simple service, that will run on any node:
public class ClientConnectionService : IClientConnectionService, IService
{
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
[InstanceResource] private IIgnite Ignite { get; set; }
public void Listen(string hostname, int port, uint username, string password,
ClientConnectionListenerOptions options = ClientConnectionListenerOptions.All)
{
Logger.Debug("Listen");
}
public void Init(IServiceContext context)
{
Logger.Debug("Initialized");
}
public void Execute(IServiceContext context)
{
Logger.Debug("Executed");
}
public void Cancel(IServiceContext context)
{
Logger.Debug("Canceled");
}
}
The application is using Castle Windsor as inversion of control container.
I would like to inject custom dependencies, that won't be serialized and transferred over the wire.
Is there any way to achieve it?
N.B. In Java version, there is #SpringResourceannotation that will basically do what I want, but the question is about .NET, that provides just [InstanceResource] attribute.

This is what I have ended up with:
In shared project where all the interfaces and contracts are described I've introduced IContainer
public interface IContainer
{
T Resolve<T>();
}
In project that is responsible for Apache Ignite.NET integration I've implemented simple Apache Ignite.NET plugin
public class DependencyInjectionPlugin
{
public IContainer Container { get; set; }
public T Resolve<T>()
{
return Container.Resolve<T>();
}
}
[PluginProviderType(typeof(DependencyInjectionPluginProvider))]
public class DependencyInjectionPluginConfiguration : IPluginConfiguration
{
public void WriteBinary(IBinaryRawWriter writer)
{
// No-op
}
public int? PluginConfigurationClosureFactoryId { get; } = null; // No Java part
}
public class DependencyInjectionPluginProvider : IPluginProvider<DependencyInjectionPluginConfiguration>
{
public string Name { get; } = "DependencyInjection";
public string Copyright { get; } = "MIT";
protected DependencyInjectionPlugin DependencyInjectionPlugin { get; set; }
public T GetPlugin<T>() where T : class
{
return DependencyInjectionPlugin as T;
}
public void Start(IPluginContext<DependencyInjectionPluginConfiguration> context)
{
DependencyInjectionPlugin = new DependencyInjectionPlugin();
}
public void Stop(bool cancel)
{
}
public void OnIgniteStart()
{
}
public void OnIgniteStop(bool cancel)
{
}
}
In main project, that is responsible for wiring up all components, I've implemented IContainer, defined previously, and registered it in Castle Windsor:
public class DependencyInjectionContainer : IContainer
{
protected IKernel Kernel { get; set; }
public DependencyInjectionContainer(IKernel kernel)
{
Kernel = kernel;
}
public T Resolve<T>()
{
return Kernel.Resolve<T>();
}
}
public class DependencyInjectionInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
Component
.For<IContainer>()
.ImplementedBy<DependencyInjectionContainer>()
);
}
}
In the very same project I've registered Apache Ignite.NET
public class IgniteInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
Component
.For<IIgnite>()
.UsingFactoryMethod(() => Ignition.Start(new IgniteConfiguration
{
PluginConfigurations = new[] {new DependencyInjectionPluginConfiguration()}
}))
);
}
}
Finally, in application's main method:
// Build Windsor container
using (var container = new WindsorContainer())
{
// Install DI abstraction layer
container.Install(new DependencyInjectionInstaller());
// Install cluster abstraction layer
container.Install(new IgniteInstaller());
// Attach DI container to cluster plugin
container
.Resolve<IIgnite>()
.GetPlugin<DependencyInjectionPlugin>("DependencyInjection")
.Container = container.Resolve<IContainer>();
// Wait
Done.Wait();
}
That's it. From now on, I am able to access IContainer implementation in Apache Ignite.NET distributed service like this:
var plugin = Ignite.GetPlugin<DependencyInjectionPlugin>("DependencyInjection");
var whatever = plugin.Resolve<IWhatever>();

Related

Autofac module order registration causes objects to be injected with their default instance values (default(T))

I'm facing a problem with Autofac registrations. In short, if I register the models BEFORE my configuration, when I load the configuration, it works smoothly but, if I register the models AFTER I register the configuration, the configuration models are loaded with their default types (default(T)). Below is the code to reproduce the problem:
using System;
using System.IO;
using Autofac;
using Microsoft.Extensions.Configuration;
namespace AutofacConfigurationTest.CrossCutting
{
public class ModuleModel : Module
{
protected override void Load(ContainerBuilder containerBuilder)
{
containerBuilder.RegisterType<Cache.Configuration>()
.As<Cache.IConfiguration>();
containerBuilder.RegisterType<Repository.Configuration>()
.As<Repository.IConfiguration>();
}
}
public class ModuleConfiguration : Module
{
protected override void Load(ContainerBuilder containerBuilder)
{
var configurationRoot = new Configuration.Container().ConfigurationRoot;
containerBuilder.RegisterInstance(configurationRoot).As<IConfigurationRoot>();
containerBuilder
.RegisterInstance(configurationRoot.GetSection(Cache.Configuration.Name)
.Get<Cache.Configuration>()).As<Cache.IConfiguration>();
containerBuilder
.RegisterInstance(configurationRoot.GetSection(Repository.Configuration.Name)
.Get<Repository.Configuration>()).As<Repository.IConfiguration>();
}
}
public class Container
{
public IContainer Kernel { get; }
public Container()
{
var containerBuilder = new ContainerBuilder();
// uncomment the line below to make it work //
containerBuilder.RegisterModule(new ModuleModel()); // if we register the models here, before the configuration, the configuration works properly //
containerBuilder.RegisterModule(new ModuleConfiguration());
// comment the line below to make it work //
containerBuilder.RegisterModule(new ModuleModel()); // if we register the models here, after the configuration, the configuration cannot load the data //
Kernel = containerBuilder.Build();
}
}
}
namespace AutofacConfigurationTest.Configuration
{
public class Container
{
private const string ConfigurationFile = "AppSettings.json";
public Container()
{
ConfigurationRoot = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile(ConfigurationFile).Build();
}
public IConfigurationRoot ConfigurationRoot { get; }
}
}
namespace AutofacConfigurationTest.Cache
{
public enum Engine
{
None,
Default
}
public interface IConfiguration
{
Engine Engine { get; set; }
int Duration { get; set; }
string ConnectionString { get; set; }
}
public class Configuration : IConfiguration
{
public const string Name = "Cache";
public Engine Engine { get; set; }
public int Duration { get; set; }
public string ConnectionString { get; set; }
}
}
namespace AutofacConfigurationTest.Repository
{
public enum Engine
{
None,
LiteDb
}
public interface IConfiguration
{
Engine Engine { get; set; }
string ConnectionString { get; set; }
}
public class Configuration : IConfiguration
{
public const string Name = "Repository";
public Engine Engine { get; set; }
public string ConnectionString { get; set; }
}
}
namespace AutofacConfigurationTest
{
internal class Program
{
private static IContainer _container;
private static void RegisterServices() => _container = new CrossCutting.Container().Kernel;
private static void DisposeServices()
{
if (_container != null &&
_container is IDisposable disposable)
disposable.Dispose();
}
private static void Main(string[] args)
{
try
{
RegisterServices();
// the following objects will be have a default(T) instance
// if the in the Autofac modules the Model is registered AFTER the Configuration
var cacheConfiguration = _container.Resolve<Cache.IConfiguration>();
var repositoryConfiguration = _container.Resolve<Repository.IConfiguration>();
Console.ReadLine();
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
DisposeServices();
}
}
}
}
I have a second question for you: I use the interfaces to force the contract in my models. In short, these interfaces are NEVER injected anywhere, it's just to make easier the maintenance for me. Should I remove the DI/IoC for these models or is there any reason to keep the models registration in the container?
The observed difference in behavior that now depends on the sequence of module registration, is due to the fact that both modules are registering services keyed to Cache.IConfiguration and Repository.IConfiguration. Therefore, the last module to be registered will "win".
When ModuleModel is registered last it will override any previous registration of the two configuration interfaces, and resolution will yield instances of Cache.Configuration and Repository.Configuration.
If ModuleConfiguration is registered last, resolution will yield instances provided by the configurationRoot object.
Inferring your intent from the two modules, since ModuleConfiguration registrations are actually trying to resolve Cache.Configuration and Repository.Configuration, ModuleModel must register these types keyed to those types instead of keyed to the interfaces. You do that using .AsSelf() instead of .As<some interface>(). Read more about AsSelf here.

C# AutoFac Inject Without Http Context (Outside Controller)

My class Is waiting for a Interface but I need initialize a class from a job in the Application_Start.
protected void Application_Start()
{
Bootstrapper.Run();
JobScheduler.RunJobSchedule();
}
On Bootstrap.Run() I have the container builder. like:
private static void SetAutofacContainer()
{
var builder = new ContainerBuilder();
builder.RegisterControllers(Assembly.GetExecutingAssembly());
builder.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerRequest();
builder.RegisterType<DbFactory>().As<IDbFactory>().InstancePerRequest();
builder.RegisterAssemblyTypes(typeof(DashboardService).Assembly)
.Where(t => t.Name.EndsWith("Service"))
.AsImplementedInterfaces().InstancePerRequest();
IContainer container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
And on JobScheduler.RunJobSchedule(); I will initializate my Jobs.
My Job need Instantiate the DashboardHelper class with IDashboardService
public class DashboardHelper
{
public IDashboardService dashboardService { get; }
public DashboardHelper(IDashboardService dashboardService)
{
this.dashboardService = dashboardService;
}
}
My Job Class:
public class ReportWeeklySenderJob : IJob
{
public IDashboardService dashboardService { get; set; }
public async Task Execute(IJobExecutionContext context)
{
this.dashboardService.Mymethods();
}
}
however I can not inject this dependency because the http context has not yet been created.
It should be a stupid question, but I could not solve it.

Hangfire.Autofac with MVC app - injection fails

I'm trying to create a simple Hangfire test but it's not working. Here's all the important code, and how I've configured it with the Hangire.Autofac . Not sure what I'm missing here. The exception I'm getting in the /hangfire dashbaord is below also.
public class AmazonSqsService : IAmazonSqsService
{
private readonly IBackgroundJobClient _backgroundJobClient;
private readonly ILogService _logService;
public AmazonSqsService(IBackgroundJobClient backgroundJobClient, ILogService logService)
{
_backgroundJobClient. = backgroundJobClient;
_logService= logService;
}
public async Task<string> Test()
{
return _backgroundJobClient.Enqueue(() => Looper());
}
public void Looper() {
while (true) { _logService.Info("In Looper Loop"); Thread.Sleep(5000); }
}
}
public partial class Startup
{
public static IContainer ConfigureContainer()
{
var builder = new ContainerBuilder();
RegisterApplicationComponents(builder);
AppGlobal.Container = builder.Build();
}
public static void RegisterApplicationComponents(ContainerBuilder builder)
{
builder.RegisterType<LogService>().As<ILogService>().InstancePerLifetimeScope();
builder.RegisterType<AmazonSqsService>().As<IAmazonSqsService>().InstancePerLifetimeScope();
builder.RegisterType<BackgroundJobClient>().As<IBackgroundJobClient>().InstancePerLifetimeScope();
builder.Register(c => JobStorage.Current).As<JobStorage>().InstancePerLifetimeScope();
builder.Register(c => new StateMachineFactory(JobStorage.Current)).As<IStateMachineFactory>().InstancePerLifetimeScope();
}
public static void ConfigureHangfire(IAppBuilder app)
{
app.UseHangfire(config =>
{
config.UseAutofacActivator(AppGlobal.Container);
config.UseSqlServerStorage("DefaultDatabase");
config.UseServer();
});
}
}
However in the dashboard I keep getting this error for the task:
Failed An exception occurred during job activation.
Autofac.Core.Registration.ComponentNotRegisteredException
The requested service 'App.Services.AmazonSqsService' has not been registered. To avoid this exception, either register a component to provide the service, check for service registration using IsRegistered(), or use the ResolveOptional() method to resolve an optional dependency.
Figured this out eventually.
Correct Usage:
public class Service : IService {
public void MethodToQueue() { ... }
}
public class AnyOtherClass {
public void StartTasks() {
BackgroundJob.Enqueue<IService>(x => x.MethodToQueue()); //Good
}
}
Incorrect usage (what I was doing wrong)
public class Service : IService {
public void StartTasks() {
BackgroundJob.Enqueue(() => this.MethodToQueue()); //Bad
}
public void MethodToQueue() { ... }
}
public class AnyOtherClass {
public AnyOtherClass(IService service) {
service.StartTasks();
}
}

How can I resolve ILog using ServiceStack and Funq.Container

The ServiceStack AppHost provides a Funq.Container with which to register types that can be injected into Services as they are constructed. Can this container be used to register an ILog factory that returns an ILog appropriate for the type in which it will reside?
Put another way, given the following AppHost:
public class AppHost : AppHostBase
{
public AppHost() : base("Example Web Services", Assembly.GetExecutingAssembly())
{
}
public override void Configure(Funq.Container container)
{
var baseLogFactory = new ServiceStack.Logging.NLogger.NLogFactory();
LogManager.LogFactory = new ServiceStack.Logging.Elmah.ElmahLogFactory(baseLogFactory);
// Would prefer to register a Func<Type, ILog> one time here
}
}
And a Service:
public class FooService : IService<FooRequest>
{
static ILog Log { get { return LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); } }
// Would prefer:
// public ILog { get; set; }
public object Execute(FooRequest request)
{
Log.Info("Received request: " + request.Dump());
return new FooResponse();
}
}
Is there anything I can add to AppHost.Configure to avoid the static ILog boilerplate in all of my Services (and instead just use a plain old ILog property)?
Put a third way, most succinctly, Can I use Funq.Container for ILog injection instead of LogManager?
container.Register<ILog>(
ctx => LogManager.LogFactory.GetLogger(typeof(IService))
);
Now your service could look like this:
public class FooService : IService<FooRequest>
{
public ILog { get; set; }
public object Execute(FooRequest request)
{
Log.Info("Received request: " + request.Dump());
return new FooResponse();
}
}

Installing Windsor Logging Facility into a library

I'm developing a framework where I've put lots of logging throughout. I used Castle Windsor's ILogger through this property pattern:
namespace Framework
{
public class SomeClass
{
private ILogger _logger = NullLogger.Instance;
public ILogger Logger
{
get { return _logger; }
set { _logger = value; }
}
public void DoSomething()
{
Logger.Info("Doing something.");
}
//...
}
}
I also provide an installer from within the framework:
namespace MyFramework
{
public class LoggerInstaller : IWindsorInstaller
{
private readonly string _configPath;
public LoggerInstaller(string configPath)
{
_configPath = configPath;
}
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.AddFacility("logging", new LoggingFacility(LoggerImplementation.Log4net, _configPath));
//I've also tried this one:
//container.AddFacility<LoggingFacility>(f => f.LogUsing(LoggerImplementation.Log4net).WithConfig(_configPath));
}
}
}
This project is then referenced from other projects. For example, in the test project, I'll construct a test by first installing the logger. I do this with an abstract class that all of my long running tests extend:
namespace Framework.Test
{
public abstract class Log4NetLoggedTest
{
private const string ConfigFilePath = "log4net.config";
protected ILogger Logger { get; set; }
protected IWindsorContainer Container { get; set; }
protected Log4NetLoggedTest()
{
Container = new WindsorContainer();
Container.Install(new LoggerInstaller(ConfigFilePath));
Logger = Container.Resolve<ILogger>();
}
~Log4NetLoggedTest()
{
Container.Dispose();
}
}
}
So that my test looks like this:
namespace Framework.Test
{
[TestFixture]
public class MyLongRunningTest : Log4NetLoggedTest
{
[Test]
[Category("LongRunning")]
public void ModelConvergesForS50()
{
Logger.Info("Starting test...");
var obj = new SomeClass();
obj.DoSomething();
// ...
}
}
}
The test's ILogger Logger gets resolved and set properly, so in this example I get the "Starting test..." but not the "Doing something." The SomeClass's ILogger stays as a NullLogger.
Please help!
You are instantiating SomeObj with 'new' rather than going through the container. If you don't go through the container, it can't inject the dependency
I may be saying something stupid, but, shouldnt be something like:
namespace Framework.Test
{
[TestFixture]
public class MyLongRunningTest : Log4NetLoggedTest
{
[Test]
[Category("LongRunning")]
public void ModelConvergesForS50()
{
Logger.Info("Starting test...");
var obj = new SomeClass();
obj.Logger = Logger;
obj.DoSomething();
// ...
}
}
}
I couldn't see you applying that instance of the logger that you use inside the class anywhere.

Categories