Autofac WCF registration exception with svc-less service - c#

I'm trying to set up Autofac as my DI container for a new WCF project I am working on. We're working with a svc-less configuration and self hosting. Without Autofac and simply using poor-man's DI, everything is working exactly as desired, but when I add Autofac into the mix, something is going awry.
The code I am using to set everything up is:
public class CustomServiceHostFactory : ServiceHostFactory
{
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
var container = InitializeDIContainer();
var customHost = new CustomServiceHost(serviceType, baseAddresses);
// Exception is being thrown on the line below
customHost.AddDependencyInjectionBehavior(serviceType, container);
return customHost;
}
private static IContainer InitializeDIContainer()
{
var builder = new ContainerBuilder();
builder.RegisterAssemblyTypes(typeof (AccountRepository).Assembly)
.Where(t => t.Name.EndsWith("Repository"))
.As(t => t.GetInterfaces().FirstOrDefault(
i => i.Name == "I" + t.Name));
builder.RegisterAssemblyTypes(typeof (AccountService).Assembly)
.Where(t => t.Name.EndsWith("Service"))
.As(t => t.GetInterfaces().FirstOrDefault(
i => i.Name == "I" + t.Name));
builder.RegisterType<tktktktkDbContext>().As<IDataContextAsync>();
builder.RegisterType<UnitOfWork>().As<IUnitOfWorkAsync>();
var container = builder.Build();
return container;
}
}
When this runs, I am getting the following exception:
An exception of type 'System.ArgumentException' occurred in Autofac.Integration.Wcf.dll but was not handled in user code.
Additional information: The service contract type 'tktktktk.Services.AccountClassService' has not been registered in the container.
When I put a breakpoint in the code and inspect the container, I can see all of my services, including the 'tktktktk.Services.AccountClassService' object, in the ComponentRegistry.Registrations collection.
I've tried reworking my web.config file to use the "Autofac.Integration.Wcf.AutofacServiceHostFactory" factory, but then my application fails before it even gets to this point.
I'm thinking that I missed a step somewhere, but am at a loss for what. Any assistance would be greatly appreciated!
UPDATE
I modified my web.config to use "Autofac.Integration.Wcf.AutofacServiceHostFactory" as indicated. I am now getting the following error:
WebHost failed to process a request.
Sender Information: System.ServiceModel.ServiceHostingEnvironment+HostingManager/45653674
Exception: System.ServiceModel.ServiceActivationException: The service '/Account/AccountClassService.svc' cannot be activated due to an exception during compilation. The exception message is: The AutofacServiceHost.Container static property must be set before services can be instantiated.. ---> System.InvalidOperationException: The AutofacServiceHost.Container static property must be set before services can be instantiated.
UPDATE 2
I tried what is suggested in the answer below, and I got the same error as above. One thing I noted, When I added AutofacServiceHost.Container = container to my code, that would not compile. I switched that to AutofacHostFactory.Container and it compiled fine. Also, with the web.config changed to use Autofac.Integration.Wcf.AutofacServiceHostFactory, I no longer hit any breakpoints in this code, suggesting that it is being bypassed completely now.

I also attempted svc-less configuration. I followed the steps in the documentation and received this error message:
The service 'MyService' configured for WCF is not registered with the Autofac container.
It turns out everything was registered correctly however the "add factory" element in web.config needs the assembly name of the service just as the .svc file would if it existed:
<add factory="Autofac.Integration.Wcf.AutofacServiceHostFactory" relativeAddress="~/MyService.svc" service="NameSpace.Service, AssemblyName" />
The Autofac documentation does not mention this or show it in the example. Also, Visual Studio will complain and underline the attribute however the service will run correctly.

Here's the situation you're using your custom host, on the other hand the Autofac have it's builtin ServiceHost provided as out of the box feature.
You need to set the property Container of AutofacServiceHost class:
public class CustomServiceHostFactory : ServiceHostFactory
{
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
var container = InitializeDIContainer();
// Add this line and try again.
AutofacServiceHost.Container = container;
var customHost = new CustomServiceHost(serviceType, baseAddresses);
customHost.AddDependencyInjectionBehavior(serviceType, container);
return customHost;
}
private static IContainer InitializeDIContainer()
{
var builder = new ContainerBuilder();
builder.RegisterAssemblyTypes(typeof (AccountRepository).Assembly)
.Where(t => t.Name.EndsWith("Repository"))
.As(t => t.GetInterfaces().FirstOrDefault(
i => i.Name == "I" + t.Name));
builder.RegisterAssemblyTypes(typeof (AccountService).Assembly)
.Where(t => t.Name.EndsWith("Service"))
.As(t => t.GetInterfaces().FirstOrDefault(
i => i.Name == "I" + t.Name));
builder.RegisterType<tktktktkDbContext>().As<IDataContextAsync>();
builder.RegisterType<UnitOfWork>().As<IUnitOfWorkAsync>();
var container = builder.Build();
return container;
}
}

