Unable to inject ActionFilter instance using AutoFac - autofac

Can someone please tell me where I'm going wrong. I have an action with the following custom filter attribute...
[UserValidation("Standard")]
public ActionResult Index()
The custom attribute is defined as...
public class UserValidationAttribute : FilterAttribute, IAutofacActionFilter
{
public ILogger Logger { get; set; }
private string _status;
public UserValidationAttribute (string status)
{
// At this point in the code, 'Logger' is null
_status = status;
}
public void OnActionExecuting(HttpActionContext actionContext)
{
}
public void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
}
}
And I'm building the AutoFac container as...
var builder = new ContainerBuilder();
builder.RegisterControllers(Assembly.GetExecutingAssembly());
builder.Register(x => new Logger()
.As<ILogger>()
.InstancePerHttpRequest();
builder.RegisterFilterProvider();
var container = builder.Build();
System.Web.Mvc.DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
and for some reason when the custom action filter's constructor is 'hit', the public 'Logger' property is null?
Any help would be appreciated.
Thanks

Related

Get current logged in user in DbContext

For audit purposes I'm trying to get the current logged in user in my DbContext. However I'm having some issues with this. A few things to take into account:
In Blazor Server we have to use AddDbContextFactory
IHttpContextAccessor returns no result in deployed website (might be because IHttpContextAccessor is not thread safe?)
I created a custom DbContext that injects AuthenticationStateProvider.
public partial class CustomDbContext : DbContext
{
private AuthenticationStateProvider _authenticationStateProvider;
#region construction
public CustomDbContext ()
{
}
public CustomDbContext (AuthenticationStateProvider stateProvider)
{
_authenticationStateProvider = stateProvider;
}
[ActivatorUtilitiesConstructor]
public CustomDbContext (DbContextOptions<CustomDbContext> options, AuthenticationStateProvider stateProvider) : base(options)
{
_authenticationStateProvider = stateProvider;
}
public CustomDbContext(DbContextOptions<CustomDbContext> options) : base(options)
{
}
#endregion
...
In this DbContext, when overwriting the SaveChanges I get the User and their claims:
var state = await _authenticationStateProvider.GetAuthenticationStateAsync();
var userIdClaim = state.User.Claims.FirstOrDefault(c => c.Type == "userId")?.Value;
userId = userIdClaim != null && !string.IsNullOrEmpty(userIdClaim ) ? userIdClaim : string.Empty;
...
However when I call .CreateDbContext(); on the injected DbContextFactory, I get the following exception:
'Cannot resolve scoped service
'Microsoft.AspNetCore.Components.Authorization.AuthenticationStateProvider'
from root provider.'
I've found some topics about this, but the suggested solution there is to create a custom DbContextFactory that is scoped. But then you lose the reason why you are using the DbContextFactory, no?
Any ideas on how to solve this?
Thank you
The DBContextFactory is a singleton registered in the root application DI container, while the AuthenticationStateProvider is a scoped service that is registered in the Hub session DI container. You can't access a lower order service from a higher order service.
You need to rethink your design and provide the user information from whatever scoped service is making whatever call to need a DbConbtext.
Additional Information
I'm not sure what your data pipeline looks like so this example uses the Blazor template weather forecast.
First a View Service that components inject and use.
This injects the AuthenticationStateProvider. It gets the current user for each request and passes it to the data pipeline in a request object.
public class WeatherForecastViewService
{
private AuthenticationStateProvider _authenticationStateProvider; // scoped service
private WeatherForecastService _weatherForecastService; //Singleton Service
public WeatherForecastViewService(AuthenticationStateProvider authenticationStateProvider, WeatherForecastService weatherForecastService)
{
_authenticationStateProvider = authenticationStateProvider;
_weatherForecastService = weatherForecastService;
}
public async ValueTask SaveWeatherForecast(WeatherForecast record)
{
var user = await GetCurrentUser();
var request = new RecordRequest<WeatherForecast>(record, user );
await _weatherForecastService.SaveRecord(request);
}
private async ValueTask<ClaimsPrincipal> GetCurrentUser()
{
var state = await _authenticationStateProvider.GetAuthenticationStateAsync();
return state.User ?? new ClaimsPrincipal();
}
}
Here are the request and result objects:
public readonly struct RecordRequest<TRecord>
{
public TRecord Record { get; init; }
public ClaimsPrincipal Identity { get; init; }
public RecordRequest(TRecord record, ClaimsPrincipal identity)
{
this.Record = record;
this.Identity = identity;
}
}
public record RecordResult
{
public bool SuccessState { get; init; }
public string Message { get; init; }
private RecordResult(bool successState, string? message)
{
this.SuccessState = successState;
this.Message = message ?? string.Empty;
}
public static RecordResult Success(string? message = null)
=> new RecordResult(true, message);
public static RecordResult Failure(string message)
=> new RecordResult(false, message);
}
And here's the singleton data service
public class WeatherForecastDataService
{
// This is a singleton
private readonly IDbContextFactory<DbContext> _factory;
public WeatherForecastDataService(IDbContextFactory<DbContext> factory)
=> _factory = factory;
public async ValueTask<RecordResult> SaveRecord(RecordRequest<WeatherForecast> request)
{
if (!request.Identity.IsInRole("SomeRole"))
return RecordResult.Failure("User does not have authority");
// simulates some async DB activity
await Task.Delay(100);
// Get your DbContext from the injected Factory
// using var dbContext = this.factory.CreateDbContext();
// do your db stuff
return RecordResult.Success();
}
}
PS I haven'y actually run this code so there may be some typos!
IHttpContextAccessor returns no result in deployed website (might be because IHttpContextAccessor is not thread safe?)
Nothing to do with whether IHttpContextAccessor is not thread safe... It's simply because the HttpContext object is not available in Blazor Server App, as communication between the client side (browser) and server side is done through the SignalR protocol, not HTTP. But there is a way how to access the HttpContext object before the Blazor App is rendered, as the initial call to the app is always made through HTTP request; that is, when you enter a url into the address bar of your browser and hit the enter button. See here how to do that...
The following code snippet describes how to inject an AuthenticationStateProvider into the ApplicationDbContext object created by default when you select Individual Accounts in Blazor Server App.
Copy and test. It should work...
Data/ApplicationDbContext.cs
public class ApplicationDbContext : IdentityDbContext
{
public DbSet<Employee> Employees { get; set; }
private AuthenticationStateProvider _authenticationStateProvider;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext>
options, AuthenticationStateProvider stateProvider)
: base(options)
{
_authenticationStateProvider = stateProvider;
}
public override async Task<int>
SaveChangesAsync(CancellationToken cancellationToken)
{
var stateProvider = await
_authenticationStateProvider.GetAuthenticationStateAsync();
if (stateProvider.User.Identity.IsAuthenticated)
{
Console.WriteLine("Authenticated User name: " +
stateProvider.User.Identity.Name);
}
// Delegate the saving action to the base class
return await base.SaveChangesAsync(cancellationToken);
}
}
Create an Employee Repository class service:
EmployeeRepository.cs
using <put here the namespace of your app>.Data;
using <put here the namespace of your app>.Models;
using Microsoft.EntityFrameworkCore;
public class EmployeeRepository
{
private readonly ApplicationDbContext ApplicationDbContext;
public EmployeeRepository(ApplicationDbContext
applicationDbContext)
{
ApplicationDbContext = applicationDbContext;
}
public async Task<Employee> CreateEmployee(Employee employee)
{
CancellationTokenSource cancellationTokenSource = new
CancellationTokenSource();
CancellationToken token = cancellationTokenSource.Token;
await ApplicationDbContext.Employees.AddAsync(employee);
await ApplicationDbContext.SaveChangesAsync(token);
return employee;
}
}
Index.razor
#inject EmployeeRepository EmployeeRepository
#using <Put here....>.Models
<button type="button" #onclick="SaveEmployee">Save Employee</button>
#if (emp != null)
{
<div>#emp.ID.ToString()</div>
<div>#emp.FirstName</div>
<div>#emp.LastName</div>
<div>#emp.City</div>
}
#code
{
private Employee emp;
private async Task SaveEmployee()
{
Employee employee = new Employee { FirstName = "Joana", LastName = "Brown", City = "London" };
emp = await EmployeeRepository.CreateEmployee(employee);
}
}
Create model class Employee:
Models/Employee.cs
public class Employee
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string City { get; set; }
}
Note: To test this code, you'll have to create A Blazor Server App with Individual Accounts, create the database, including the Employees table
Last but not least: Startup
// Created by the default template
//services.AddDbContext<ApplicationDbContext>(options =>
// options.UseSqlServer(
// Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>(options =>
options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddDbContextFactory<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")),
ServiceLifetime.Scoped);
// This is your code...
services.AddScoped<ApplicationDbContext>(p =>
p.GetRequiredService<IDbContextFactory<ApplicationDbContext>>
().CreateDbContext());
services.AddScoped<EmployeeRepository>();
services.AddScoped<AuthenticationStateProvider,
RevalidatingIdentityAuthenticationStateProvider<IdentityUser>>();
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddSingleton<WeatherForecastService>();
UPDATE:
but does that no against the the recommendations of Microsoft? They ae suggesting to always use using
var context = DbFactory.CreateDbContext();
You mean:
using var context = DbFactory.CreateDbContext();
No, it is not against the recommendations of Microsoft. It's another way to instantiate the DbContext. I did it that way in order to stick to this code by you:
services.AddScoped<ApplicationDbContext>(p => p.GetRequiredService<IDbContextFactory<ApplicationDbContext>>().CreateDbContext());
Anyhow, these are the changes you should make in order to reflect "Microsoft's recommendations"
Change:
services.AddScoped<ApplicationDbContext>(p => p.GetRequiredService<IDbContextFactory<ApplicationDbContext>>().CreateDbContext());
To:
services.AddScoped<ApplicationDbContext>();
Change:
private readonly ApplicationDbContext ApplicationDbContext;
public EmployeeRepository(ApplicationDbContext
applicationDbContext)
{
ApplicationDbContext = applicationDbContext;
}
To:
private readonly IDbContextFactory<ApplicationDbContext>
DbFactory;
public EmployeeRepository(IDbContextFactory<ApplicationDbContext>
_DbFactory)
{
DbFactory = _DbFactory;
}
And change:
await ApplicationDbContext.Employees.AddAsync(employee);
await ApplicationDbContext.SaveChangesAsync(token);
To:
await context.Employees.AddAsync(employee);
await context.SaveChangesAsync(token);
Also add:
using var context = DbFactory.CreateDbContext();
at the beginning of the EmployeeRepository.CreateEmployee method
Run and test.
Hope this work...
New Version
Data/ApplicationDbContext.cs
public class ApplicationDbContext : IdentityDbContext
{
public DbSet<Employee> Employees { get; set; }
private AuthenticationStateProvider _authenticationStateProvider;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext>
options, AuthenticationStateProvider stateProvider)
: base(options)
{
_authenticationStateProvider = stateProvider;
}
public override async Task<int>
SaveChangesAsync(CancellationToken cancellationToken)
{
var stateProvider = await
_authenticationStateProvider.GetAuthenticationStateAsync();
if (stateProvider.User.Identity.IsAuthenticated)
{
Console.WriteLine("Authenticated User name: " +
stateProvider.User.Identity.Name);
}
// Delegate the saving action to the base class
return await base.SaveChangesAsync(cancellationToken);
}
}
Create an Employee Repository class service:
EmployeeRepository.cs
using <put here the namespace of your app>.Data;
using <put here the namespace of your app>.Models;
using Microsoft.EntityFrameworkCore;
public class EmployeeRepository
{
private readonly IDbContextFactory<ApplicationDbContext> DbFactory;
public EmployeeRepository(IDbContextFactory<ApplicationDbContext> _DbFactory)
{
DbFactory = _DbFactory;
}
public async Task<Employee> CreateEmployee(Employee
employee)
{
using var context = DbFactory.CreateDbContext();
// CancellationTokenSource provides the token and have authority to cancel the token
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken token = cancellationTokenSource.Token;
await context.Employees.AddAsync(employee);
await context.SaveChangesAsync(token);
return employee;
}
}
Index.razor
#inject EmployeeRepository EmployeeRepository
#using <Put here....>.Models
<button type="button" #onclick="SaveEmployee">Save Employee</button>
#if (emp != null)
{
<div>#emp.ID.ToString()</div>
<div>#emp.FirstName</div>
<div>#emp.LastName</div>
<div>#emp.City</div>
}
#code
{
private Employee emp;
private async Task SaveEmployee()
{
Employee employee = new Employee { FirstName = "Joana", LastName = "Brown", City = "London" };
emp = await EmployeeRepository.CreateEmployee(employee);
}
}
Create model class Employee:
Models/Employee.cs
public class Employee
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string City { get; set; }
}
Note: To test this code, you'll have to create A Blazor Server App with Individual Accounts, create the database, including the Employees table
Last but not least: Startup
public void ConfigureServices(IServiceCollection services)
{
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddDbContextFactory<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")),
ServiceLifetime.Scoped);
services.AddScoped<ApplicationDbContext>();
services.AddScoped<EmployeeRepository>();
services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<IdentityUser>>();
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddSingleton<WeatherForecastService>();
}

