Ninject Dependency Injection works for only for one controller - asp.net

I had originally set up DI with ninject for an asp.net web api 2 service with a single controller and everything was working correctly. Upon adding a second controller, ninject does not work for the new one. I'm getting the following error:
"An error occurred when trying to create a controller of type 'VstsController'. Make sure that the controller has a parameterless public constructor."
First controller (for which ninject works):
public class RepositoryController : ApiController
{
private GitHubClient _client;
public RepositoryController(IGitHubClientAuthenticated gitHubClientAuthenticated)
{
_client = gitHubClientAuthenticated.Client;
_client.Credentials = gitHubClientAuthenticated.Credentials;
}
Second controller:
public class VstsController : ApiController
{
private VssConnection _connection;
public VstsController(IVssConnectionAuthenticated vssConnectionAuthenticated)
{
_connection = vssConnectionAuthenticated.VssConnection;
}
Ninject config file:
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<IVssConnectionAuthenticated>().To<VssConnectionAuthenticated>();
kernel.Bind<IGitHubClientAuthenticated>().To<GitHubClientAuthenticated>();
kernel.Bind<IAuthenticationHelper>().To<AuthenticationHelper>();
}
Do I need to tweak anything if I want to keep adding controllers? Couldn't find any documentation on this. Thanks in advance
EDIT: Including ninject set up code as well as VssAuthenticated + IvssAuthenticated:
namespace Dashboard.WebAPI.Models
{
public interface IVssConnectionAuthenticated
{
VssConnection VssConnection { get; }
Uri Uri { get; }
}
}
namespace Dashboard.WebAPI.Models
{
public class VssConnectionAuthenticated: IVssConnectionAuthenticated
{
public VssConnection VssConnection { get; private set; }
public Uri Uri { get; private set; }
VssConnectionAuthenticated()
{
Uri = new Uri("uri");
string vstsSecretUri = "vstssecreturi";
GetKeyVaultSecret keyVaultSecretGetter = new GetKeyVaultSecret(new AuthenticationHelper(), vstsSecretUri);
string keyVaultSecret = keyVaultSecretGetter.KeyVaultSecret;
VssBasicCredential vssBasicCredential = new VssBasicCredential(string.Empty, keyVaultSecret);
VssConnection = new VssConnection(Uri, vssBasicCredential);
}
Full Ninject Config File:
namespace Dashboard.WebAPI.App_Start
{
using System;
using System.Web;
using Microsoft.Web.Infrastructure.DynamicModuleHelper;
using Ninject;
using Ninject.Web.Common;
using System.Web.Http;
using Dashboard.WebAPI.Models;
public static class NinjectWebCommon
{
private static readonly Bootstrapper bootstrapper = new Bootstrapper();
/// <summary>
/// Starts the application
/// </summary>
public static void Start()
{
DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule));
DynamicModuleUtility.RegisterModule(typeof(NinjectHttpModule));
bootstrapper.Initialize(CreateKernel);
}
/// <summary>
/// Stops the application.
/// </summary>
public static void Stop()
{
bootstrapper.ShutDown();
}
/// <summary>
/// Creates the kernel that will manage the application.
/// </summary>
/// <returns>The created kernel.</returns>
private static IKernel CreateKernel()
{
var kernel = new StandardKernel();
try
{
kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
RegisterServices(kernel);
GlobalConfiguration.Configuration.DependencyResolver = new NinjectDependencyResolver(kernel);
return kernel;
}
catch
{
kernel.Dispose();
throw;
}
}
/// <summary>
/// Load modules and register services
/// </summary>
/// <param name="kernel">The kernel.</param>
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<IVssConnectionAuthenticated>().To<VssConnectionAuthenticated>();
kernel.Bind<IGitHubClientAuthenticated>().To<GitHubClientAuthenticated>();
kernel.Bind<IAuthenticationHelper>().To<AuthenticationHelper>();
}
}
}
Registering Ninject as Dependency Resolver:
namespace Dashboard.WebAPI.App_Start
{
public class NinjectDependencyScope : IDependencyScope
{
IResolutionRoot resolver;
public NinjectDependencyScope(IResolutionRoot resolver)
{
this.resolver = resolver;
}
public object GetService(Type serviceType)
{
if (resolver == null)
throw new ObjectDisposedException("this", "This scope has been disposed");
return resolver.TryGet(serviceType);
}
public System.Collections.Generic.IEnumerable<object> GetServices(Type serviceType)
{
if (resolver == null)
throw new ObjectDisposedException("this", "This scope has been disposed");
return resolver.GetAll(serviceType);
}
public void Dispose()
{
IDisposable disposable = resolver as IDisposable;
if (disposable != null)
disposable.Dispose();
resolver = null;
}
}
public class NinjectDependencyResolver: NinjectDependencyScope, IDependencyResolver
{
IKernel kernel;
public NinjectDependencyResolver(IKernel kernel) : base(kernel)
{
this.kernel = kernel;
}
public IDependencyScope BeginScope()
{
return new NinjectDependencyScope(kernel.BeginBlock());
}
}
}