I ran into this problem myself. I managed to solve this by using the Named style registration
```
builder.RegisterType<YourServiceType>()
.Named<object>("myservice");
//and in your config:..
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" >
<serviceActivations>
<add relativeAddress="~/serviceurl.svc" service="myservice" factory="Autofac.Integration.Wcf.AutofacServiceHostFactory"/>
</serviceActivations>
</serviceHostingEnvironment>
```
Note that in my case i am not using apsnetcompatibilitymode.
I think its a bug in the AutofacHostFactory, its using the wrong servicename identifier to resolve the wcfservice type.
Using the Named registration style, your basically making sure that the type the registration is known under and the name used by the AutofacHostFactory match up

Related

DbContextOptionsBuilder is not working as expected at ASP.NET Core 6 project?

When I configure my Startup file as below,
public void ConfigureServices(IServiceCollection services)
{
var dbContextOptions = new DbContextOptionsBuilder<StoreContext>();
dbContextOptions.UseSqlite(_config.GetConnectionString("DefaultConnection"));
if (_env.IsDevelopment())
{
dbContextOptions.EnableSensitiveDataLogging();
}
services.AddDbContext<StoreContext>(x => x = dbContextOptions);
services.AddApplicationServices(_env, _config);
services.AddAutoMapper(typeof(MappingProfiles));
services.AddControllers();
services.AddSwaggerDocumentation();
services.AddCors();
}
the app gives me the below error:
System.InvalidOperationException: No database provider has been configured for this DbContext. A provider can be configured by overriding the 'DbContext.OnConfiguring' method or by using 'AddDbContext' on the application service provider. If 'AddDbContext' is used, then also ensure that your DbContext type accepts a DbContextOptions object in its constructor and passes it to the base constructor for DbContext.
When I changed this line
services.AddDbContext<StoreContext>(x => x = dbContextOptions);
to
services.AddDbContext<StoreContext>(x => x.UseSqlite(_config.GetConnectionString("DefaultConnection")));
then it is working. But what am I missing, why doesn't work first code.
I believe the issue is you are assigning parameter passed into an action you are creating by x => x = dbContextOptions, which is in the beginning reference to existing instance of options, but it would’t change reference outside of the action. Just inside the action the reference is changed.
Try this.
string original = "original";
Action<string> action = x => x = "new";
action(original);
Console.WriteLine(original); // Output: original
Console.ReadKey();

Environment specific error: Cannot consume scoped service from singleton 'Microsoft.AspNetCore.Hosting.Internal.HostedServiceExecutor'