Inheriting logging services from base class

I have a .Net Core 3.1 API controller with a constructor that looks like this:
public class MachineListsController : ControllerBase
{
private readonly jiWeb_ProdContext _context;
private readonly ILogger _logger;
private readonly ILoggingMessageService _loggingMessage;
public MachineListsController(jiWeb_ProdContext context, ILogger<MachineListsController> logger, ILoggingMessageService loggingMessage)
{
_context = context;
_logger = logger;
_loggingMessage = loggingMessage;
}
public string Message { get; set; }
...
}
You can see that I am injecting a .Net Core logging service and the database context into it.
Then I use the logging like this in my controller methods:
[HttpGet("FactoryMachines/{factoryId}")]
public async Task<ActionResult<IEnumerable<MachineList>>> GetMachinesForFactory(Guid factoryId)
{
var machineList = await _context.MachineList.Where(n => n.FactoryId == factoryId).ToListAsync();
Message = _loggingMessage.GetLogSuccess(this.GetType().Name.ToString(), ControllerActions.GetAction, "FactoryMachines", factoryId.ToString());
_logger.LogInformation(Message);
return machineList;
}
The logging is working great, but I'm realizing that I should create a base class that handles logging so I don't have to add or change it inside of every controller I write.
So I started to write this base controller:
[ApiController]
public class MyBaseController : ControllerBase
{
readonly jiWeb_ProdContext _context;
readonly ILogger _logger;
readonly ILoggingMessageService _loggingMessage;
public BaseController(jiWeb_ProdContext context, ILogger<BaseController> logger, ILoggingMessageService loggingMessage)
{
_context = context;
_logger = logger;
_loggingMessage = loggingMessage;
}
}
Then I changed my controller to inherit from it like this:
public class MachineListsController : MyBaseController
{
[HttpGet("FactoryMachines/{factoryId}")]
public async Task<ActionResult<IEnumerable<MachineList>>> GetMachinesForFactory(Guid factoryId)
{
var machineList = await _context.MachineList.Where(n => n.FactoryId == factoryId).ToListAsync();
return machineList;
}
}
But I'm getting error and I'm unsure of what to do on the next step.
Here's the error:
There is no argument given that corresponds to the required formal parameter 'context' of 'BaseController.BaseController(jiWeb_ProdContext, ILogger<BaseController>, ILoggingMessageService)'
Specifically, how do I set up my controllers so that they can just use the base class for logging so I don't have to write logging code for every new controller action I create?
Thanks!
As far as I know, if the base class constructor method contains value, we should pass it in the subclass constructor method and also you should follow Nkosi comment to modify the property to protected.
More details, you could refer to below codes:
[ApiController]
public class MyBaseController : ControllerBase
{
protected readonly ILogger _logger;
public MyBaseController(ILogger<MyBaseController> logger)
{
_logger = logger;
}
}
[Route("api/[controller]")]
public class MachineListsController : MyBaseController
{
public MachineListsController(ILogger<MyBaseController> logger) :base(logger)
{
}
[HttpGet]
public IActionResult Get() {
_logger.Log(Microsoft.Extensions.Logging.LogLevel.Trace,"aaa" );
return Ok();
}
}
I am wondering, would there be a way to do the logging in the base class? Like where you call _logger.Log in the MachineListsController class, could that be moved to base?
As far as I know, we could only add logs before the MachineListsController's action executed or after the MachineListsController's action executed.
If this match your requirement, you could try to use action filter.
You could add iactionfilter interface to the basecontroller and overried the OnActionExecuted and OnActionExecuting method.
More details, you could refer to below codes:
[Route("api/[controller]")]
[ApiController]
public class MyBaseController : ControllerBase, IActionFilter
{
protected readonly ILogger _logger;
public MyBaseController(ILogger<MyBaseController> logger)
{
_logger = logger;
}
public void OnActionExecuted(ActionExecutedContext context)
{
_logger.Log(Microsoft.Extensions.Logging.LogLevel.Trace, "aaa");
int i = 0;
}
public void OnActionExecuting(ActionExecutingContext context)
{
_logger.Log(Microsoft.Extensions.Logging.LogLevel.Trace, "bbb");
int i = 0;
}
}
Result:

