Inject settings in Setup class - c#

On Application_Start of a MVC project, using Autofac, I have the following:
public class MvcApplication : HttpApplication {
protected void Application_Start() {
RouteSetup.Run();
} // Application_Start
}
RouteSetup is as follows:
public class RouteSetup {
public static void Run() {
ISettings settings = new Settings();
RouteTable.Routes.Localization(x => {
x.AcceptedCultures = settings.AcceptedLanguages;
x.DefaultCulture = settings.DefaultLanguage;
});
CultureSensitiveHttpModule.GetCultureFromHttpContextDelegate = context => { return new CultureResolver().GetCulture(context); };
} // Run
}
ISettings is a class I inject in various parts of my application.
How should I request this class in RouteSetup?

You can change the Run method to accept a ILifetimeScope (IContainer inherits from ILifetimeScope) or you can use the DependencyResolver provided by ASP.net MVC, in the second case the ASP.net DependencyResolver has to be configured using DependencyResolver.SetResolver(...)
public class RouteSetup {
public static void Run(ILifetimeScope scope) {
ISettings settings = scope.Resolve<ISettings>();
// or
ISettings settings = DependencyResolver.Current.GetService<ISettings>();
RouteTable.Routes.Localization(x => {
x.AcceptedCultures = settings.AcceptedLanguages;
x.DefaultCulture = settings.DefaultLanguage;
});
CultureSensitiveHttpModule.GetCultureFromHttpContextDelegate = context => {
return new CultureResolver().GetCulture(context);
};
} // Run
}
By the way, I recommend you trying to always inject dependency using constructor parameter.

Related

Object reference exeption - Dependency Injection with Entity Framework WPF

I tried to implement connection to database using Entity Framework and Dependency Injection. I want to create Host in App.xaml.cs.
public partial class App : Application
{
public static IHost? AppHost { get; private set; }
public App()
{
AppHost = Host.CreateDefaultBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddSingleton<LoginWindow>();
services.AddSingleton<LoginViewModel>();
services.AddDbContext<KnitterNotebookContext>(
options =>
{
string appSettingsPath = Path.Combine(ProjectDirectory.ProjectDirectoryFullPath, "appsettings.json");
string appSettingsString = File.ReadAllText(appSettingsPath);
AppSettings AppSettings = JsonConvert.DeserializeObject<AppSettings>(appSettingsString)!;
options.UseSqlServer(AppSettings.KnitterNotebookConnectionString);
});
})
.Build();
}
protected override async void OnStartup(StartupEventArgs e)
{
await AppHost!.StartAsync();
var startupWindow = AppHost.Services.GetRequiredService<LoginWindow>();
startupWindow.Show();
base.OnStartup(e);
}
protected override async void OnExit(ExitEventArgs e)
{
await AppHost!.StopAsync();
base.OnExit(e);
}
I want to pass DbContext as parameter to ViewModel, but when I do, it throws exception.
public class LoginViewModel : BaseViewModel
{
public LoginViewModel(KnitterNotebookContext knitterNotebookContext)
//public LoginViewModel()
{
KnitterNotebookContext = knitterNotebookContext;
ShowRegistrationWindowCommand = new RelayCommand(ShowRegisterWindow);
LogInCommandAsync = new AsyncRelayCommand(LogIn);
}
private KnitterNotebookContext KnitterNotebookContext { get; set; }
}
There is no problem if I use parameterless constructor of LoginViewModel and create new instances of KnitterNotebookContext with new(), but I want to pass it as a parameter. How to solve it?
I happened to encounter the same problem at one time - i'm guessing that you are setting DataContext for MainWindow in xaml, like this:
<Window.DataContext>
<local:LoginViewModel/>
<Window.DataContext>
Doing it this way, when your LoginWindow is created, tries to create new instance of its DataContext (your LoginViewModel), but it does not know how to resolve constructor - since you injected it with service, it's not parameterless anymore.
You have to provide a way, to enforce DataContext to be instantiated by ServiceProvider (a container for all injected services).
You could achieve this through use of a helper class, which is usable in xaml (inherits from MarkupExtension), like this one below.
public class ServiceDispatcher : MarkupExtension
{
public static Func<Type, object> Resolver { get; set; }
public Type Type { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
return Resolver?.Invoke(Type);
}
}
And assigning it's service dispatching Resolver delegate in your App.xaml.cs
public App()
{
AppHost = Host.CreateDefaultBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddSingleton<LoginWindow>();
services.AddSingleton<LoginViewModel>();
services.AddDbContext<KnitterNotebookContext>(
options =>
{
string appSettingsPath = Path.Combine(ProjectDirectory.ProjectDirectoryFullPath, "appsettings.json");
string appSettingsString = File.ReadAllText(appSettingsPath);
AppSettings AppSettings = JsonConvert.DeserializeObject<AppSettings>(appSettingsString)!;
options.UseSqlServer(AppSettings.KnitterNotebookConnectionString);
});
})
.Build();
ConfigureServiceProvider(AppHost.Services);
}
private static void ConfigureServiceProvider(IServiceProvider appHost)
{
ServiceDispatcher.Resolver = (type) =>
{
return appHost.GetRequiredService(type);
};
}
Now you can use your ServiceDispatcher to dynamically provide DataContext instance for your Views using syntax below.
<Window.DataContext>
<base:ServiceDispatcher Type="{x:Type local:LoginViewModel}"/>
</Window.DataContext>