I have developed a .Net Core background service which was working fine both when running as a service and when debugging in Visual Studio.
Then the following error started happening but only when debugging in Visual Studio
Cannot consume scoped service myDbContext from singleton 'Microsoft.AspNetCore.Hosting.Internal.HostedServiceExecutor'"
On deployment the service does not experience the same problem.
I've read several posts saying to get around this by creating a scoped service, however if I set the project up on a different machine by getting the code from source control, the error doesn't happen.
I have deleted the project from the problem environment and pulled the code from source control again but the issue still arises.
To me this suggests it's an environment issue, rather than a coding one.
The basic code is below.
The issue arises at the line host.Run();.
Does anyone have any pointers as to how to isolate the cause and therefore potentially find a fix?
Thanks
public class Program
{
public static void Main(string[] args)
{
var method = MethodBase.GetCurrentMethod();
var methodName = method.Name;
var type = method.DeclaringType;
Log.Information("{0}.{1}: Started.", type, methodName);
try
{
var isService = !(Debugger.IsAttached || args.Contains("--console"));
if (isService)
{
var pathToExe = Process.GetCurrentProcess().MainModule.FileName;
var pathToContentRoot = Path.GetDirectoryName(pathToExe);
Directory.SetCurrentDirectory(pathToContentRoot);
}
var builder = CreateWebHostBuilder(args.Where(arg => arg != "--console").ToArray());
var host = builder.Build();
if (isService)
{
host.RunAsWebCustomService();
}
else
{
host.Run();
}
return;
}
catch (Exception ex)
{
Log.Fatal(ex, "Error in {0{.{1}: {2}", type, methodName, ex.Message);
throw;
}
finally
{
Log.CloseAndFlush();
}
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
var method = MethodBase.GetCurrentMethod();
var methodName = method.Name;
var type = method.DeclaringType;
Log.Information("{0}.{1}:called", type, methodName);
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
var builder = new ConfigurationBuilder()
.SetBasePath(Path.Combine(AppContext.BaseDirectory))
.AddJsonFile("appsettings.json", optional: true);
var configuration = builder.Build();
var url = configuration["WebHostBuilder:Url"];
return WebHost.CreateDefaultBuilder(args)
.UseUrls(url)
.ConfigureServices(
(hostContext, services) =>{services.AddHostedService<Worker>();})
.UseStartup<Startup>()
.UseSerilog((hostingContext, loggerConfiguration) => loggerConfiguration.ReadFrom.Configuration(hostingContext.Configuration));
}
}
I have been working in dotnet core since 2 years and things that i found out is:-
Do not resolve a scoped service from a singleton. It may cause the service to have incorrect state when processing subsequent requests. It's fine to:
Resolve a singleton service from a scoped or transient service.
Resolve a scoped service from another scoped or transient service.
By default, in the development environment, resolving a service from another service with a longer lifetime throws an exception. For more information, Scope Validation

SimpleInjector Lazy in a Reflection

We are using SimpleInjector as a Dependency Injector, and we are registering all interface types using assembly iteration.
public static void RegisterInterfaceTypes(this Container container, Assembly assembly)
{
assembly.GetExportedTypes()
.Select(t => new {
Type = t,
Interface = t.GetInterfaces().FirstOrDefault()
})
.ToList()
.ForEach(t =>
{
container.Register(t.Interface, t.Type, Lifestyle.Transient);
});
}
We also have lazy classes to register. We can register these classes like below one by one. But we want to register all lazy types with similar iteration using reflection.
container.Register(() => new Lazy<ICommonBusiness>(container.GetInstance<CommonBusiness>));
You can make use of the ResolveUnregisteredType extension method to make last-minute registrations for resolve Lazy<T> dependencies:
Source:
public static void AllowResolvingLazyFactories(this Container container)
{
container.ResolveUnregisteredType += (sender, e) =>
{
if (e.UnregisteredServiceType.IsGenericType &&
e.UnregisteredServiceType.GetGenericTypeDefinition() == typeof(Lazy<>))
{
Type serviceType = e.UnregisteredServiceType.GetGenericArguments()[0];
InstanceProducer registration = container.GetRegistration(serviceType, true);
Type funcType = typeof(Func<>).MakeGenericType(serviceType);
Type lazyType = typeof(Lazy<>).MakeGenericType(serviceType);
var factoryDelegate = Expression.Lambda(funcType, registration.BuildExpression()).Compile();
var lazyConstructor = (
from ctor in lazyType.GetConstructors()
where ctor.GetParameters().Length == 1
where ctor.GetParameters()[0].ParameterType == funcType
select ctor)
.Single();
var expression = Expression.New(lazyConstructor, Expression.Constant(factoryDelegate));
var lazyRegistration = registration.Lifestyle.CreateRegistration(
serviceType: lazyType,
instanceCreator: Expression.Lambda<Func<object>>(expression).Compile(),
container: container);
e.Register(lazyRegistration);
}
};
}
Usage:
container.AllowResolvingLazyFactories();
But please note the warnings from the documentation:
Warning: Registering [Lazy<T>] by default is a design smell. The use of [Lazy<T>] makes your design harder to follow and your system harder to maintain and test. Your system should only have a few of those [...] at most. If you have many constructors in your system that depend on a [Lazy<T>], please take a good look at your dependency strategy. The following article goes into details about why [this is] a design smell.
Warning: [...] the constructors of your components should be simple, reliable and quick (as explained in this blog post by Mark Seemann). That would remove the need for lazy initialization. For more information about creating an application and container configuration that can be successfully verified, please read the How To Verify the container’s configuration.

Using two Simple Injector container instances with different lifestyles in c#