In case anyone else runs into this problem- The problem was in VssConnectionAuthenticated: The constructor needs to be public.

Related

How to get an email provider into a logger using DI in ASP.NET Core?

Sorry this is a bit new to me so I don't quite 'get it'.
I already have a logging provider
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging(loggingBuilder =>
{
var loggingSection = Configuration.GetSection("Logging");
loggingBuilder.AddFile(loggingSection);
loggingBuilder.AddConsole();
loggingBuilder.AddDebug();
I am using the package NReco.Logging.File to define AddFile etc.
I want to make it so that exceptions are emailed to me too. So I followed https://learn.microsoft.com/en-us/dotnet/core/extensions/custom-logging-provider to create a custom logger.
public sealed class EmailLoggerConfiguration
{
public int EventId { get; set; }
public string EmailToSendTo { get; set; }
public IEmailSender EmailSender { get; set; }
}
internal class EmailLoggingProvider : ILoggerProvider
{
private readonly IDisposable? _onChangeToken;
private EmailLoggerConfiguration _currentConfig;
private readonly ConcurrentDictionary<string, EmailLogger> _loggers =
new(StringComparer.OrdinalIgnoreCase);
private readonly IEmailSender emailSender;
public EmailLoggingProvider(
IOptionsMonitor<EmailLoggerConfiguration> config)
{
_currentConfig = config.CurrentValue;
_onChangeToken = config.OnChange(updatedConfig => _currentConfig = updatedConfig);
}
public ILogger CreateLogger(string categoryName) =>
_loggers.GetOrAdd(categoryName, name => new EmailLogger(name, GetCurrentConfig ));
private EmailLoggerConfiguration GetCurrentConfig() => _currentConfig;
public void Dispose()
{
_loggers.Clear();
_onChangeToken?.Dispose();
}
}
internal class EmailLogger : ILogger
{
private readonly string categoryName;
private Func<EmailLoggerConfiguration> getCurrentConfig;
IEmailSender emailSender;
public EmailLogger(string categoryName, Func<EmailLoggerConfiguration> getCurrentConfig)
{
this.getCurrentConfig = getCurrentConfig;
this.categoryName = categoryName;
}
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => default!;
public bool IsEnabled(LogLevel logLevel) => !String.IsNullOrEmpty(getCurrentConfig().EmailToSendTo);
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
var emailTo = getCurrentConfig().EmailToSendTo;
//var emailServer = getCurrentConfig().EmailSender;
if (!String.IsNullOrEmpty(emailTo) && exception != null)
{
emailSender.SendEmailAsync(emailTo, "Admin exception", exception.ToString());
}
}
}
public static class EmailLoggingExtensions
{
public static ILoggingBuilder AddEmailLogger(
this ILoggingBuilder builder)
{
builder.AddConfiguration();
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, EmailLoggingProvider>());
LoggerProviderOptions.RegisterProviderOptions<EmailLoggerConfiguration, EmailLoggingProvider>(builder.Services);
return builder;
}
public static ILoggingBuilder AddEmailLogger(
this ILoggingBuilder builder,
Action<EmailLoggerConfiguration> configure)
{
builder.AddEmailLogger();
builder.Services.Configure(configure);
return builder;
}
}
You can see that EmailLogger.Log requires emailSender which should be an IEmailSender but I cannot figure out how to get it there using DI.
I realise that you can chain dependencies in DI but ???? I don't see how in this context.
I tried this
loggingBuilder.AddEmailLogger(c =>
{
c.EmailToSendTo = Configuration["Logging:Email:EmailToSendTo"];
c.EmailSender = new AuthMessageSender(????, Configuration);
});
but that didn't help and wouldn't even be right anyway.
In fact, by default, EmailSender is the implementation method of IEmailSender, which is used to call the SendEmailAsync() method. You don't need to go and set c.EmailSender = xxx.
You can consider the following dependency injection approach:
public interface IEmailSender
{
Task SendEmailAsync(string email, string subject, string message);
}
public class EmailSender : IEmailSender
{
//...
private readonly ILogger<EmailSender> logger;
public EmailSender(ILogger<EmailSender> logger) {
//...
this.logger = logger;
}
public Task SendEmailAsync(string email, string subject, string message) {
//...
}
}
At this point, IEmailSender will exist as a custom interface instead of inheriting from Microsoft.AspNetCore.Identity.UI.Services.
And you need to register it as a service:
services.AddTransient<IEmailSender, EmailSender>();
Helpful links:
Add ILogger to send email service
Should I use IEmailSender?
Using IEmailSender from Configure() in my Startup.cs file
Hope this will help you better understand IEmailSender and dependency injection.