Property Injection does not work when using RegisterApiControllers extension method

Consider my simple controller class where I want to use a logger (ILogger is coming from Castle in this case).
[RoutePrefix("api/orders")]
public class SignalController : ApiController
{
public ILogger Logger { get; set; } = new NullLogger();
// POST api/orders/update
[HttpPost, Route("update")]
public virtual void UpdateHandler(ChangeStateDto update)
{
this.Logger.Info($"Received ChangeStateDto with status {update.Status}");
}
}
Then I've got a self-hosted webhost, that looks like this.
public class WebHost
{
private readonly string url;
private IDisposable disposable;
private readonly ILifetimeScope scope;
public WebHost(string url, ILifetimeScope scope)
{
this.url = url;
this.scope = scope;
}
public ILogger Logger { get; set; } = new NullLogger();
// ...
public void Start()
{
try
{
this.Logger.Info($"Starting web host at {url}");
this.disposable = WebApp.Start(this.url, app =>
{
var config = new HttpConfiguration
{
DependencyResolver = new AutofacWebApiDependencyResolver(scope)
};
config.MapHttpAttributeRoutes();
app.UseWebApi(config);
});
// ...
}
catch (Exception e)
{
// ...
this.Logger.Error(e.Message, e);
}
}
}
And this is the calling type
public class SomeCallerClass
{
public SomeCallerClass()
{
var webHostLogger = new SomeILoggerImplementation(this, "WebHost");
var builder = new ContainerBuilder();
builder.RegisterApiControllers()
.WithProperty("Logger", webHostLogger);
var container = builder.Build();
this.webHost = new WebHost("http://localhost:9000", container)
{
Logger = webHostLogger
};
}
}
Now the issue I am having is that property injection is not working for my SignalController type. It always holds a reference to the NullLogger instance. I just can't figure out why.
I solved it by using .RegisterType<> instead of .RegisterApiControllers
So the following works as expected. However I still don't understand why my first approach wasn't working.
var builder = new ContainerBuilder();
builder.RegisterType<SignalController>()
.WithProperty("Logger", webHostLogger)
.InstancePerRequest();
var container = builder.Build();
It probably wasn't working because it wasn't registering any controllers.
In your code you have:
builder
.RegisterApiControllers()
.WithProperty("Logger", webHostLogger);
However, you have to tell the registration extension which assemblies your controllers are in. You can see that in the examples shown in the docs.
Try:
builder
.RegisterApiControllers(Assembly.GetExecutingAssembly())
.WithProperty("Logger", webHostLogger);

ASP.Net Core register Controller at runtime