I have a web forms application in the Global.asax of which I am buiding the Simple Injector container like below. The reason I am doing two is because I am using Hangfire to schedule recurring jobs and it does not take the Scoped lifestyle which I currently have for the application since it runs as a background worked thread. I am getting the below error when I am creating two instances of the container for my EF entities.
The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects
Can someone please tell me how I can have two containers with different lifestyles registered in my web forms applictaion.
ContainerConfig.BuildContainer();
var container = ContainerConfig.BuildContainerJobs();
public static Container BuildContainer()
{
var container = new Container();
container.Options.DefaultScopedLifestyle = new ExecutionContextScopeLifestyle();
container.Register<TraceTimer>(Lifestyle.Scoped);
container.Register<Entities>(() => new Entities(), Lifestyle.Scoped);
container.Register<ReferenceDataCache>(
() => ReferenceDataCacheFactory.Create(), Lifestyle.Scoped);
var adapter = new SimpleInjectorAdapter(container);
ServiceLocator.SetLocatorProvider(() => (IServiceLocator)adapter);
ExecutionContextScopeManager.Current = (IExecutionContextScopeManager)adapter;
return container;
}
public static Container BuildContainerJobs()
{
var container = new Container();
container.Options.DefaultScopedLifestyle = new ExecutionContextScopeLifestyle();
container.Register<Entities>(() => new Entities(), Lifestyle.Transient);
container.Register<ReferenceDataCache>(
() => ReferenceDataCacheFactory.Create(), Lifestyle.Transient);
var adapter = new SimpleInjectorAdapter(container);
ServiceLocator.SetLocatorProvider(() => (IServiceLocator)adapter);
ExecutionContextScopeManager.Current = (IExecutionContextScopeManager)adapter;
return container;
}
Global.asax code for registering
ContainerConfig.BuildContainer();
var container = ContainerConfig.BuildContainerJobs();
var options = new SqlServerStorageOptions
{
QueuePollInterval = TimeSpan.FromMinutes(5) // Default value
};
GlobalConfiguration.Configuration
.UseSqlServerStorage("Jobs",options);
GlobalConfiguration.Configuration.UseDefaultActivator();
GlobalConfiguration.Configuration.UseActivator(new SimpleInjectorJobActivator(container));
GlobalJobFilters.Filters.Add(new SimpleInjectorAsyncScopeFilterAttribute(container));
JobsHelper.SetRecurringJob();
_backgroundJobServer = new BackgroundJobServer();
This exception is not thrown by Simple Injector but by Entity Framework. This exception is typically caused by using instances of entities that are created with one DbContext inside another DbContext.
Unfortunately I can't be more specific and pinpoint where you are going wrong and how to fix this, because your question does not contain the appropriate details.

Managing Lifetime Scopes with Autofac

Context: Owin (self-host) + WebApi + UseAutofacMiddleware + UseAutofacWebApi
What I'm trying to do is:
Register an ILog instance in the app startup container.
For each request, register a new ILog instance wrapping the "root" instance, so that each middleware and/or per-request services can use it.
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterInstance(log).As<ILog>();
containerBuilder.Register(ctx => {
var rootLog = ctx.Resolve<ILog>();
return new PrependStringLog(rootLog, "request: ");
}).InstancePerRequest();
However, Autofac complains about circular dependencies when instancing middleware having an ILog in their constructors.
If I name the "root log", and resolve with the given name, everything works as expected.
containerBuilder.RegisterInstance(log)
.Named("root", typeof(ILog));
containerBuilder.Register(ctx => {
var rootLog = ctx.ResolveNamed<ILog>("root");
return new PrependStringLog(rootLog, "request: ");
}).InstancePerRequest();
Am I forced to use a named instance to make it work?
Autofac uses the latest registered service when a component request for a service.
In your case, the latest ILog registered is a lambda expression :
containerBuilder.Register(ctx => {
var rootLog = ctx.Resolve<ILog>();
return new PrependStringLog(rootLog, "request: ");
}).InstancePerRequest();
This lambda expression request an ILog which is what Autofac is trying to build : that's why it detects a circular dependency.
The easiest way to avoid the circular dependency is to make your registration not rely on itself. This is what you do by resolving a named ILog and this is the solution I recommend.
In your case, you can also directly use the root log without resolving it :
containerBuilder.RegisterInstance(rootLog).As<ILog>();
containerBuilder.Register(ctx => {
return new PrependStringLog(rootLog, "request: ");
}).InstancePerRequest();

Categories

Resources