How do I resolve a WebAPI dependency in Autofac that requires a parameter from the route?

I am struggling with wiring dependencies through autofac in my WebApi 2 project. I have a following interface and class that i'd like to inject in my GET and POST controller actions,
public interface IRepository
{
IContext Context
{
get;
}
void SomeOperation();
}
public MyRepository : IRepository
{
IContext _context;
public MyRepository(IContext context)
{
_context = context;
}
public Context
{
get
{
return _context;
}
}
public void SomeOperation
{
// Perform some operation using _context;
}
}
I 'd like IRepository to be injected in controller like this,
public class MyController : ApiController
{
private readonly IRepository _repo;
public ApplicationsController(IRepository repo)
{
_repo = repo;
}
// GET: api/v1/Contexts({contextId})
public IHttpActionResult Get(string contextId)
{
_repo.SomeOperation();
}
}
IContext object to be injected in MyRepository has to be fetched from a factory, something like this
public class ContextFactory
{
Hashtable contextMap;
IContext Get(string contextId)
{
if contextMap.Contains(contextId)
return contextMap[contextId].Value;
else
{
IContextConfiguration configuration = ContextConfigurationFactory.Get(contextId);
IContext context = new ConcreteContext(configuration);
contextMap.Add[contextId, context];
return context;
}
}
}
I am not sure how to wire all the classes and convert logic in factory classes by injecting relationships through Autofac so that context id passed in url is passed to ContextConfigurationFactory.Get and instantiate ConcreteContext object when not found in hash and eventually Autofac injecting right context object in MyRepository before passing it on to Get action in the controller.
Let's simplify this a bit. What you're trying to do is:
Get the context ID from a route parameter.
Use that route parameter in the factory to create a context.
The rest seems pretty much peripheral - the repository, the controller, all that. The crux of the question is that you need to get a route parameter into your factory.
Given that, let's put together some simplified code:
public class ContextFactory
{
public IContext Get(string contextId)
{
return new Context(contextId);
}
}
public interface IContext
{
string Id { get; }
}
public class Context : IContext
{
public Context(string id)
{
this.Id = id;
}
public string Id { get; private set; }
}
That's basically what you have:
An IContext interface that things need.
A ContextFactory that is basically responsible for building these things.
A Context concrete implementation of IContext that is built by the factory.
I would probably do something like this:
var builder = new ContainerBuilder();
builder.RegisterType<ContextFactory>();
builder.Register(ctx =>
{
var routeData = HttpContext.Current.Request.RequestContext.RouteData;
var id = routeData.Values["contextId"] as string;
var factory = ctx.Resolve<ContextFactory>();
return factory.Get(id);
}).As<IContext>()
.InstancePerLifetimeScope();
Now when you resolve IContext it will use your factory, get the current context ID from route data, and pass it through the factory.
I will leave the following for you to look into:
What happens if the route parameter isn't there? (Autofac won't let you return null.)
What happens if the route parameter has invalid data?
The route parameter is pretty hackable, is this a security risk?
...and so on.