I am asking myself if it is possible to load a DLL with Controllers in it at runtime and use it.
The only solution I've found is to add an assembly via ApplicationPart on the StartUp:
var builder = services.AddMvc();
builder.AddApplicationPart(
AssemblyLoadContext.Default.LoadFromAssemblyPath(
#"PATH\ExternalControllers.dll"
));
Do anyone know if it is possible to register Controller at any time, because the issue with that solution is, that you have to restart the WebService when you want to add another DLL with Controllers in it. It would be nice when you just can add them at any time and register them at any time in the application.
This is possible now on .net core 2.0+
Please see code ActionDescriptorCollectionProvider.cs:
public ActionDescriptorCollectionProvider(
IEnumerable<IActionDescriptorProvider> actionDescriptorProviders,
IEnumerable<IActionDescriptorChangeProvider> actionDescriptorChangeProviders)
{
_actionDescriptorProviders = actionDescriptorProviders
.OrderBy(p => p.Order)
.ToArray();
_actionDescriptorChangeProviders = actionDescriptorChangeProviders.ToArray();
ChangeToken.OnChange(
GetCompositeChangeToken,
UpdateCollection);
}
Step 1:Implement IActionDescriptorChangeProvider class:
public class MyActionDescriptorChangeProvider : IActionDescriptorChangeProvider
{
public static MyActionDescriptorChangeProvider Instance { get; } = new MyActionDescriptorChangeProvider();
public CancellationTokenSource TokenSource { get; private set; }
public bool HasChanged { get; set; }
public IChangeToken GetChangeToken()
{
TokenSource = new CancellationTokenSource();
return new CancellationChangeToken(TokenSource.Token);
}
}
Step 2:AddSingleton on Startup.ConfigureServices():
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton<IActionDescriptorChangeProvider>(MyActionDescriptorChangeProvider.Instance);
services.AddSingleton(MyActionDescriptorChangeProvider.Instance);
}
Step 3: Register controller at runtime:
public class TestController : Controller
{
private readonly ApplicationPartManager _partManager;
private readonly IHostingEnvironment _hostingEnvironment;
public TestController(
ApplicationPartManager partManager,
IHostingEnvironment env)
{
_partManager = partManager;
_hostingEnvironment = env;
}
public IActionResult RegisterControllerAtRuntime()
{
string assemblyPath = #"PATH\ExternalControllers.dll";
var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath);
if (assembly != null)
{
_partManager.ApplicationParts.Add(new AssemblyPart(assembly));
// Notify change
MyActionDescriptorChangeProvider.Instance.HasChanged = true;
MyActionDescriptorChangeProvider.Instance.TokenSource.Cancel();
return Content("1");
}
return Content("0");
}
}
This is possible now.
Please see the updated documentation about how to add dynamic controllers:
public class GenericControllerFeatureProvider : IApplicationFeatureProvider<ControllerFeature>
{
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)
{
// This is designed to run after the default ControllerTypeProvider,
// so the list of 'real' controllers has already been populated.
foreach (var entityType in EntityTypes.Types)
{
var typeName = entityType.Name + "Controller";
if (!feature.Controllers.Any(t => t.Name == typeName))
{
// There's no 'real' controller for this entity, so add the generic version.
var controllerType = typeof(GenericController<>)
.MakeGenericType(entityType.AsType()).GetTypeInfo();
feature.Controllers.Add(controllerType);
}
}
}
}

Passing configuration to Ninject

I need to pass an IConfiguration to my DbClient class and my repositories depend on this DbClient. I couldn't get this to work.
My DbClient:
public class DbClient
{
public DbClient(IConfiguration config)
{
// Perform some initialization
}
}
My repository depends on the DbClient:
public class MyRepository : IMyRepository
{
private DbClient _client;
public MyRepository(DbClient client)
{
_client = client;
}
}
My Bindings class
public class NinjectBindings : NinjectModule
{
public override void Load()
{
Bind<DbClient>().To<DbClient>(); // ?? Not sure about this
Bind<IMyRepository>().To<MyRepository>();
}
}
And the Main in my console app:
static void Main()
{
var config = new Configuration();
config.AddJsonFile("settings.json");
var kernel = new StandardKernel();
kernel.Load(Assembly.GetExecutingAssembly());
var myRepository = kernel.Get<IMyRepository>();
}
What am I missing? What do I need to do to pass IConfiguration into the DbClient and make sure the repository initializes properly?
You need to bind IConfiguration.
You can do it in Load method:
Bind<IConfiguration>().ToMethod(ctx =>
{
var config = new Configuration();
config.AddJsonFile("settings.json");
return config;
});
Or in Main method:
kernel.Bind<IConfiguration>().ToMethod(...);
Binding type depends on your need. Maybe you should bind config as constant without context dependency.
Also line Bind<DbClient>().To<DbClient>(); isn't necessary because DbClient will be automatically bind to self.

