ASP.NET Core View Injection problems - c#

Has anyone tried to use the new View Injection from ASP.NET Core?
I'm trying to use straight forward as described on the documentation (https://docs.asp.net/en/latest/mvc/views/dependency-injection.html) but no success at all.
The unique diference from my implementation and the documentation is that I'm using AutoFac for DI.
When I try to use the injection on my view I get an exception that my Service has not been registered.
#inject Domain.Service.LevelService LevelService
Error Message:
ComponentNotRegisteredException: The requested service 'Domain.Service.LevelService' 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.
Btw, the service is correctly registered and can be accessed from the controller for example.
Edit to include Startup:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
services.AddMemoryCache();
services.AddSession();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
return new AutofacServiceProvider(DependencyInjection.RegisterServices(services));
}
Code of the method RegisterServices:
public static IContainer RegisterServices(IServiceCollection services)
{
// Create the container builder.
var builder = new ContainerBuilder();
var assembly = Assembly.GetExecutingAssembly();
assembly.GetTypes()
.Where(x => x.IsSubclassOf(typeof(ServiceInjectionModule)))
.ToList()
.ForEach(x =>
{
var t = (ServiceInjectionModule)Activator.CreateInstance(x, new object[] { true });
t.AddtoContainer(builder);
});
// Add automapper configurations
var mapperConfiguration = AutoMapperConfig.Configure();
var mapper = mapperConfiguration.CreateMapper();
builder.RegisterInstance(mapper).As<IMapper>();
// Populate default services
builder.Populate(services);
return builder.Build();
}

The problem is in the assembly scanning section you've written. It's much easier to use the built in functionality of AutoFac. Not sure your code is .Net Core just based on the fact you're not using GetTypeInfo. GetTypeInfo is backwards compatible so will work with .Net 4.x
public static IContainer RegisterServices(IServiceCollection services)
{
// Create the container builder.
var builder = new ContainerBuilder();
var assembly = Assembly.GetExecutingAssembly();
builder.RegisterAssemblyTypes(assembly)
.Where(t => t.GetTypeInfo().IsSubclassOf(typeof(ServiceInjectionModule)))
.AsSelf();
// Add automapper configurations
var mapperConfiguration = AutoMapperConfig.Configure();
var mapper = mapperConfiguration.CreateMapper();
builder.RegisterInstance(mapper).As<IMapper>();
// Populate default services
builder.Populate(services);
return builder.Build();
}

OK, I solved the problem.
Well, I didn't paid attention and seems that no one too :p.
The problem is that I'm trying to inject an instance and not an interface. Just changed the implementation and everything started working.
Final code:
#inject Domain.Service.Interfaces.ILevelService LevelService

Related

Inject SignalR IHubContext into controller with Autofac

I'm trying to inject a SignalR IHubContext into a Web API 2.x controller in an ASP.NET MVC 5 app Framework 4.72 (not .NET Core). It's throwing this exception when calling the Web API controller MyController:
An error occurred when trying to create a controller of type 'MyController'. Make sure that the controller has a parameterless public constructor
The inner exception says:
None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'MyController' can be invoked with the available services and parameters: Cannot resolve parameter 'Microsoft.AspNet.SignalR.IHubContext[MyHub] context' of constructor 'Void .ctor(Microsoft.AspNet.SignalR.IHubContext [MyHub])'.
I don't mind doing this using property injection but haven't had any luck getting that to work. So I'm doing injection into the c'tor of the controller.
I've followed these answers for help:
https://stackoverflow.com/a/37913821/177416 --> c'tor injection
https://stackoverflow.com/a/29793864/177416 --> c'tor injection
https://stackoverflow.com/a/26810399/177416 --> property injection
https://stackoverflow.com/a/15600493/177416 --> property injection
Here's the Web API controller:
public class MyController : WebApiController
{
public IHubContext<MyHub> Context { get; set; }
public MyController(IHubContext<MyHub> context)
{
Context = context;
}
}
And here's the pertinent part of the Startup.cs:
public void Configuration(IAppBuilder app)
{
// Other code...
var builder = new ContainerBuilder();
var config = new HttpConfiguration();
builder.RegisterHubs(Assembly.GetExecutingAssembly());
builder.RegisterControllers(typeof(MvcApplication).Assembly)
.InstancePerRequest();
builder.RegisterApiControllers(Assembly.GetExecutingAssembly())
.InstancePerRequest();
builder.RegisterType<AutofacDependencyResolver>()
.As<IDependencyResolver>()
.SingleInstance();
builder
.Register(c => c.Resolve<IConnectionManager>().GetHubContext<MyHub>())
.Named<IHubContext>("MyHub");
builder.RegisterType<MyController>()
.WithParameter(
new ResolvedParameter(
(pi, ctx) => pi.ParameterType == typeof(IHubContext),
(pi, ctx) => ctx.ResolveNamed<IHubContext>("MyHub")
)
);
var container = builder.Build();
app.UseAutofacMiddleware(container);
DependencyResolver.SetResolver(new Autofac.Integration.Mvc.AutofacDependencyResolver(container));
config.DependencyResolver = new AutofacWebApiDependencyResolver((IContainer)container);
app.Map("/signalr", map =>
{
var hubConfiguration = new HubConfiguration
{
Resolver = new AutofacDependencyResolver(container),
};
map.RunSignalR(hubConfiguration);
});
}
What am I missing? Thanks.
Your first problem is that typeof(IHubContext) is not the same as typeof(IHubContext<MyHub>). You can get around that by using:
pi.ParameterType == typeof(IHubContext).MakeGenericType(typeof(MyHub))
However, old versions of SignalR don't support the generic interfaces very well, so it would probably work better if you left the comparison as is, and inject an IHubContext rather than an IHubContext<MyHub> in MyController.

