How can I get NLog to inject dependencies into a target? - c#

I have a custom NLog log target class that looks like this:
public class MyTarget : AsyncTaskTarget
{
public MyTarget() {}
public MyTarget(INeedThisThingToFunction thing)
{
Thing = thing;
}
public INeedThisThingToFunction Thing { get; set; }
public override Task WriteAsyncTask(LogEventInfo logEvent, CancellationToken cancellationToken)
{
Thing.Use();
return null;
}
}
I cannot figure out how to ensure that the second constructor gets called. I've done this in Program.cs:
public static void Main(string[] args)
{
var host = CreateWebHostBuilder(args).Build();
ConfigureLogging(host.Services.GetAutofacRoot());
LogManager.GetCurrentClassLogger().Info("Hi mom");
host.Run();
}
private static void ConfigureLogging(IComponentContext container) {
ConfigurationItemFactory.Default.CreateInstance = type =>
{
if (type != typeof(MyTarget) return Activator.CreateInstance(type);
var thing = new ThingTheTargetNeedsToFunction();
return new MyTarget(thing);
}
LogManager.Configuration.Reload();
}
I've tried a number of other things, too, but that comes the closest to doing something. When LogManager.Configuration.Reload() is called, the CreateInstance code fires; but when the Info method fires, the Thing property on the target is null.
Is there a better way to do this? Like, a way that works?
Using .NET Core 3, NLog, Autofac.

If the Thing is only available after having built the host, then you can do it like this:
public static void Main(string[] args)
{
var host = CreateWebHostBuilder(args).UseNLog().Build();
ConfigureLogging(host.Services.GetAutofacRoot());
LogManager.GetCurrentClassLogger().Info("Hi mom");
host.Run();
}
private static void ConfigureLogging(IComponentContext container)
{
var defaultConstructor = ConfigurationItemFactory.Default.CreateInstance;
ConfigurationItemFactory.Default.CreateInstance.CreateInstance = type =>
{
if (type == typeof(MyTarget))
{
var thing = new ThingTheTargetNeedsToFunction();
return new MyTarget(thing);
}
return defaultConstructor(type);
};
// Reload config and assign the newly reloaded config
LogManager.Configuration = LogManager.Configuration?.Reload();
}
Then make sure that your custom MyTarget can handle that it is running in "disabled mode" where Thing is unassigned:
[Target("MyTarget")]
public class MyTarget : AsyncTaskTarget
{
public MyTarget() {}
public MyTarget(INeedThisThingToFunction thing)
{
Thing = thing;
}
public INeedThisThingToFunction Thing { get; set; }
public override await Task WriteAsyncTask(LogEventInfo logEvent, CancellationToken cancellationToken)
{
if (Thing == null)
return null; // Handle that `Thing` is unassigned by default-constructor
await Thing.UseAsync().ConfigureAwait(false);
}
}

Related

How to make the ActivatorUtilities.CreateInstance method use a different constructor?

Is there a way the tell the ActivatorUtilities.CreateInstance<T>(IServiceProvider serviceProvider); method to try to use other constructors if the first one can't be constructed?
I have a class with multiple constructors:
public ViewModelB(SomeDependency someDependency): this one only takes SomeDependency which is registered in a DI container
public ViewModelB(SomeDependency someDependency, GetUserRequest request): this one takes SomeDependency which is registered in a DI container and a GetUserRequest which has to be passed in manually
And I'm trying to activate them and resolve dependencies like so:
IServiceProvider serviceProvider; //this gets passed from somewhere
Guid userId; //this gets passed manually by the caller
//works
var instanceAWithoutParams = ActivatorUtilities.CreateInstance<ViewModelA>(serviceProvider);
//works
var instanceAWithParams = ActivatorUtilities.CreateInstance<ViewModelA>(serviceProvider, new[] { new GetUserRequest { UserId = userId } });
//does NOT work, it tries to use the first constructor and fails
var instanceBWithoutParams = ActivatorUtilities.CreateInstance<ViewModelB>(serviceProvider);
//works
var instanceBWithParams = ActivatorUtilities.CreateInstance<ViewModelB>(serviceProvider,, new[] { new GetUserRequest { UserId = userId } });
The activation of instanceBWithoutParams fails because it can't resolve the request parameter. It tries to use the first constructor and doesn't check other ones when the activation fails.
Here's what the services look like, they're the same with one difference: the order of the constructors.
public class ViewModelA
{
private readonly SomeDependency _someDependency;
private readonly GetUserRequest? _request;
public ViewModelA(SomeDependency someDependency)
{
_someDependency = someDependency;
}
public ViewModelA(SomeDependency someDependency, GetUserRequest request)
{
_someDependency = someDependency;
_request = request;
}
}
public class ViewModelB
{
private readonly SomeDependency _someDependency;
private readonly GetUserRequest? _request;
public ViewModelB(SomeDependency someDependency, GetUserRequest request)
{
_someDependency = someDependency;
_request = request;
}
public ViewModelB(SomeDependency someDependency)
{
_someDependency = someDependency;
}
}
public class GetUserRequest
{
public Guid UserId { get; set; }
}
Thanks.
I struggled with the same issue. Eventually I came up with this solution:
I would use something like a factory which is able to construct ServiceB by calling a method.
For example:
var serviceBFactory = ActivatorUtilities.CreateInstance<ServiceBFactory>(serviceProvider);
var instanceBWithoutParams = serviceBFactory.CreateServiceB();
var instanceBWithParams = serviceBFactory.CreateServiceB(new Request());
This way you keep you DI clean. But this means that the ServiceBFactory need to know which services need to be injected in a ServiceB. (so that will be a tight coupling) They come as a package.
I've chosen to re-design the view models instead of trying to pass optional parameters next to services from DI (thanks to Steven for the helpful articles: 1 and 2).
There also seems to be no way of making the ActivatorUtilities.CreateInstance<T>(IServiceProvider serviceProvider); method try other constructors after one fails, so here's what my edited solution looks like.
I've moved the initialization of the optional parameter out of the constructor, that way I only have one constructor that only takes injectables. The parameter is then passed separately via the TakeParameter method. The only downside I can think of is that the parameter can no longer be readonly and I can live with that.
My custom activator utility:
public interface IAcceptParameter<T>
{
void TakeParameter(T parameter);
}
public static class CustomActivator
{
public static T CreateInstance<T>()
{
return ActivatorUtilities.CreateInstance<T>(_serviceProvider);
}
public static T CreateInstanceWithParam<T, K>(K parameter) where T : IAcceptParameter<K>
{
var instance = ActivatorUtilities.CreateInstance<T>(_serviceProvider);
instance.TakeParameter(parameter);
return instance;
}
}
Changed view model
public class SomeViewModel : IAcceptParameter<Guid>
{
private readonly SomeDependency _someDependency;
private Guid? _userId;
public SomeViewModel(SomeDependency someDependency)
{
_someDependency = someDependency;
}
public void TakeParameter(Guid parameter){
_userId = parameter;
}
}
How I use it
var instanceWithoutParam = CustomActivator.CreateInstance<SomeViewModel>(serviceProvider);
Guid userId;
var instanceWithParam = CustomActivator.CreateInstanceWithParam<SomeViewModel, Guid>(serviceProvider, userId);
Let say you have a class like this:
public class a
{
public string p { get; set; }
public a()
{
p = "default constructor";
}
public a(string pv)
{
p = pv;
}
}
You can use .GetConstructor method to use a specific constructor:
public class Program
{
static void Main(string[] args)
{
var c = typeof(a).GetConstructor(new Type[] { typeof(string) });
if (c != null)
{
var myA = (a)c.Invoke(new object[] { "new value" });
Console.WriteLine($"Value of p is {myA.p}");
}
}
}

using localization service GetAllLanguages from a component composer, incorrect DI?

I have an interface as below, which I use to add a specific language if it does not exist:
public interface IGetLanguagesService
{
void GetLanguages(ILocalizationService localization);
}
public class LanguageService : IGetLanguagesService
{
ILocalizationService _localizationService;
public void GetLanguages(ILocalizationService localization)
{
_localizationService = localization;
var currentLanguages = _localizationService.GetAllLanguages();
bool exists = false;
foreach (var currentLan in currentLanguages)
{
if (currentLan.IsoCode == "es-ES")
{
exists = true;
}
}
if (!exists)
{
AddLanguage(_localizationService);
}
}
public void AddLanguage(ILocalizationService localization)
{
var languageSE = new Language("es-ES") { CultureName = "es-ES", IsMandatory = true };
localization.Save(languageSE);
}
}
I want to use this at start-up so have created a component composer, which on Initialize() I want to call CallGetLanguages() but Im not entirely sure what should be in Initialize(), I think my DI may be wrong?
public class LanguagesComposer : ComponentComposer<LanguagesComponent>
{
public void Compose(Composition composition)
{
composition.Register<IGetLanguagesService>(Lifetime.Singleton);
composition.Register<ILocalizationService>(Lifetime.Singleton);
composition.Components().Append<LanguagesComponent>();
}
}
public class LanguagesComponent : IComponent
{
public void Initialize()
{
???????
}
public void Terminate()
{
throw new NotImplementedException();
}
IGetLanguagesService _getLanguagesService;
ILocalizationService _localization;
public void CallGetLanguages(IGetLanguagesService getLanguages, ILocalizationService localization)
{
_getLanguagesService = getLanguages;
_localization = localization;
_getLanguagesService.GetLanguages(localization);
}
}
You've passed ILocalizationService localization instance to LanguageService twice, pass it to constructor instead and use a constructor injection. The same issue with LanguagesComponent, pass all its dependencies to constructor instead of methods

how to perform a runtime view update when using ResourceViewLocationProvider

Im runing a nancyfx with owin on centos 6.5 with mono 5.10.0.140, I change the default ViewLocationProvider to ResourceViewLocationProvider for the default ViewLocationProvider causes memory leak of somekind after running for days, and the ResourceViewLocationProvider dont have the same problem. I would like to hot update Views just like what we can do with a default ViewLocationProvider, but it seems impossibe when googling around.
I did find a partial solution though, by implenting a custom IViewLocator and a IViewCache, I did achieve someking of hot update. But It didn`t feel right aside from those ugly static class
//Here is what I did in the custom IViewLocator
//...class definition fallback viewlocator and other staffs
private static ConcurrentDictionary<string, ViewLocationResult> _cachedViewLocationResults;
//..other code
public ViewLocationResult LocateView(string viewName, NancyContext context)
{
//...lock and others
if (_cachedViewLocationResults != null && _cachedViewLocationResults.ContainsKey(viewName))
{
return _cachedViewLocationResults[viewName];
}
//...lock and others
return fallbackViewLocator.LocateView(viewName, context);
}
//...other class
//here is how I update Views
public static void UpdateCachedView(IDictionary<string, ViewLocationResult> replacements)
{
lock (CacheLock)
{
if(_cachedViewLocationResults == null)_cachedViewLocationResults = new ConcurrentDictionary<string, ViewLocationResult>();
foreach (var replace in replacements)
{
_cachedViewLocationResults.AddOrUpdate(replace.Key, x=>replacements[x], (x,y)=>y);
}
}
}
//END OF IViewLocator
//here is what I did in the custom IViewCache
//another static for ViewCache to tell if the view has been updated
public static List<ViewLocationResult> Exceptions { get; private set; }
//...some other code
//here is how I ignore the old cache
public TCompiledView GetOrAdd<TCompiledView>(ViewLocationResult viewLocationResult, Func<ViewLocationResult, TCompiledView> valueFactory)
{
if (Exceptions.Any(x=>x.Name == viewLocationResult.Name && x.Location == viewLocationResult.Location && x.Extension == viewLocationResult.Extension))
{
object old;
this.cache.TryRemove(viewLocationResult, out old);
Exceptions.Remove(viewLocationResult);
}
return (TCompiledView)this.cache.GetOrAdd(viewLocationResult, x => valueFactory(x));
}
With those implentions and a little bit of settings on the bootstrapper plus a router for some mysql update, I can update the View the way I want, but here is the problem:
1. now I have to manually map all the Location,Name,Extension for the ViewLocationResult to use and there are too many of them (243...), I would like to use the some sort of built-in function to identify the changes, something like the IsStale function of the ViewLocationResult, but I didnt know which and how...
2. those static class are ugly and I think it could be problematic but I didnt know a better way to replace them.
Could some one kindly give me a hint, thank in advance.
Well, I finally figure out how to do this myself, just in case anyone else want to use the same method as I do, Here is how you update your view in memory:
Make a interface
public interface INewViewLocationResultProvider
{
bool UseCachedView { get; set; }
ViewLocationResult GetNewerVersion(string viewName, NancyContext context);
void UpdateCachedView(IDictionary<string, ViewLocationResult> replacements);
}
Make a new ViewLocationResultProvider
public class ConcurrentNewViewLocationResultProvider : INewViewLocationResultProvider
{
private Dictionary<string, ViewLocationResult> _cachedViewLocationResults;
private readonly object _cacheLock = new object();
public bool UseCachedView { get; set; }
public ConcurrentNewViewLocationResultProvider()
{
lock (_cacheLock)
{
if(_cachedViewLocationResults == null)_cachedViewLocationResults = new Dictionary<string, ViewLocationResult>();
}
}
public ViewLocationResult GetNewerVersion(string viewName, NancyContext context)
{
if (UseCachedView)
{
if (Monitor.TryEnter(_cacheLock, TimeSpan.FromMilliseconds(20)))
{
try
{
if (_cachedViewLocationResults != null && _cachedViewLocationResults.ContainsKey(viewName))
{
return _cachedViewLocationResults[viewName];
}
}
finally
{
Monitor.Exit(_cacheLock);
}
}
}
return null;
}
public void UpdateCachedView(IDictionary<string, ViewLocationResult> replacements)
{
lock (_cacheLock)
{
if(_cachedViewLocationResults == null)_cachedViewLocationResults = new Dictionary<string, ViewLocationResult>();
foreach (var replace in replacements)
{
if (_cachedViewLocationResults.ContainsKey(replace.Key))
{
_cachedViewLocationResults[replace.Key] = replace.Value;
}
else
{
_cachedViewLocationResults.Add(replace.Key,replace.Value);
}
}
}
}
}
In your Bootstrapper,register the new ViewLocationResultProvider with tinyIoc or equivalent
container.Register<INewViewLocationResultProvider, ConcurrentNewViewLocationResultProvider>().AsSingleton();
Make a derived class from ViewLocationResult
public class OneTimeUsedViewLocationResult : ViewLocationResult
{
private bool _used = false;
public OneTimeUsedViewLocationResult(string location, string name, string extension, Func<TextReader> contents)
: base(location, name, extension, contents)
{
}
public override bool IsStale()
{
if (_used) return false;
_used = true;
return true;
}
}
And a new IViewLocator:
public class CachedViewLocator : IViewLocator
{
private readonly INewViewLocationResultProvider _newVersion;
private readonly DefaultViewLocator _fallbackViewLocator;
public CachedViewLocator(IViewLocationProvider viewLocationProvider, IEnumerable<IViewEngine> viewEngines, INewViewLocationResultProvider newVersion)
{
_fallbackViewLocator = new DefaultViewLocator(viewLocationProvider, viewEngines);
_newVersion = newVersion;
}
public ViewLocationResult LocateView(string viewName, NancyContext context)
{
if (_newVersion.UseCachedView)
{
var result = _newVersion.GetNewerVersion(viewName, context);
if (result != null) return result;
}
return _fallbackViewLocator.LocateView(viewName, context);
}
public IEnumerable<ViewLocationResult> GetAllCurrentlyDiscoveredViews()
{
return _fallbackViewLocator.GetAllCurrentlyDiscoveredViews();
}
}
}
Tell nancy about the new ViewLocator
protected override NancyInternalConfiguration InternalConfiguration
{
get
{
return NancyInternalConfiguration.WithOverrides
(
nic =>
{
nic.ViewLocationProvider = typeof(ResourceViewLocationProvider);//use this or your equivalent
nic.ViewLocator = typeof(CachedViewLocator);
}
);
}
}
Then you can update it through a API like this:
public class YourModule : NancyModule
{
public YourModule(INewViewLocationResultProvider provider)
{
Get["/yourupdateinterface"] = param =>
{
if(!provider.UseCachedView) return HttpStatusCode.BadRequest;//in case you turn off the hot update
//you can serialize your OneTimeUsedViewLocationResult with Newtonsoft.Json and store those views in any database, like mysql, redis, and load them here
//data mock up
TextReader tr = new StringReader(Resources.TextMain);
var vlr = new OneTimeUsedViewLocationResult("","index","cshtml",()=>tr);
var dir = new Dictionary<string, ViewLocationResult> {{"index",vlr}};
//mock up ends
provider.UpdateCachedView(dir);
return HttpStatusCode.OK;
}
}
}
Note: Those code above doesn't solve the manually map all the Location,Name,Extension for the ViewLocationResult thing menthions in my question, but since I endup build a view editor for my colleges to upload their views, I don't need to solve it anymore.

Roslyn CodeFixProvider: Move caret after applying code fix

i have implemented a custom CodeFixProvider that adds some XML documentation to members.
Example:
public void MyMethod() { }
will be transformed to
/// <summary></summary>
public void MyMethod() { }
The CodeFixProvider is implemented like this:
public class MyCodeFixProvider : CodeFixProvider
{
...
public async override Task RegisterCodeFixesAsync(CodeFixContext context)
{
await Task.Run(() =>
{
Diagnostics diagnostics = context.Diagnostics.First();
CodeAction codeFix = CodeAction.Create("Title", c => CreateXmlDocs(...));
context.RegisterCodeFix(codeFix, diagnostics);
}
).ConfigureAwait(false);
}
...
}
Everything is working like expected.
Now i want to add some extra functionality: After applying the code fix, the caret should be moved inside the empty summary tag.
I discovered the DocumentNavigationOperation class included in Microsoft.CodeAnalysis.Features NuGet package. This class should be able to move the caret to a specified position. But I can't find any instructions how to use this class. If i call it from inside my CreateXmlDocs method, an exception is thrown:
Navigation must be performed on the foreground thread.
Code:
private static async Task<Solution> CreateXmlDocs()
{
...
new DocumentNavigationOperation(newDocument.Id, 42)
.Apply(newDocument.Project.Solution.Workspace, cancellationToken);
...
}
I'm not sure if it makes sense to use this class inside my CreateXmlDocs method, because the new solution created inside this method isn't yet applied by Visual Studio when DocumentNavigationOperation is called.
Does anybody knows a solution to move the caret after applying a code fix?
Ok, in the meantime i found a solution for this.
A custom CodeAction is required to get it working:
internal class NavigateAfterCodeChangeAction : CodeAction
{
private readonly Func<CancellationToken, Task<Solution>> codeChangeOperation;
private readonly Func<Solution, CancellationToken, Task<NavigationTarget>> navigationTargetCalculation;
public NavigateAfterCodeChangeAction(
string title,
Func<CancellationToken, Task<Solution>> codeChangeOperation,
Func<Solution, CancellationToken, Task<NavigationTarget>> navigationTargetCalculation)
{
this.Title = title;
this.codeChangeOperation = codeChangeOperation;
this.navigationTargetCalculation = navigationTargetCalculation;
}
public override string Title { get; }
protected override async Task<IEnumerable<CodeActionOperation>> ComputeOperationsAsync(CancellationToken cancellationToken)
{
var operations = new List<CodeActionOperation>();
Solution changedSolution = await this.codeChangeOperation(cancellationToken);
NavigationTarget navigationTarget = await this.navigationTargetCalculation(changedSolution, cancellationToken);
operations.Add(new ApplyChangesOperation(changedSolution));
if (navigationTarget != null)
{
operations.Add(new DocumentNavigationOperation(navigationTarget.DocumentId, navigationTarget.Position));
}
return operations;
}
}
internal class NavigationTarget
{
public NavigationTarget(DocumentId documentId, int position)
{
this.DocumentId = documentId;
this.Position = position;
}
public DocumentId DocumentId { get; }
public int Position { get; }
}
The new CodeAction can be used in the CodeFixProvider instead of CodeAction.Create():
public class MyCodeFixProvider : CodeFixProvider
{
...
public async override Task RegisterCodeFixesAsync(CodeFixContext context)
{
await Task.Run(() =>
{
Diagnostics diagnostics = context.Diagnostics.First();
CodeAction codeFix = new NavigateAfterCodeChangeAction(
"Title",
c => CreateXmlDocs(...)
(s, c) => CalculateNavigationTarget(context.Document));
context.RegisterCodeFix(codeFix, diagnostics);
}
).ConfigureAwait(false);
}
private static NavigationTarget CalculateNavigationTarget(Document doc)
{
// Calculate the navigation target here...
// Example: Navigate to position 42 of the document
return new NavigationTarget(doc.Id, 42);
}
...
}

Structuremap interception for registry scanned types

I have a ASP MVC 4 app that uses Structuremap. I'm trying to add logging to my application via Structuremap interception.
In a Registry, I scan a specific assembly in order to register all of it's types with the default convention:
public class ServicesRegistry : Registry
{
public ServicesRegistry()
{
Scan(x =>
{
x.AssemblyContainingType<MyMarkerService>();
x.WithDefaultConventions();
});
}
}
The interceptor:
public class LogInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
var watch = Stopwatch.StartNew();
invocation.Proceed();
watch.Stop();//log the time
}
}
I can add the interceptor for one specific plugin type like this:
var proxyGenerator = new ProxyGenerator();
container.Configure(x => x.For<IServiceA>().Use<ServiceA>().DecorateWith(instance => proxyGenerator.CreateInterfaceProxyWithTarget(instance, new LogInterceptor())));
but I want to make structuremap create logging proxies for all the types that were scanned in the registry.
Is there a way to achieve this?
It doesn't look like there's an easy extension point for this, but I got it working with a fairly decent solution using a custom convention. In order to help you understand the decisions I made I'll walk you through a few steps (skipping the many, many missteps I made on my way).
First lets look at the DefaultConvention which you are already using.
DefaultConvention:
public class DefaultConventionScanner : ConfigurableRegistrationConvention
{
public override void Process(Type type, Registry registry)
{
if (!TypeExtensions.IsConcrete(type))
return;
Type pluginType = this.FindPluginType(type);
if (pluginType == null || !TypeExtensions.HasConstructors(type))
return;
registry.AddType(pluginType, type);
this.ConfigureFamily(registry.For(pluginType, (ILifecycle)null));
}
public virtual Type FindPluginType(Type concreteType)
{
string interfaceName = "I" + concreteType.Name;
return Enumerable.FirstOrDefault<Type>((IEnumerable<Type>)concreteType.GetInterfaces(), (Func<Type, bool>)(t => t.Name == interfaceName));
}
}
Pretty simple, we get the type and interface pairs and check to make sure they have a constructor, if they do we register them. It would be nice to just modify this so that it calls DecorateWith, but you can only call that on For<>().Use<>(), not For().Use().
Next lets look at what DecorateWith does:
public T DecorateWith(Expression<Func<TPluginType, TPluginType>> handler)
{
this.AddInterceptor((IInterceptor) new FuncInterceptor<TPluginType>(handler, (string) null));
return this.thisInstance;
}
So this creates a FuncInterceptor and registers it. I spent a fair bit of time trying to create one of these dynamically with reflection before deciding it would just be easier to make a new class:
public class ProxyFuncInterceptor<T> : FuncInterceptor<T> where T : class
{
public ProxyFuncInterceptor() : base(x => MakeProxy(x), "")
{
}
protected ProxyFuncInterceptor(Expression<Func<T, T>> expression, string description = null)
: base(expression, description)
{
}
protected ProxyFuncInterceptor(Expression<Func<IContext, T, T>> expression, string description = null)
: base(expression, description)
{
}
private static T MakeProxy(T instance)
{
var proxyGenerator = new ProxyGenerator();
return proxyGenerator.CreateInterfaceProxyWithTarget(instance, new LogInterceptor());
}
}
This class just makes it easier to work with when we have the type as a variable.
Finally I've made my own Convention based on the Default convention.
public class DefaultConventionWithProxyScanner : ConfigurableRegistrationConvention
{
public override void Process(Type type, Registry registry)
{
if (!type.IsConcrete())
return;
var pluginType = this.FindPluginType(type);
if (pluginType == null || !type.HasConstructors())
return;
registry.AddType(pluginType, type);
var policy = CreatePolicy(pluginType);
registry.Policies.Interceptors(policy);
ConfigureFamily(registry.For(pluginType));
}
public virtual Type FindPluginType(Type concreteType)
{
var interfaceName = "I" + concreteType.Name;
return concreteType.GetInterfaces().FirstOrDefault(t => t.Name == interfaceName);
}
public IInterceptorPolicy CreatePolicy(Type pluginType)
{
var genericPolicyType = typeof(InterceptorPolicy<>);
var policyType = genericPolicyType.MakeGenericType(pluginType);
return (IInterceptorPolicy)Activator.CreateInstance(policyType, new object[]{CreateInterceptor(pluginType), null});
}
public IInterceptor CreateInterceptor(Type pluginType)
{
var genericInterceptorType = typeof(ProxyFuncInterceptor<>);
var specificInterceptor = genericInterceptorType.MakeGenericType(pluginType);
return (IInterceptor)Activator.CreateInstance(specificInterceptor);
}
}
Its almost exactly the same with one addition, I create an interceptor and interceptorType for each type we register. I then register that policy.
Finally, a few unit tests to prove it works:
[TestFixture]
public class Try4
{
[Test]
public void Can_create_interceptor()
{
var type = typeof (IServiceA);
Assert.NotNull(new DefaultConventionWithProxyScanner().CreateInterceptor(type));
}
[Test]
public void Can_create_policy()
{
var type = typeof (IServiceA);
Assert.NotNull(new DefaultConventionWithProxyScanner().CreatePolicy(type));
}
[Test]
public void Can_register_normally()
{
var container = new Container();
container.Configure(x => x.Scan(y =>
{
y.TheCallingAssembly();
y.WithDefaultConventions();
}));
var serviceA = container.GetInstance<IServiceA>();
Assert.IsFalse(ProxyUtil.IsProxy(serviceA));
Console.WriteLine(serviceA.GetType());
}
[Test]
public void Can_register_proxy_for_all()
{
var container = new Container();
container.Configure(x => x.Scan(y =>
{
y.TheCallingAssembly();
y.Convention<DefaultConventionWithProxyScanner>();
}));
var serviceA = container.GetInstance<IServiceA>();
Assert.IsTrue(ProxyUtil.IsProxy(serviceA));
Console.WriteLine(serviceA.GetType());
}
[Test]
public void Make_sure_I_wait()
{
var container = new Container();
container.Configure(x => x.Scan(y =>
{
y.TheCallingAssembly();
y.Convention<DefaultConventionWithProxyScanner>();
}));
var serviceA = container.GetInstance<IServiceA>();
serviceA.Wait();
}
}
}
public interface IServiceA
{
void Wait();
}
public class ServiceA : IServiceA
{
public void Wait()
{
Thread.Sleep(1000);
}
}
public interface IServiceB
{
}
public class ServiceB : IServiceB
{
}
There's definitely room for some clean up here (caching, make it DRY, more tests, make it easier to configure) but it works for what you need and is a pretty reasonable way of doing it.
Please ask if you have any other questions about it.

Categories