I am loading assemblies from a "plugins" folder and registering some implementations dynamically.
var appPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
var pluginsPath = Path.Combine(appPath, path);
var asmfiles = Directory.EnumerateFiles(pluginsPath, "*.dll", SearchOption.AllDirectories);
foreach(var asmfile in asmfiles)
{
Assembly.LoadFile(asmfile);
}
After loading all assemblies, I proceed to register the services:
builder.RegisterAssemblyTypes(asms)
.Where(t => typeof(IValidator).IsAssignableFrom(t))
.AsClosedTypesOf(typeof(IValidator<>));
builder.RegisterAssemblyTypes(asms)
.Where(t => typeof(IService).IsAssignableFrom(t) && t.IsClass)
.AsImplementedInterfaces()
.AsSelf();
Just to make sure, I log all registrations:
container.ComponentRegistry
.Registrations
.SelectMany(x => x.Services)
.Select(x => x.Description)
2016-07-13 09:58:52.5673 TRACE: Registered services:
Services.Test.ITestService
ServiceModel.IService
Services.Test.TestService
2016-07-13 09:58:52.5673 TRACE: Registered validators:
TestRequestValidator
Problem is, Autofac can't seem to create a controller with a ITestService dependency in its constructor.
"type": "Autofac.Core.DependencyResolutionException",
"message": "None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'ApiHostService.Controllers.EchoController' can be invoked with the available services and parameters: Cannot resolve parameter 'Services.Test.ITestService testService' of constructor 'Void .ctor(Services.Test.ITestService)'.",
What could be the problem?
Edit: resolving from the container fails.
I think you might be loading assemblies with same identities twice when you run Assembly.LoadFile(asmfile). This causes the ITestService you're trying to resolve (this is the one in your project reference) to be a different one from the ITestService you've loaded in the container (the one loaded as a separate assembly).
Could you please try using Assembly.LoadFrom(asmfile) instead?
See this question:
Difference between LoadFile and LoadFrom with .NET Assemblies?
You can also use this assembly preloader that will achieve what you want without loading duplicates:
https://github.com/zidad/net-tools/blob/master/src/Net.Core/Reflection/AssemblyPreloader.cs
Related
I have a NET 5.0 console application, from which I am trying to compile and execute external code BUT also be able to update the code, unload the previously created appdomain and re-compile everything.
This is my entire static class that handles code compilation and assembly loading
using System;
using System.IO;
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System.Reflection;
using Microsoft.CodeAnalysis.Emit;
using System.Runtime.Loader;
namespace Scripting
{
public static class ScriptCompiler
{
public static Dictionary<string, AppDomain> _appDomainDict = new();
public static object CompileScript(string scriptpath)
{
var tree = SyntaxFactory.ParseSyntaxTree(File.ReadAllText(scriptpath));
//Adding basic references
List<PortableExecutableReference> refs = new List<PortableExecutableReference>();
var assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location);
refs.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "mscorlib.dll")));
refs.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.dll")));
refs.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Private.CoreLib.dll")));
refs.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Core.dll")));
refs.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Runtime.dll")));
// A single, immutable invocation to the compiler
// to produce a library
string hash_name = scriptpath.GetHashCode();
if (_appDomainDict.ContainsKey(hash_name))
{
AppDomain.Unload(_appDomainDict[hash_name]);
_appDomainDict.Remove(hash_name);
}
AppDomain new_domain = AppDomain.CreateDomain(hash_name);
_appDomainDict[hash_name] = new_domain;
var compilation = CSharpCompilation.Create(hash_name)
.WithOptions(
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary,
optimizationLevel: OptimizationLevel.Release,
allowUnsafe:true))
.AddReferences(refs.ToArray())
.AddSyntaxTrees(tree);
MemoryStream ms = new MemoryStream();
EmitResult compilationResult = compilation.Emit(ms);
ms.Seek(0, SeekOrigin.Begin);
if (compilationResult.Success)
{
// Load the assembly
Assembly asm = new_domain.Load(ms.ToArray());
object main_ob = asm.CreateInstance("SomeClass");
ms.Close();
return main_ob;
}
else
{
foreach (Diagnostic codeIssue in compilationResult.Diagnostics)
{
string issue = $"ID: {codeIssue.Id}, Message: {codeIssue.GetMessage()}," +
$" Location: { codeIssue.Location.GetLineSpan()}," +
$" Severity: { codeIssue.Severity}";
Callbacks.Logger.Log(typeof(NbScriptCompiler), issue, LogVerbosityLevel.WARNING);
}
return null;
}
}
}
}
Its all good when I am trying load the assembly in the current domain and execute from the instantiated object. The problem with this case is that since I wanna do frequent updates to the code, even if I make sure that the assembly names are different. I'll end up loading a ton of unused assemblies to the current domain.
This is why I've been trying to create a new domain and load the assembly there. But for some reason I get a platform not supported exception. Is this not possible to do in NET 5? Are there any workarounds or am I doing something wrong here.
Ok, it turns out that AppDomain support for NET Core + is very limited and in particular there seems to be only one appdomain
On .NET Core, the AppDomain implementation is limited by design and
does not provide isolation, unloading, or security boundaries. For
.NET Core, there is exactly one AppDomain. Isolation and unloading are
provided through AssemblyLoadContext. Security boundaries should be
provided by process boundaries and appropriate remoting techniques.
Source: https://learn.microsoft.com/en-us/dotnet/api/system.appdomain?view=net-6.0
And indeed, when trying to use AssemblyLoadContext and create object instances through these contexts everything worked like a charm!
One last note is that if the created context is not marked as collectible, its not possible to unload it. But this can be very easily set during AssemblyLoadContext construction.
I'm using Autofac for DI with the builder.RegisterApiControllers(Assembly.GetExecutingAssembly()).InstancePerRequest(); but sometimes it gives an error on all my controllers (System.InvalidOperationException: An error occurred when trying to create a controller of type 'UserController'. Make sure that the controller has a parameterless public constructor. ---> Autofac.Core.DependencyResolutionException)
and when I give a rebuild of WEB API and it starts working fine.
Here is my code in startup.cs
private void ConfigureAutofac(HttpConfiguration config, IAppBuilder app)
{
var builder = new ContainerBuilder();
//Register HttpRequestMessage
builder.RegisterType<CurrentRequest>().InstancePerRequest();
builder.Register(c => new UrlHelper(c.Resolve<CurrentRequest>().Value));
//Register Web API controllers.
builder.RegisterApiControllers(Assembly.GetExecutingAssembly()).InstancePerRequest();
//Register the Autofac filter provider.
builder.RegisterWebApiFilterProvider(config);
//Register the Autofac model binder provider.
builder.RegisterWebApiModelBinderProvider();
#region Register managers
var businessasm = BuildManager.GetReferencedAssemblies()
.Cast<Assembly>()
.Where(n => n.FullName.Contains("Business"))
.FirstOrDefault();
builder.RegisterAssemblyTypes(businessasm)
.Where(t => t.Name.EndsWith("Manager") && t.Name != "DocumentManager")
.AsImplementedInterfaces()
.InstancePerRequest();
builder.RegisterGeneric(typeof(DocumentManager<>))
.As(typeof(IDocumentManager<>))
.InstancePerRequest();
#endregion
//Set the dependency resolver to be Autofac.
var container = builder.Build();
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
config.MessageHandlers.Insert(0, new ApiDelegatingHandler());
config.MessageHandlers.Insert(1, new ActivityLogHandler());
app.UseAutofacMiddleware(container);
app.UseAutofacWebApi(config);
}
The error indicates that UserController (which you didn't show above) takes a parameter that isn't registered with Autofac.
Since it works sometimes and not others, my guess is that the problem is this line:
var businessasm = BuildManager.GetReferencedAssemblies()
.Cast<Assembly>()
.Where(n => n.FullName.Contains("Business"))
.FirstOrDefault();
FirstOrDefault will return null if no assembly is found; and later on when you register all the things that end with Manager it means nothing will get registered.
My guess is that the UserController needs one of these managers, Autofac doesn't have it registered, and instantiation fails. If you have a breakpoint on the next line, you can see if businessasm is null; or, alternatively, switch to .First() instead of .FirstOrDefault() if you always expect the registrations to work.
Oh, and if you happen to have two assemblies with Business in them, you might want to make sure you're getting the right one. Assembly load order isn't guaranteed to be consistent.
That said I see the exception message you posted seems to indicate there are some nested exceptions in the stack. Don't stop reading at the first exception. Unfortunately with DI and other layers in the stack, you get exceptions that have inner exceptions that may, themselves, have inner exceptions... and the full set of messages may actually have more information that can help you troubleshoot.
Autofac has a really good troubleshooting page and Autofac v6 has some pretty detailed diagnostics that might help figure out where the missing things are if you can't figure it out from the exceptions.
I have a C# solution that includes multiple WebAPI projects. One of these projects, let's call it Project A, already uses SimpleInjector successfully. I'm trying to add SimpleInjector to another of these WebAPI projects, Project B, but I'm facing a problem.
I'm trying to create a second container at Project B as I did in Project A, but when I do this and try to build the solution, there is and exception in Project A, which is built after Project B, at the container.Verify() method. It tells me that a interface that is located at Project B (IUserService) is not properly registered at Project A, but Project A doesn't use this interface.
In Project B at Global.asax.cs I have this configuration:
/* Dependency Injection */
var container = new Container();
container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
container.Register<IUserService>(() => { return new UserService(); }, Lifestyle.Scoped);
container.RegisterWebApiControllers(GlobalConfiguration.Configuration);
container.Verify();
GlobalConfiguration.Configuration.DependencyResolver = new SimpleInjectorWebApiDependencyResolver(container);
In Project A, I have this configuration:
/* Dependency Injection */
var container = new Container();
container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
container.Register<ILog>(() => { return LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); }, Lifestyle.Scoped);
container.Register<IFundRepository>(() => { return new IporangaFundRepository(dbConnectorMiddle); }, Lifestyle.Scoped);
container.Register<ITradeRepository>(() => { return new IporangaTradeRepository(dbConnectorMiddle, middleReadClient); }, Lifestyle.Scoped);
container.Register<ITradeManager, TradeManager>(Lifestyle.Scoped);
container.Register<ITradeService>(() => new TradeService(container.GetInstance<ITradeManager>()),Lifestyle.Scoped);
container.Register<ISimulationService>(() => new SimulationService(container.GetInstance<ITradeService>()), Lifestyle.Scoped);
container.Register<IBookerService>(() => new BookerService(container.GetInstance<ITradeService>(), container.GetInstance<ISimulationService>()), Lifestyle.Scoped);
container.RegisterWebApiControllers(GlobalConfiguration.Configuration);
container.Verify();
GlobalConfiguration.Configuration.DependencyResolver = new SimpleInjectorWebApiDependencyResolver(container);
The error message:
System.InvalidOperationException: 'The configuration is invalid.
Creating the instance for type UserController failed. The constructor
of type UserController contains the parameter with name 'userService'
and type IUserService that is not registered. Please ensure
IUserService is registered, or change the constructor of
UserController.'
The following is not an answer to your question, but rather a suggestion how to improve and simplify the registrations for Project A.
Instead of using the posted code, I'd suggest using the following code to wire up the Container instance of Project A:
var container = new Container();
container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
// For Log4NetAdapter<T>, please see: https://stackoverflow.com/a/25113659
container.RegisterConditional(typeof(ILog),
c => typeof(Log4NetAdapter<>).MakeGenericType(c.Consumer.ImplementationType),
Lifestyle.Singleton,
c => true);
container.RegisterInstance(dbConnectorMiddle);
container.RegisterInstance(middleReadClient);
container.Register<IFundRepository, IporangaFundRepository>(Lifestyle.Scoped);
container.Register<ITradeRepository, IporangaTradeRepository(Lifestyle.Scoped);
container.Register<ITradeManager, TradeManager>(Lifestyle.Scoped);
container.Register<ITradeService, TradeService>(Lifestyle.Scoped);
container.Register<ISimulationService, SimulationService>(Lifestyle.Scoped);
container.Register<IBookerService, BookerService(Lifestyle.Scoped);
container.RegisterWebApiControllers(GlobalConfiguration.Configuration);
container.Verify();
GlobalConfiguration.Configuration.DependencyResolver =
new SimpleInjectorWebApiDependencyResolver(container);
This is better because:
Registrations are simplified because they only specify the mapping between the abstraction (e.g. ITradeService) and the implementation (e.g. TradeService), while letting Simple Injector Auto-Wire the types by inspecting the constructors' dependencies.
Not only are the registrations simplified, but now Simple Injector is aware of the structure of the complete dependency graph and can therefore effectively do verification on your behalf. This will, for instance, allow Simple Injector to find any Lifestyle Mismatches.
A conditional registration is used for the ILog abstraction. This allows Simple Injector to inject an ILog implementation specific to the consuming type. This allows your logging library to log information about the originating class. This is something that didn't work in your registration, where it would always inject a logger with a type that contains your registrations.
RegisterWebApiControllers uses reflection under the covers to search for implementations of ApiController.
I guess, based upon the error you get, project B is referenced by project A and the call to container.RegisterWebApiControllers in project A finds and registers the controllers of project B also.
Now when .Verify() is called it will scan the constructors of the ApiControllers in project B, finds a dependency on IUserService and breaks because this registration is actually missing in project A.
The integration package contains another overload for RegisterWebApiControllers which takes an array of assemblies that must be scanned for ApiControllers instead of scanning through all referenced assemblies.
Assuming the assembly of project A contains all ApiControllers that need to be registered this overload can be used in project A like:
container.RegisterWebApiControllers(GlobalConfiguration.Configuration,
new[] {typeof(MyApiControllerInProjectA).Assembly});
I want to do something like this Autofac line below with DryIoC
builder.RegisterType<TenantDBContext>
().InstancePerLifetimeScope().WithParameter(new
NamedParameter("connectionString", ""));
I have a CoreDBContext that has the connection string of TenantDBContext. Is it valid to pass the connection string at the point of registering my context in DryIoc?
container.Register<DbContext>(
Reuse.InCurrentScope,
made: Parameters.Of.Name("connectionString", _ => ""));
Update:
Asuming that you want to automatically select from multiple constructors:
Here is live snippet
container.Register<DbContext>(
Reuse.InCurrentScope,
made: Made.Of(FactoryMethod.ConstructorWithResolvableArguments,
Parameters.Of.Name("connectionString", _ => "")));
Note, that version above is brittle to parameter name change, so you may consider strongly-typed constructor expression:
container.Register<DbContext>(
Made.Of(() => new DbContext("")),
reuse: Reuse.InCurrentScope);
I have a code in Web Api Delegating Handler that extract data from request header.
However, I can't register instance in Autofac container because Autofac container require SingleInstance only.
public class ExtractUserNameMessageHandler : DelegatingHandler
{
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
var userNameFromFrontEnd = request.GetDependencyScope().GetService(typeof (IUserNameFromFrontEnd));
if (userNameFromFrontEnd == null)
{
var updatedContainerBuilder = new ContainerBuilder();
userNameFromFrontEnd = ExtractUserName(request);
if (userNameFromFrontEnd == null)
{
throw new Exception("We've got a request without UserName header");
}
updatedContainerBuilder.RegisterInstance(userNameFromFrontEnd)
.As<IUserNameFromFrontEnd>()
.InstancePerRequest();
var autofacDependencyResolver = GlobalConfiguration.Configuration.DependencyResolver as AutofacWebApiDependencyResolver;
if (autofacDependencyResolver == null)
{
throw new Exception("We can work with Autofac DI container");
}
updatedContainerBuilder.Update(autofacDependencyResolver.Container as IContainer);
}
When I try to update container I get an exception with message - registration can support singleinstance() sharing only.
What does it mean? I can't understand why we have this limitation. But in any cases my first goal - update container with new dependency.
Does anybody have ideas?
(Note: This question was cross-posted to the Autofac forums as well.)
When you register a specific instance, it's effectively a singleton - it's one instance, the instance you provided.
When you try to assign it InstancePerRequest or, really, any other lifetime scope besides SingleInstance, it doesn't make logical sense because you're not going to get a different instance per request (or whatever). You're going to get the exact same instance you registered, which is a singleton.
The exception message is trying to tell you how to avoid incorrect expectations: that it can't provide you a different instance per request even though you told it to because you didn't tell it how to create a new instance, you instead provided a specific instance.
If you need a different instance of an object per lifetime scope/request/whatever, you need to register a type, a delegate, or something else that tells Autofac how to create that new instance.
What that means is that if you want a different IUserNameFromFrontEnd per request, you need to move that logic out of a DelegatingHandler and into an Autofac registration delegate.
// Make sure to register the HttpRequestMessage in the container
// so you can resolve it...
builder.RegisterHttpRequestMessage(httpConfiguration);
// Then, whilst building your root container...
builder
.Register(ctx =>
{
var request = ctx.Resolve<HttpRequestMessage>();
return ExtractUserName(request);
})
.As<IUserNameFromFrontEnd>()
.InstancePerRequest();
Now it will probably do what you're looking to do - because you told Autofac how to create the instance that belongs in each request. It also means you don't need that DelegatingHandler anymore because Autofac will just do the right thing.
More advanced (and probably not useful here, but for completeness):
If, for whatever reason, you still feel like you need to modify the registration directly in the lifetime scope, instead of updating the container you should add the registration when the request lifetime scope is created.
Again, do not update the root container for per-lifetime-scope or per-request dependencies. It's not going to work how you think.
When a new lifetime scope is created, you can add registrations on the fly.
using(var scope = container.BeginLifetimeScope(
builder => builder.RegisterInstance(myfoo).As<IFoo>()))
{
// This will use the registrations in the container
// and the scope. f == myfoo
var f = scope.Resolve<IFoo>();
}
The AutofacDependencyResolver is the thing that creates the request lifetime scope and hands it off to Web API. You can see the full source here. The key method is BeginScope:
public IDependencyScope BeginScope()
{
var lifetimeScope = _container.BeginLifetimeScope(MatchingScopeLifetimeTags.RequestLifetimeScopeTag);
return new AutofacWebApiDependencyScope(lifetimeScope);
}
If you create your own AutofacDependencyResolver you can modify how the scope is created:
public IDependencyScope BeginScope()
{
var lifetimeScope = _container.BeginLifetimeScope(
MatchingScopeLifetimeTags.RequestLifetimeScopeTag,
builder => builder.RegisterInstance(myfoo).As<IFoo>());
return new AutofacWebApiDependencyScope(lifetimeScope);
}
This isn't an explicitly supported extension point in the Autofac Web API integration right now - that's why you'd have to create your own resolver.
However, this seems like overkill to solve the thing it appears you're trying to solve. I strongly recommend just registering the delegate with Autofac rather than trying to update existing containers or scopes. You will have far more luck using the path of least resistance.