Mapster Global Configuration with Dependency Injection

I'd like to know if there is a way to globally configure Mapster while using Dependency Injection?
The configuration options appear to be for the static usage and also for a singleton pattern only.
Mapster Configuration
Mapster Dependency Injection
I have created an extension method.
// Extension method
public static IServiceCollection AddMapster(this IServiceCollection services, Action<TypeAdapterConfig> options = null)
{
var config = new TypeAdapterConfig();
config.Scan(Assembly.GetAssembly(typeof(Startup)));
options?.Invoke(config);
services.AddSingleton(config);
services.AddScoped<IMapper, ServiceMapper>();
return services;
}
// Called in Startup.ConfigureServices(IServiceCollection services)
services.AddMapster(options =>
{
options.Default.IgnoreNonMapped(true); // Does not work.
TypeAdapterConfig.GlobalSettings.Default.IgnoreNonMapped(true); // Does not work.
});
I imagine these don't work because the ServiceMapper is creating its own instance without using anything I've configured.
I implemented Mapster in a Blazor Server application, and I struggled to find documentation on how to scan the assembly for mapping registrations.
I have a class in my application that implements the IRegister interface and defines the mappings
public class MappingRegistration : IRegister
{
void IRegister.Register(TypeAdapterConfig config)
{
config.NewConfig<ModelA, ModelB>();
}
}
In the ConfigureServices of the Startup.cs I have this then
var typeAdapterConfig = TypeAdapterConfig.GlobalSettings;
// scans the assembly and gets the IRegister, adding the registration to the TypeAdapterConfig
typeAdapterConfig.Scan(Assembly.GetExecutingAssembly());
// register the mapper as Singleton service for my application
var mapperConfig = new Mapper(typeAdapterConfig);
services.AddSingleton<IMapper>(mapperConfig);
I hope this can save someone's time. If anybody is aware of better ways, please let me know.
You can change from
var config = new TypeAdapterConfig();
to
var config = TypeAdapterConfig.GlobalSettings;

Simple Injector integration with HostBuilder in .NET Core 2.x