Unable to inject DBContext into my Web API 2 Controller with Unity

I've been at it for days, but I can't get Unity to inject anything with RegisterType<> into my Controller. I'm using Web Api 2, in Visual Studio 2015, with Unity 4. Whenever I try to inject IUnitOfWork or IRFContext, I get "message": "An error occurred when trying to create a controller of type 'ClPlayersController'. Make sure that the controller has a parameterless public constructor.".
I'm using the Unity.AspNet.WebApi to bootstrapp into WebApi. Below is my UnityWebApiActivator
[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(mycompany.project.api.UnityWebApiActivator), "Start")]
[assembly: WebActivatorEx.ApplicationShutdownMethod(typeof(mycompany.project.api.UnityWebApiActivator), "Shutdown")]
namespace mycompany.project.api
{
public static class UnityWebApiActivator
{
public static void Start()
{
var resolver = new UnityDependencyResolver(UnityConfig.GetConfiguredContainer());
GlobalConfiguration.Configuration.DependencyResolver = resolver;
}
public static void Shutdown()
{
var container = UnityConfig.GetConfiguredContainer();
container.Dispose();
}
}
}
I'm using a Start.cs due to Owin.
[assembly: OwinStartup(typeof(mycompany.project.api.Startup))]
namespace mycompany.project.api
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
ConfigureOAuth(app);
config.DependencyResolver = new UnityDependencyResolver(UnityConfig.GetConfiguredContainer());
WebApiConfig.Register(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
}
public void ConfigureOAuth(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new SimpleAuthorizationServerProvider(),
RefreshTokenProvider = new SimpleRefreshTokenProvider()
};
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
}
}
My WebApiConfig.cs is below:
namespace mycompany.project.api
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
log4net.Config.XmlConfigurator.Configure();
config.MapHttpAttributeRoutes();
config.EnableSystemDiagnosticsTracing();
config.Services.Add(typeof(IExceptionLogger),
new SimpleExceptionLogger(new LogManagerAdapter()));
config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());
}
}
}
My UnityConfig.cs is below
namespace mycompany.project.api
{
public class UnityConfig
{
#region Unity Container
private static Lazy<IUnityContainer> container = new Lazy<IUnityContainer>(() =>
{
var container = new UnityContainer();
RegisterTypes(container);
return container;
});
public static IUnityContainer GetConfiguredContainer()
{
return container.Value;
}
#endregion
public static void RegisterTypes(IUnityContainer container)
{
var config = new MapperConfiguration(cfg =>
{
//AutoMapper bindings
});
container.RegisterInstance<IMapper>(config.CreateMapper());
container.RegisterType<IRFContext, RFContext>(new PerThreadLifetimeManager());
container.RegisterType<IUnitOfWork, UnitOfWork>();
XmlConfigurator.Configure();
var logManager = new LogManagerAdapter();
container.RegisterInstance<ILogManager>(logManager);
}
}
}
All that I have in my Global.asax is below:
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Error()
{
var exception = Server.GetLastError();
if (exception != null)
{
var log = new LogManagerAdapter().GetLog(typeof(WebApiApplication));
log.Error("Unhandled exception.", exception);
}
}
}
If my Controller is like this, it works fine:
public class ClPlayersController : ApiController
{
private readonly IMapper mapper;
public ClPlayersController(IMapper _mapper, IUnityContainer container)
{
mapper = _mapper;
}
But placing IUnitOfWork, like below, or the IRFContext, I get the error:
private readonly IMapper mapper;
private readonly IUnitOfWork unitOfWork;
public ClPlayersController(IMapper _mapper, IUnityContainer container, IUnitOfWork _unitOfWork)
{
mapper = _mapper;
unitOfWork = _unitOfWork;
}
I can't find, for the life of me, what I'm doing wrong. If I loop through the container.Registrations on the constructor, I find the mappings, but they refuse to get injected. Any hints?
EDIT
Below is the code for UnitOfWork and RFContext
namespace mycompany.project.data.configuracao
{
public class UnitOfWork : IUnitOfWork
{
private readonly IRFContext _rfContext;
private bool _disposed = false;
public UnitOfWork(IRFContext rfContext)
{
_rfContext = rfContext;
}
public void Commit()
{
if (_disposed)
{
throw new ObjectDisposedException(this.GetType().FullName);
}
_rfContext.SaveChanges();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing && _rfContext != null)
{
_rfContext.Dispose();
}
_disposed = true;
}
}
}
and
namespace mycompany.project.data.configuracao
{
public interface IUnitOfWork : IDisposable
{
void Commit();
}
}
and RFContext is a basic POCO generated DBContext
namespace mycompany.project.data.configuracao
{
using System.Linq;
public class RFContext : System.Data.Entity.DbContext, IRFContext
{
public System.Data.Entity.DbSet<ClGrupoEconomico> ClGrupoEconomicoes { get; set; }
//all my DbSets
public System.Data.Entity.DbSet<SpTipoLog> SpTipoLogs { get; set; }
static RFContext()
{
System.Data.Entity.Database.SetInitializer<RFContext>(null);
}
public RFContext()
: base("Name=RFContext")
{
}
public RFContext(string connectionString)
: base(connectionString)
{
}
public RFContext(string connectionString, System.Data.Entity.Infrastructure.DbCompiledModel model)
: base(connectionString, model)
{
}
public RFContext(System.Data.Common.DbConnection existingConnection, bool contextOwnsConnection)
: base(existingConnection, contextOwnsConnection)
{
}
public RFContext(System.Data.Common.DbConnection existingConnection, System.Data.Entity.Infrastructure.DbCompiledModel model, bool contextOwnsConnection)
: base(existingConnection, model, contextOwnsConnection)
{
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
}
protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new ClGrupoEconomicoConfiguration());
//all my Configuration classes
modelBuilder.Configurations.Add(new SpTipoLogConfiguration());
}
public static System.Data.Entity.DbModelBuilder CreateModel(System.Data.Entity.DbModelBuilder modelBuilder, string schema)
{
modelBuilder.Configurations.Add(new ClGrupoEconomicoConfiguration(schema));
//all my configuration classes
modelBuilder.Configurations.Add(new SpTipoLogConfiguration(schema));
return modelBuilder;
}
}
}
Unfortunately the exception you are seeing can occur for several reasons. One of them is when Unity cannot resolve one or more of your injections.
An error occurred when trying to create a controller of type
'FooController'. Make sure that the controller has a parameterless
public constructor.
So, based on the information in your question your setup is apparently correct, since IMapper can be injected. Therefore I guess that UnitOfWork and RFContext have dependencies that Unity cannot resolve. Maybe a repository?
UPDATE:
The problem here is that your RFContext has several constructors.
https://msdn.microsoft.com/en-us/library/cc440940.aspx#cnstrctinj_multiple
When a target class contains more than one constructor with the same
number of parameters, you must apply the InjectionConstructor
attribute to the constructor that the Unity container will use to
indicate which constructor the container should use. As with automatic
constructor injection, you can specify the constructor parameters as a
concrete type, or you can specify an interface or base class for which
the Unity container contains a registered mapping.
In this case Unity doesn't know how to resolve your RFContext, and will try to use the constructor with the most parameters. You can solve it by using
container.RegisterType<IRFContext, RFContext>(new InjectionConstructor());