use dependency injection in Signalr 2.0 self-host?

With SignalR 2.0 in a self-hosted application, from these instructions you have something like this:
class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR(new HubConfiguration { Resolver = ... });
}
}
class Program
{
static void Main(string[] args)
{
using (WebApp.Start("http://localhost:8080")) // constructs Startup instance internally
{
Console.WriteLine("Server running on {0}", url);
Console.ReadLine();
}
}
}
You will notice that the Startup class instance is created with some behind-the-scenes magic. I can't figure out how to fill in dependencies on it. Is there some way to override the construction of the Startup class so that I can inject dependencies into it?
Instead of replacing the IAppActivator, you can simply register Startup's constructor arguments with Katana's ServiceProvider.
The default IAppActivator will resolve any services matching the Startup constructor's argument types for you. The only downside is you can't use WebApp.Start, since that doesn't expose the ServiceProvider:
public class MyService : IMyService
{
private readonly IMyOtherService _myOtherService;
// Services will be recursively resolved by Katana's ServiceProvider
public MyService(IMyOtherService myOtherService)
{
_myOtherService = myOtherService;
}
// Implementation
}
public class Startup
{
private readonly IMyService _myService;
// Startup must have exactly one constructor.
public Startup(IMyService myService)
{
_myService = myService
}
public void Configuration(IAppBuilder app)
{
app.MapSignalR(new HubConfiguration { Resolver = ... });
}
}
using System;
using Microsoft.Owin.Hosting;
using Microsoft.Owin.Hosting.Services;
using Microsoft.Owin.Hosting.Starter;
public class Program
{
static void Main(string[] args)
{
var url = "http://localhost:8080";
var services = (ServiceProvider)ServicesFactory.Create();
var options = new StartOptions(url);
services.Add<IMyOtherService, MyOtherService>();
services.Add<IMyService, MyService>();
var starter = services.GetService<IHostingStarter>();
using (starter.Start(options)) // constructs Startup instance internally
{
Console.WriteLine("Server running on {0}", url);
Console.ReadLine();
}
}
}
I copied the default implementation of WebApp.Start into Program.Main, but instead of calling IHostingStarter.Start immediately, I add custom services first: http://katanaproject.codeplex.com/SourceControl/changeset/view/c726b87e90c05677a256ca1821bac481f402d6bd#src/Microsoft.Owin.Hosting/WebApp.cs
There are a bunch of other overloads for ServiceProvider.Add if you need them: http://msdn.microsoft.com/en-us/library/microsoft.owin.hosting.services.serviceprovider(v=vs.111).aspx
This should be much simpler than replacing Katana's IAppActivator using StartOptions.Settings like I suggest in my previous answer.
I am leaving my previous answer up, however, as it does explain in more detail how the Startup class is constructed and how to replace default service implementations using the Settings dictionary.
Checkout the dependency injection information here: http://www.asp.net/signalr/overview/signalr-20/extensibility/dependency-injection
Should have everything you need to know :)
Hope this helps!
class Startup
{
private readonly IDependencyResolver _resolver;
public Startup(IDependencyResolver resolver)
{
_resolver = resolver;
}
public void Configuration(IAppBuilder app)
{
app.MapSignalR(new HubConfiguration { Resolver = _resolver; });
}
}
class Program
{
static void Main(string[] args)
{
Startup startup = new Statrtup(new MyResolver());
using (WebApp.Start("http://localhost:8080", startup.Configuration))
{
Console.WriteLine("Server running on {0}", url);
Console.ReadLine();
}
}
}

Categories