We are developing a windows service that runs .net core 2.x. Following this blog post by Steve Gordon running .netcore generic host applications as a service things seem to be working beautifully... as long as we use the IServiceCollection. I prefer SimpleInjector but I'm not sure how I can use it like I do in asp.net core. I there's a way to replace the built in DI as described here Default service container replacement and I know the SI team doesn't recommend the approach ASP.NET Core MVC Integration Guide so is there a better way in this use case?
Here is what I have so far but it's uncomfortable
--main program
internal class Program
{
private static async Task Main(string[] args)
{
var isService = !(Debugger.IsAttached || args.Contains("--console"));
var builder = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Runner>();
//configure SimpleInjector here???
});
if (isService)
{
await builder.RunAsServiceAsync();
}
else
{
await builder.RunConsoleAsync();
}
}
}
Configuring the container here works more or less but the first class being created by the host (i.e. Runner in this case) gets created by the Host and injects any dependencies via the IServicesCollection. So my question is how do I have it inject from my SI container instead?
The obvious answer here is... Don't have any dependencies injected into the Runner. Instead Runner is the class that represents your application entry point so I configure my container there and dispose of it when the Runner is stopped. The complete code for runner...
public class Runner : IHostedService, IDisposable
{
private Container _container;
public Runner()
{
_container = new Container();
_container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
}
public Task StartAsync(CancellationToken cancellationToken)
{
Bootstrapper.Bootstrap(_container);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public void Dispose()
{
_container.Dispose();
_container = null;
}
}
I would hook onto the ConfigureContainer method of the HostBuilder and setup simpleinjectore there likke this:
HostBuilder()
.ConfigureContainer<ServiceCollection>((builder, services) =>
{
var container = new Container();
container.RegisterSingleton<IJobRepository, JobRepository>();
services.AddTransient<IHostedService, TimedService>();
})
.ConfigureServices((hostContext, services) =>
{
// Originally we would have done this
//services.AddHostedService<Service>();
})
.Build();
using (host)
{
await host.StartAsync();
await host.WaitForShutdownAsync();
}
While you could use your IHostedService implementation indeed I think it may hide what is going on. I believe the infrastructure bootstrapping should be done in one place or orchestrated at least in one place. I consider the container to be infrastructure and would set it all up with the rest of the app via the HostBuilder methods.
An added advantage may also be that you do not entirely replace the ServiceCollection as it works well with doing other framework related things. An example of some stuff I would still do with the ServiceCollection:
HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddLogging();
services.AddOptions();
})
This is in line with what is stated in the simpleinjector docs about setting the container up with ASP.NET Core:
The practice with Simple Injector is to use Simple Injector to build up object graphs of your application components and let the built-in container build framework and third-party components,The practice with Simple Injector is to use Simple Injector to build up object graphs of your application components and let the built-in container build framework and third-party components
The same should apply with just .net core and the generic HostBuilder.
Generic host resolves hosted services from services collection, so the solution is to register hosted services in Simple Injector and then resolve them from Simple Injector to register in Services collection:
var container = new Container();
var host = new HostBuilder()
//...
.ConfigureServices((context, services) =>
{
container.Collection.Append(typeof(IHostedService), typeof(Runner));
services.AddSingleton(_ => container.GetAllInstances<IHostedService>());
})
//...
.Build();
container.Verify();
await host.RunAsync();

AutoFac / .NET Core - Register DBcontext