How can I achieve the following using IOC?

I want to use IOC with my service and I want to instead inject a class not an interface in the constructor as below in the services layer but I do not want to create a new object from the calling layer like var service = new InvoiceService(new ChangeInvoiceDueDateCommand()) instead I want to create something like this from my controller in MVC where the IInvoiceService is injected into the controller constructor but the problem I see is that
public InvoiceController(IInvoiceService invoiceService, IMapper mapper)
{
_invoiceService = invoiceService;
_mapper = mapper;
}
and then called like this
public ActionResult ChangeInvoiceDueDate(InvoiceChangeDueDateViewModel invoiceChangeDueDateViewModel )
{
var request = _mapper.Map<InvoiceChangeDueDateViewModel, ChangeInvoiceDuedateRequest>(invoiceChangeDueDateViewModel);
InvoiceChangeDueDateResponse response = _invoiceService.ChangeDueDate(request);
return View();
}
Service Layer
public class InvoiceService : IInvoiceService
{
private readonly ChangeInvoiceDueDateCommand _changeInvoiceDueDateCommand;
public InvoiceService(ChangeInvoiceDueDateCommand changeInvoiceDueDateCommand)
{
_changeInvoiceDueDateCommand = changeInvoiceDueDateCommand;
}
public InvoiceChangeDueDateResponse ChangeDueDate(ChangeInvoiceDuedateRequest invoiceChangeDueDateRequest)
{
_changeInvoiceDueDateCommand.Execute(invoiceChangeDueDateRequest);
return new InvoiceChangeDueDateResponse {Status = new Status()};
}
}
Command
public class ChangeInvoiceDueDateCommand : ICommand<ChangeInvoiceDuedateRequest>
{
private readonly IRepository<Invoice> _invoiceRepository;
readonly InvoiceDueDateChangeValidator _validator;
public ChangeInvoiceDueDateCommand(IRepository<Invoice> invoiceRepository)
{
_invoiceRepository = invoiceRepository;
_validator = new InvoiceDueDateChangeValidator();
}
public void Execute(ChangeInvoiceDuedateRequest request)
{
if (_validator.IsDuedateValid(request.NewDuedate))
{
Invoice invoice = _invoiceRepository.GetById(request.Id);
invoice.ChangedDueDate(request.NewDuedate);
_invoiceRepository.SaveOrUpdate(invoice);
}
else
{
throw new InvalidDueDateException();
}
}
}
ICommand
public interface ICommand<T> where T : IRequest
{
void Execute(T request);
}
IRequest
public interface IRequest
{
int Id { get; set; }
}
I worked it out. It was just a Windsor syntax issue. It ended up being as simple as registering the Command using the container.Register(Component.For<ChangeInvoiceDueDateCommand>());