How to inject my AddDbContext<ContainerContext> into my DAL project (Core 3.1)

I'm using MVC 5, Core 3.1
I have 'AddDbContext' added to my service in Startup.cs.
I then have a Class library core 3.1 project which is my ADO Dal layer.
This is added as a service as well in The ConfigureServices of Startup.cs.
I want to inject the Connection String into the DAL application.
I have:
public partial class ContainerContext : DbContext
{
public ContainerContext()
{
}
public ContainerContext(DbContextOptions<ContainerContext> options) : base(options)
{
}
}
In Startup.cs
public void ConfigureServices(IServiceCollection services)
{
var connection = Configuration.GetConnectionString("DefaultConnection");
services.AddDbContext<ContainerContext>(options => options.UseSqlServer(connection));
services.AddDAL();
}
In the Dal project:
public static class ServiceCollectionExtensions
{
// Add parameters if required, e.g. for configuration
public static IServiceCollection AddDAL(this IServiceCollection services)
{
// Register all services as required
services.AddScoped<ILeaseBll, LeaseDal>();
return services;
}
}
The Dal class.
public class LeaseDal : ILeaseBll
{
private string conString;
public LeaseDal(???????)
{
// Some validation for the Context maybe (isNull etc?) throw new ArgumentNullException("conString");
//this.connectionString = conString;
}
How would / should it be done?
Thanks
There is a philosophy change with Dot-Net-Core and Dot-Net-Framework....
public class LeaseDal : ILeaseBll
{
private string conString;
This is not best practice in dot-net-CORE.
You do NOT inject your "connection string" in your concrete DataAccessLayer object.
You inject the db-context.
(and the db-context already has been wired to the Ioc...with its correct connection string)
Something like this:
public interface IDepartmentQueryDomainData()
{
Task<int> GetCountAsync(CancellationToken token);
}
..
public class DepartmentQueryEntityFrameworkDomainDataLayer : IDepartmentQueryDomainData
{
public const string ErrorMessageILoggerFactoryIsNull = "ILoggerFactory is null";
public const string ErrorMessageMyCoolDbContextIsNull =
"MyCoolDbContext is null";
private readonly ILogger<DepartmentQueryEntityFrameworkDomainDataLayer> logger;
private readonly MyCoolDbContext entityDbContext;
public DepartmentQueryEntityFrameworkDomainDataLayer(
ILoggerFactory loggerFactory,
MyCoolDbContext context
{
if (null == loggerFactory)
{
throw new ArgumentNullException(ErrorMessageILoggerFactoryIsNull, (Exception)null);
}
this.logger = loggerFactory.CreateLogger<DepartmentQueryEntityFrameworkDomainDataLayer>();
this.entityDbContext = context ?? throw new ArgumentNullException(
ErrorMessageMyCoolDbContextIsNull,
(Exception)null);
}
public async Task<int> GetCountAsync(CancellationToken token)
{
int returnValue = await this.entityDbContext.Departments.AsNoTracking().CountAsync(token);
this.logger.Log(
new LogEntry(
LoggingEventTypeEnum.Trace,
string.Format(
LogMessages.Count,
returnValue)));
return returnValue;
}
}
You can also "see" this here:
https://learn.microsoft.com/en-us/ef/core/dbcontext-configuration/
public class MyController
{
private readonly ApplicationDbContext _context;
public MyController(ApplicationDbContext context)
{
_context = context;
}
}
I would never inject the dbContext into a "controller"...(I agree with you that the Dal should be a separate layer)...
but besides that "miscue" on the microsoft example, you do see that you inject the dbContext.
Also see:
https://hovermind.com/aspnet-core/using-dbcontext-with-dependency-injection.html

Refit inheritance misses methods

We use Refit for our API declarations and want to use Interface inheritance.
A sample is shown here: https://github.com/reactiveui/refit#interface-inheritance
public interface IPlatformRestClient
{
HttpClient Client { get; }
[Get("/version")]
Task<string> GetVersion();
}
public interface ITestRestClient : IPlatformRestClient
{
[Get("/test")]
Task<string> Test();
}
I may be blind and do something wrong, but the construct results in the following error message
'AutoGeneratedITestRestClient' does not implement interface member 'IPlatformRestClient.GetVersion()' Platform.RestClient.UnitTests
And if I open the stubs:
/// <inheritdoc />
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[global::System.Diagnostics.DebuggerNonUserCode]
[Preserve]
[global::System.Reflection.Obfuscation(Exclude=true)]
partial class AutoGeneratedITestRestClient : ITestRestClient
{
/// <inheritdoc />
public HttpClient Client { get; protected set; }
readonly IRequestBuilder requestBuilder;
/// <inheritdoc />
public AutoGeneratedITestRestClient(HttpClient client, IRequestBuilder requestBuilder)
{
Client = client;
this.requestBuilder = requestBuilder;
}
/// <inheritdoc />
Task<string> ITestRestClient.Test()
{
var arguments = new object[] { };
var func = requestBuilder.BuildRestResultFuncForMethod("Test", new Type[] { });
return (Task<string>)func(Client, arguments);
}
}
So is this feature broken or do I have any error?

Windsor ComponentNotFoundException IHttpControllerActivator

I have tried to implement Castle Windsor into my ASP.NET Web API project by following the guide by Mark Seemann. But when I try to run the code it gives me a ComponentNotFoundException exception. I mean that I should have registered the dependency right.
I really hope that someone has a solution to my problem. I have tried to search for a solution but with out any luck.
Global.asax
public class WebApiApplication : System.Web.HttpApplication
{
private readonly IWindsorContainer _container;
public WebApiApplication()
{
_container = new WindsorContainer().Install(new ControllerInstaller());
}
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), new WindsorCompositionRoot(_container));
}
public override void Dispose()
{
_container.Dispose();
base.Dispose();
}
}
IHttpControllerActivator implementation
public class WindsorCompositionRoot : IHttpControllerActivator
{
private readonly IWindsorContainer _container;
public WindsorCompositionRoot(IWindsorContainer container)
{
_container = container;
}
public IHttpController Create(
HttpRequestMessage request,
HttpControllerDescriptor controllerDescriptor,
Type controllerType)
{
var controller =
(IHttpController)_container.Resolve(controllerType);
request.RegisterForDispose(
new Release(
() => _container.Release(controller)));
return controller;
}
private class Release : IDisposable
{
private readonly Action release;
public Release(Action release)
{
this.release = release;
}
public void Dispose()
{
release();
}
}
}
ControllerInstaller
public class ControllerInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(Component.For<IGymnastDataAccess>().ImplementedBy<GymnastDataAccess>());
}
}
Controller
public class GymnastController : ApiController
{
private readonly IGymnastDataAccess _gymnastDataAccess;
public GymnastController(IGymnastDataAccess gymnastDataAccess)
{
_gymnastDataAccess = gymnastDataAccess;
}
public IEnumerable<string> Get()
{
_gymnastDataAccess.Load();
return new string[] { "value1", "value2" };
}
}
Castle Windsor do not support automatic resolve of concrete classes out of the box, so you should register your controller class in container:
container.Register(Component.For<GymnastController>());
or implement ILazyComponentLoader like here to get automatic resolve of concrete classes.