I have a new .NET Core Web API project that has the following projects structure:
API -> Business / Domain -> Infrastructure
The API is very thin with only the API methods. The Business / Domain layer has all my business logic. And finally, my Infrastructure layer has my DB classes using EF Core 2.0.
I know using .NET Core built-in Dependency Injection I can add a reference from the API project to the Infrastructure project, then add the following code in the StartUp.cs file:
services.AddDbContext<MyContext>(options => options.UseSqlServer(connectionString));
However, I would like to maintain a more traditional separation of concerns. So far I have added a module in my Infrastructure layer that attempts to make the registration like so:
builder.Register(c =>
{
var config = c.Resolve<IConfiguration>();
var opt = new DbContextOptionsBuilder<MyContext>();
opt.UseSqlServer(config.GetSection("ConnectionStrings:MyConnection:ConnectionString").Value);
return new MyContext(opt.Options);
}).AsImplementedInterfaces().InstancePerLifetimeScope();
The DBContext, however, is not getting registered. Any class that attempts to access the injected DBContext cannot resolve the parameter.
Is there a way to register the DBContext in a separate project using AuftoFac in a .NET Core Web API Project?
I use Autofac to register both HttpContextAccessor and DbContext.
builder
.RegisterType<HttpContextAccessor>()
.As<IHttpContextAccessor>()
.SingleInstance();
builder
.RegisterType<AppDbContext>()
.WithParameter("options", DbContextOptionsFactory.Get())
.InstancePerLifetimeScope();
DbContextOptionsFactory
public class DbContextOptionsFactory
{
public static DbContextOptions<AppDbContext> Get()
{
var configuration = AppConfigurations.Get(
WebContentDirectoryFinder.CalculateContentRootFolder());
var builder = new DbContextOptionsBuilder<AppDbContext>();
DbContextConfigurer.Configure(
builder,
configuration.GetConnectionString(
AppConsts.ConnectionStringName));
return builder.Options;
}
}
DbContextConfigurer
public class DbContextConfigurer
{
public static void Configure(
DbContextOptionsBuilder<AppDbContext> builder,
string connectionString)
{
builder.UseNpgsql(connectionString).UseLazyLoadingProxies();
}
}
I think that the problem is that you're trying to register MyContext() using AsImplementedInterfaces(). This is not how DbContext are getting registered usually. You should register and resolve class itself.
Another simple solution for Autofac version 4.8.1
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().AddControllersAsServices();
services.AddDbContext<MyContext>(options => options.UseSqlServer(Configuration.GetConnectionString("ConnectionStrings:MyConnection:ConnectionString")));
var builder = new ContainerBuilder();
builder.Populate(services);
//...
// Your interface registration
//...
builder.Build(Autofac.Builder.ContainerBuildOptions.None);
}
Here's an implementation I use - it mimics EF Core 3.1 registration with Autofac 4.9.4. Be sure to adjust scopes per your requirements.
public void RegisterContext<TContext>(ContainerBuilder builder)
where TContext : DbContext
{
builder.Register(componentContext =>
{
var serviceProvider = componentContext.Resolve<IServiceProvider>();
var configuration = componentContext.Resolve<IConfiguration>();
var dbContextOptions = new DbContextOptions<TContext>(new Dictionary<Type, IDbContextOptionsExtension>());
var optionsBuilder = new DbContextOptionsBuilder<TContext>(dbContextOptions)
.UseApplicationServiceProvider(serviceProvider)
.UseSqlServer(configuration.GetConnectionString("MyConnectionString"),
serverOptions => serverOptions.EnableRetryOnFailure(5, TimeSpan.FromSeconds(30), null));
return optionsBuilder.Options;
}).As<DbContextOptions<TContext>>()
.InstancePerLifetimeScope();
builder.Register(context => context.Resolve<DbContextOptions<TContext>>())
.As<DbContextOptions>()
.InstancePerLifetimeScope();
builder.RegisterType<TContext>()
.AsSelf()
.InstancePerLifetimeScope();
}
In the desired project you can create an extension method that adds the context to the collection
public static class MyDataExtensions {
public static IServiceCollection AddMyData(this IServiceCollection services) {
//...
services.AddDbContext<MyContext>(options => options.UseSqlServer(connectionString));
//...
}
}
with that then in your start up it is just a matter of calling the extension exposed from the other project
services.AddMyData();
//...other settings
The API project is the composition root, so it needs to know all the relevant dependencies anyway. At least with this extension you do not have to make direct reference of the used db context,

DI autofac & mvc 6 beta7

I'm having trouble with Autofac resolve. I can't use this with mvc 6 beta7.
Using dependences:
"Autofac": "4.0.0-beta7-130",
"Microsoft.AspNet.Mvc": "6.0.0-beta7",
My Startup.cs
public IContainer Container { get; set; }
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
// Create the autofac container
var builder = new ContainerBuilder();
// Create the container and use the default application services as a fallback
//AutofacRegistration.Populate(builder, services);
// Add any Autofac modules or registrations.
builder.RegisterModule(new AutofacModule());
Container = builder.Build();
}
public void Configure(IApplicationBuilder app)
{
//app.Run(async (context) =>
//{
// await context.Response.WriteAsync("Hello World!");
//});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
// Uncomment the following line to add a route for porting Web API 2 controllers.
// routes.MapWebApiRoute("DefaultApi", "api/{controller}/{id?}");
});
app.ApplicationServices = Container.Resolve<IServiceProvider>();
}
Take this exception
An exception of type 'Autofac.Core.Registration.ComponentNotRegisteredException' occurred in Autofac.dll but was not handled in user code
Additional information: The requested service 'System.IServiceProvider' 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.
How to use autofac with MVC 6 beta 7?
You can replace the ConfigureServices method with the following to get autofac to work.
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// Add MVC services to the services container.
services.AddMvc();
// Create the autofac container
var builder = new ContainerBuilder();
// Create the container and use the default application services as a fallback
//AutofacRegistration.Populate(builder, services);
// Populate the services.
builder.Populate(services);
// Add any Autofac modules or registrations.
builder.RegisterModule(new AutofacModule());
Container = builder.Build();
// Resolve and return the service provider.
return Container.Resolve<IServiceProvider>();
}

Categories

Resources