WCF + Unity nested web.config problem

I'm trying to setup the following:
/WebApplication
web.config
tokenlogin.aspx
/Services
web.config
AccessTokenService.svc
I put my WCF Service + configuration in the /WebApplication/Services folder.
This still workes as expected.
Now my AccessTokenService, which resides in an other assembly, expects an interface in its constructor called IAccessTokenRepository (see all code samples below).
Because normally WCF only allows for parameter-less constructors, I extended WCF using a custom IInstanceProvider, IServiceBehavior and BehaviorExtensionElement so Unity could resolve this for me.
As I did with the WCF configuration, I also put the Unity configuration inside the web.config file which resides in the Services folder. This way I don't pollute my web.config in my web app root. But that doesn't seem to work. I'm unable to read the unity configuration section from the web.config in the services folder. The GetSection part of the follwing code returns null:
public class UnityBehaviorExtensionElement : BehaviorExtensionElement
{
public override Type BehaviorType
{
get { return typeof(UnityServiceBehaviour); }
}
protected override object CreateBehavior()
{
UnityContainer unityContainer = new UnityContainer();
UnityConfigurationSection section = LoadUnitySection() as UnityConfigurationSection;
section.Containers.Default.Configure(unityContainer);
return new UnityServiceBehaviour(unityContainer);
}
private object LoadUnitySection()
{
if (System.Web.Hosting.HostingEnvironment.IsHosted)
return WebConfigurationManager.GetSection("unity");
return ConfigurationManager.GetSection("unity");
}
}
Now if I move the unity configuration to the web.config int the root of the WebApplication everything works fine. And I have no idea why.
The rest of the code:
[ServiceContract(Namespace = "http://services.xxx.com/AccessTokenService/1.0")]
public interface IAccessTokenService
{
[OperationContract]
Guid RequestAccessToken(AccessTokenRequest accessTokenRequest);
}
public class AccessTokenService : IAccessTokenService
{
private readonly IAccessTokenRepository accessTokenRepository;
public AccessTokenService(IAccessTokenRepository accessTokenRepository)
{
if (accessTokenRepository == null)
throw new ArgumentNullException("accessTokenRepository");
this.accessTokenRepository = accessTokenRepository;
}
public Guid RequestAccessToken(AccessTokenRequest accessTokenRequest)
{
return accessTokenRepository.Store(accessTokenRequest);
}
}
public class UnityInstanceProvider : IInstanceProvider
{
private readonly Type serviceType;
private readonly UnityContainer unityContainer;
public UnityInstanceProvider(Type serviceType, UnityContainer unityContainer)
{
this.serviceType = serviceType;
this.unityContainer = unityContainer;
}
#region IInstanceProvider Members
public object GetInstance(InstanceContext instanceContext, System.ServiceModel.Channels.Message message)
{
return unityContainer.Resolve(serviceType);
}
public object GetInstance(InstanceContext instanceContext)
{
return GetInstance(instanceContext, null);
}
public void ReleaseInstance(InstanceContext instanceContext, object instance)
{
if (instance == null)
return;
if (instance is IDisposable)
((IDisposable)instance).Dispose();
}
#endregion
}
public class UnityServiceBehaviour : IServiceBehavior
{
private readonly UnityContainer unityContainer;
public UnityServiceBehaviour(UnityContainer unityContainer)
{
this.unityContainer = unityContainer;
}
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection endpoints, BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (var channel in serviceHostBase.ChannelDispatchers.OfType())
{
if (channel == null)
continue;
foreach (var endPointDispatcher in channel.Endpoints)
{
endPointDispatcher.DispatchRuntime.InstanceProvider =
new UnityInstanceProvider(serviceDescription.ServiceType, unityContainer);
}
}
}
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}
}

Resources