I can't seem to find an solution to this problem. I've seen several questions about this, but none really give me a solution. I am totally new to Autofac and haven't really done much WPF + MVVM, but know the basics.
I have a WPF application (using ModernUI for WPF) which I'm trying to add Autofac to, and I am having a hard time figuring out how to resolve my services within all the views, since they have no access to my container. I have a main view, which is my entry point, where I set up my container:
public partial class MainWindow : ModernWindow
{
IContainer AppContainer;
public MainWindow()
{
SetUpContainer();
this.DataContext = new MainWindowViewModel();
InitializeComponent();
Application.Current.MainWindow = this;
}
private void SetUpContainer()
{
var builder = new ContainerBuilder();
BuildupContainer(builder);
var container = builder.Build();
AppContainer = container;
}
private void BuildupContainer(ContainerBuilder builder)
{
builder.RegisterType<Logger>().As<ILogger>();
...
}
}
The problem I'm having is figuring out how I can resolve my logger and other services within my other views, where I inject all my dependencies through the ViewModel constructor, like so:
public partial class ItemsView : UserControl
{
private ItemsViewModel _vm;
public ItemsView()
{
InitializeComponent();
IFileHashHelper fileHashHelper = new MD5FileHashHelper();
ILibraryLoader libraryLoader = new LibraryLoader(fileHashHelper);
ILogger logger = new Logger();
_vm = new ItemsViewModel(libraryLoader, logger);
this.DataContext = _vm;
}
}
Some views have a ridiculous amount of injected parameters, and this is where I want Autofac to come in and help me clean things up.
I was thinking of passing the container to the ViewModel and storing it as a property on my ViewModelBase class, but I've read that this would be an anti-pattern, and even then I don't know if that would automatically resolve my objects within the other ViewModels.
I managed to put together a simple Console Application using Autofac
class Program
{
static void Main(string[] args)
{
var builder = new ContainerBuilder();
builder.RegisterType<Cleaner>().As<ICleaner>();
builder.RegisterType<Repository>().AsImplementedInterfaces().InstancePerLifetimeScope();
var container = builder.Build();
using (var scope = container.BeginLifetimeScope())
{
ICleaner cleaner = container.Resolve<ICleaner>();
cleaner.Update(stream);
}
}
}
but that was simple since it has a single entry point.
I'd like some ideas on how to add Autofac to my WPF app. I'm sure that I'm doing something wrong. Your help is appreciated.
Expanding on my comment above:
I use Autofac with all my WPF MVVM applications, I believe it to be one of the better DI frameworks - this is my opinion, but I think it is valid.
Also for me PRISM should be avoided 99% of the time, it's a 'solution looking for a problem' and since most people don't build dynamically composable runtime solutions in WPF it is not needed, i'm sure people would\will disagree.
Like any architectural patterns there is a setup\configuration phase to the application life-cycle, put simply in your case before the first View (window) is shown there will be a whole of setup done for Dependency Injection, Logging, Exception Handling, Dispatcher thread management, Themes etc.
I have several examples of using Autofac with WPF\MVVM, a couple are listed below, I would say look at the Simple.Wpf.Exceptions example:
https://github.com/oriches/Simple.Wpf.Exceptions
https://github.com/oriches/Simple.Wpf.DataGrid
https://github.com/oriches/Simple.MahApps.Template
You can use a similar technique as your console application:
class Program
{
[STAThread]
static void Main(string[] args)
{
var builder = new ContainerBuilder();
builder.RegisterType<Cleaner>().As<ICleaner>();
builder.RegisterType<Repository>().AsImplementedInterfaces().InstancePerLifetimeScope();
// Add the MainWindowclass and later resolve
build.RegisterType<MainWindow>().AsSelf();
var container = builder.Build();
using (var scope = container.BeginLifetimeScope())
{
var main = scope.Resolve<MainWindow>();
main.ShowDialog();
}
}
}
Be sure to mark Main with [STAThread]. Then in the project's properties, under the Application tab, set the Startup object to the Program class.
However, I am not certain of the implications of not running App.Run() and of running MainWindow.ShowDialog() instead.
To do the same using App.Run(), do the following:
1) delete StartupUri="MainWindow.xaml" from App.xaml
2) Add the following to App.xaml.cs
protected override void OnStartup(StartupEventArgs e)
{
var builder = new ContainerBuilder();
builder.RegisterType<Cleaner>().As<ICleaner>();
builder.RegisterType<Repository>().AsImplementedInterfaces().InstancePerLifetimeScope();
// Add the MainWindowclass and later resolve
build.RegisterType<MainWindow>().AsSelf();
var container = builder.Build();
using (var scope = container.BeginLifetimeScope())
{
var window = scope.Resolve<MainWindow>();
window.Show();
}
}
WPF doesn't have a natural composition root or easy DI integration. Prism is a pretty common set of libraries specifically intended to bridge that for you.
(That's not Autofac specific - it's general guidance for adding DI to WPF apps.)
Related
I've built a small app using MVVM Light, and I've reached a point in which I need to pass parameters between a few different ViewModels in my app. I've explored several different options, but I'm not a huge fan of them really. The most promising I've encountered so far is simply passing messages between the ViewModels, but this is somewhat limiting as the application has the potential to have multiple of the same View open at once, and I need to isolate the parameters to a singular instance of a View/ViewModel.
I'm not currently using the built in INavigationService provided by MVVM Light, but I've made one incredibly similar (and if I can solve the parameter injection, I'll likely switch).
Here is a trimmed down version of my navigation service:
public class NavigationService : INavigationService
{
/* this implementation will not allow us to have the same window open
more than once. However, for this application, that should be sufficient.
*/
public NavigationService()
{
_openPages = new Dictionary<string, Window>();
}
private readonly Dictionary<string, Window> _openPages;
public void ClosePage(string pageKey)
{
if (!_openPages.ContainsKey(pageKey)) return;
var window = _openPages[pageKey];
window.Close();
_openPages.Remove(pageKey);
}
public IEnumerable<string> OpenPages => _openPages.Keys;
public void NavigateTo(string pageKey)
{
if (!AllPages.ContainsKey(pageKey))
throw new InvalidPageException(pageKey);
// Don't re-open a window that's already open
if (_openPages.ContainsKey(pageKey))
{
_openPages[pageKey].Activate();
return;
}
var page = (Window) Activator.CreateInstance(AllPages[pageKey]);
page.Show();
page.Closed += OnWindowClosedHandler;
_openPages.Add(pageKey, page);
}
// Probably a better way to remove this.
private void OnWindowClosedHandler(object sender, EventArgs args)
{
foreach (var item in _openPages.Where(kvp => kvp.Value == sender).ToList())
{
_openPages.Remove(item.Key);
}
}
// Reflection might work for this.
// Might also consider making this more dynamic so it isn't hard-coded into my service
private readonly Dictionary<string, Type> AllPages = new Dictionary<string, Type>
{
["AddPatientView"] = typeof(AddPatientView),
["CheckInView"] = typeof(CheckInView),
["MainView"] = typeof(MainWindow),
["PatientLookupView"] = typeof(PatientLookupView),
["PatientDetailsView"] = typeof(PatientDetailsView)
};
}
Most of my ViewModels use dependency injection to wire-up other injected services, like so:
public class CheckInViewModel : ViewModelBase
{
public CheckInViewModel(ILicenseValidationService licenseValidationService,
IPatientFetchService patientFetchService,
IPatientCheckInService patientCheckInService)
{
if (IsInDesignMode)
{
Title = "Find Member (Design)";
}
else
{
Title = "Find Member";
CanFetch = true;
FindMemberCommand = new RelayCommand(async () => await FindMemberHandler(), () => CanFetch);
CheckInPatientCommand = new RelayCommand<Window>(async (window) => await CheckInPatientHandler(window),
(window) => Patient?.PatientId != null);
_licenseValidationService = licenseValidationService;
_patientFetchService = patientFetchService;
_patientCheckInService = patientCheckInService;
}
}
}
I would like to implement some method of injecting other parameters alongside my injected services. Has anything like this been done in a relatively straightforward way?
The way dependency injection works in almost all cases is when you resolve or getinstance a type that then will use the constructor with the most parameters in providing you with an object.
If you register a concrete object against an interface ( or just a type ) then later resolve/getinstance a class which uses one of those things in it's ctor then DI provides that instance you registered.
With MVVMLight you have SimpleIoc and SimpleIoc.Default is equivalent to that static service you're thinking about.
There is a catch with simpleioc. It's very simple.
With simpleioc once you getinstance a viewmodel of a given type then that is a singleton. You can force a different instance by passing a unique key but they're all cached. You can getinstance with parameters and maybe that replaces the current object. I'm not sure offhand. A more sophisticated DI container might be advisable.
Other than that.
Since you're using different windows this creates a bit of a complication in that you want to instantiate a window and that will have a datacontext you need to provide somehow with your parameters.
What you could use is viewmodel first.
You get inavigationservice out DI or resources or a static.
You have a DoWindow(Object vm) method.
When you want to navigate you presumably know the parameters for the vm. New up your viewmodel with parameters. New up a window you use for all views. Set it's content to your viewmodel. That is templated out into what you have as windows now. Except you make them usercontrols. Use Datatype="vmtype" to associate view as template with viewmodel. Bind the title of your window to Content.Title and of course add a Title property to a base viewmodel.
ALternatively with a single window app you can have a contentcontrol fills the area yor views will be shown in. Bind the content of that to a currentviewmodel property and you can use viewmodel first navigation within that window.
In a C# WindowForms application I start an OWIN WebApp that creates a singleton instance of my other class Erp:
public partial class Engine : Form
{
const string url = "http://*:8080"; //49396
private IDisposable webApp;
public Engine()
{
InitializeComponent();
StartServer();
}
private void StartServer()
{
webApp = WebApp.Start<Startup>(url);
Debug.WriteLine("Server started at " + url);
}
private void btnDoSomething(object sender, System.EventArgs e)
{
// needs to call a method in erp
}
}
class Startup
{
public void Configuration(IAppBuilder app)
{
Trace.Listeners.Remove("HostingTraceListener");
app.UseCors(CorsOptions.AllowAll);
var builder = new ContainerBuilder();
var config = new HubConfiguration();
builder.RegisterHubs(Assembly.GetExecutingAssembly()).PropertiesAutowired();
var erp = new Erp();
builder.RegisterInstance<Erp>(erp).SingleInstance();
var container = builder.Build();
config.Resolver = new AutofacDependencyResolver(container);
app.UseAutofacMiddleware(container);
app.MapSignalR(config);
}
}
After the creation of the WebApp I want to retrieve in other part of my code (i.e. in the button's event handler above) the singleton erp instance created.
As far as I understand I need to use the resolve function:
var erp = container.Resolve<Erp>();
but it's not clear to me how to retrieve the container outside the Configuration function.
I wouldn't overthink it. Set a static variable somewhere and just hold onto it.
public static class ContainerProvider
{
public static IContainer Container { get; set; }
}
and in the block in Startup:
var container = builder.Build();
ContainerProvider.Container = container;
config.Resolver = new AutofacDependencyResolver(container);
Now you can get the container wherever you need it.
EDIT: I have just realised the accepted answer is by a co-owner of the Autofac project, which has left me confused as it seems to go against what is in the documentation. I am going to leave the answer for now in hope of clarification.
Just wanted to provide my own answer is because whilst the accepted answer will work; it is generally considered bad practice.
From the Best Practices and Recommendations section in Autofac's documentation:
Use Relationship Types, Not Service Locators
Giving components access to the container, storing it in a public static property, or making functions like Resolve() available on a global “IoC” class defeats the purpose of using dependency injection. Such designs have more in common with the Service Locator pattern.
If components have a dependency on the container (or on a lifetime scope), look at how they’re using the container to retrieve services, and add those services to the component’s (dependency injected) constructor arguments instead.
Use relationship types for components that need to instantiate other components or interact with the container in more advanced ways.
You haven't given a specific scenario of how you want to use it in your code, so I can't provide you with an exact solution, but is there any reason you need to resolve the instance yourself? Could you not just deliver the Erp instance via dependency injection?
If the answer is yes, the following code I adapted from the Windows Forms Integration Guide page in Autofac's documentation demonstrates how this would be done:
public partial class Form1 : Form {
private readonly Erp _erp;
public Form1(Erp erp) {
this._erp = erp;
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e) {
//do stuff with erp here
}
}
Then Autofac, assuming the registration has been setup correctly, should inject the instance into that class.
I hope this helps!
One of my dependencies (DbContext) is registered using the WebApiRequestLifestyle scope.
Now, my background job uses IoC and depends on the service that was registered above using the WebApiRequestLifestyle. I'm wondering how this works when Hangfire calls the method i registered for the background job. Will the DbContext be treated like a transistent object since the web api is not involved?
Any guidance would be great!
Here is my initialize code that occurs during start up:
public void Configuration(IAppBuilder app)
{
var httpConfig = new HttpConfiguration();
var container = SimpleInjectorWebApiInitializer.Initialize(httpConfig);
var config = (IConfigurationProvider)httpConfig.DependencyResolver
.GetService(typeof(IConfigurationProvider));
ConfigureJwt(app, config);
ConfigureWebApi(app, httpConfig, config);
ConfigureHangfire(app, container);
}
private void ConfigureHangfire(IAppBuilder app, Container container)
{
Hangfire.GlobalConfiguration.Configuration
.UseSqlServerStorage("Hangfire");
Hangfire.GlobalConfiguration.Configuration
.UseActivator(new SimpleInjectorJobActivator(container));
app.UseHangfireDashboard();
app.UseHangfireServer();
}
public static Container Initialize(HttpConfiguration config)
{
var container = new Container();
container.Options.DefaultScopedLifestyle = new WebApiRequestLifestyle();
InitializeContainer(container);
container.RegisterMvcControllers(Assembly.GetExecutingAssembly());
container.RegisterWebApiControllers(config);
container.RegisterMvcIntegratedFilterProvider();
container.Register<Mailer>(Lifestyle.Scoped);
container.Register<PortalContext>(Lifestyle.Scoped);
container.RegisterSingleton<TemplateProvider, TemplateProvider>();
container.Verify();
DependencyResolver.SetResolver(new SimpleInjectorDependencyResolver(container));
config.DependencyResolver = new SimpleInjectorWebApiDependencyResolver(container);
return container;
}
Here is my code that kicks off the background job:
public class MailNotificationHandler : IAsyncNotificationHandler<FeedbackCreated>
{
private readonly Mailer mailer;
public MailNotificationHandler(Mailer mailer)
{
this.mailer = mailer;
}
public Task Handle(FeedbackCreated notification)
{
BackgroundJob.Enqueue<Mailer>(x => x.SendFeedbackToSender(notification.FeedbackId));
BackgroundJob.Enqueue<Mailer>(x => x.SendFeedbackToManagement(notification.FeedbackId));
return Task.FromResult(0);
}
}
Finally here is the code that runs on the background thread:
public class Mailer
{
private readonly PortalContext dbContext;
private readonly TemplateProvider templateProvider;
public Mailer(PortalContext dbContext, TemplateProvider templateProvider)
{
this.dbContext = dbContext;
this.templateProvider = templateProvider;
}
public void SendFeedbackToSender(int feedbackId)
{
Feedback feedback = dbContext.Feedbacks.Find(feedbackId);
Send(TemplateType.FeedbackSender, new { Name = feedback.CreateUserId });
}
public void SendFeedbackToManagement(int feedbackId)
{
Feedback feedback = dbContext.Feedbacks.Find(feedbackId);
Send(TemplateType.FeedbackManagement, new { Name = feedback.CreateUserId });
}
public void Send(TemplateType templateType, object model)
{
MailMessage msg = templateProvider.Get(templateType, model).ToMailMessage();
using (var client = new SmtpClient())
{
client.Send(msg);
}
}
}
I'm wondering how this works when Hangfire calls the method i registered for the background job. Will the DbContext be treated like a transistent object since the web api is not involved?
As the design decisions describe, Simple Injector will never allow you to resolve an instance outside an active scope. So that DbContext will neither be resolved as transient or singleton; Simple Injector will throw an exception when there's no scope.
Every application type requires its own type of scoped lifestyle. Web API requires the AsyncScopedLifestyle (in previous versions WebApiRequestLifestyle), WCF an WcfOperationLifestyle and MVC the WebRequestLifestyle. For Windows Services you will typically use an AsyncScopedLifestyle.
If your Hangfire jobs run in a Windows Service, you will have to use either a ThreadScopedLifestyle or the AsyncScopedLifestyle. Those scopes require explicit starting.
When running the jobs on a background thread in a web (or Web API) application, there is no access to the required context and this means that Simple Injector will throw an exception if you try to do so.
You however are using the Hangfire.SimpleInjector integration library. This library implements a custom JobActivator implementation called SimpleInjectorJobActivator and this implementation will create start a Scope for you on the background thread. Hangfire will actually resolve your Mailer within the context of this execution context scope. So the Mailer constructor argument in your MailNotificationHandler is actually never used; Hangfire will resolve this type for you.
The WebApiRequestLifestyle and AsyncScopedLifestyle are interchangeable; the WebApiRequestLifestyle uses an execution context scope in the background and the SimpleInjectorWebApiDependencyResolver actually starts an execution context scope. So the funny thing is that your WebApiRequestLifestyle can be used for background operations as well (although it can be a bit confusing). So your solution works and works correctly.
When running in MVC, however, this will not work, and in that case you would have to create a Hybrid lifestyle, for instance:
var container = new Container();
container.Options.DefaultScopedLifestyle = Lifestyle.CreateHybrid(
new AsyncScopedLifestyle(),
new WebRequestLifestyle());
You can register your DbContext as follows:
container.Register<DbContext>(() => new DbContext(...), Lifestyle.Scoped);
Here's some feedback on your application's design, if you don't mind.
Prevent letting application code, such as your MailNotificationHandler, from taking a direct dependency on an external library such as Hangfire. This is a direct violation of the Dependency Inversion Principle and makes your application code very hard to test and maintain. Instead, let solely your Composition Root (the place where you wire your dependencies) take a dependency on Hangfire. In your case, the solution is really straightforward and I would even say pleasant, and it would look as follows:
public interface IMailer
{
void SendFeedbackToSender(int feedbackId);
void SendFeedbackToManagement(int feedbackId);
}
public class MailNotificationHandler : IAsyncNotificationHandler<FeedbackCreated>
{
private readonly IMailer mailer;
public MailNotificationHandler(IMailer mailer)
{
this.mailer = mailer;
}
public Task Handle(FeedbackCreated notification)
{
this.mailer.SendFeedbackToSender(notification.FeedbackId));
this.mailer.SendFeedbackToManagement(notification.FeedbackId));
return Task.FromResult(0);
}
}
Here we added a new IMailer abstraction and made the MailNotificationHandler dependent on this new abstraction; unaware of the existence of any background processing. Now close to the part where you configure your services, define an IMailer proxy that forwards the calls to Hangfire:
// Part of your composition root
private sealed class HangfireBackgroundMailer : IMailer
{
public void SendFeedbackToSender(int feedbackId) {
BackgroundJob.Enqueue<Mailer>(m => m.SendFeedbackToSender(feedbackId));
}
public void SendFeedbackToManagement(int feedbackId) {
BackgroundJob.Enqueue<Mailer>(m => m.SendFeedbackToManagement(feedbackId));
}
}
This requires the following registrations:
container.Register<IMailer, HangfireBackgroundMailer>(Lifestyle.Singleton);
container.Register<Mailer>(Lifestyle.Transient);
Here we map the new HangfireBackgroundMailer to the IMailer abstraction. This ensures that the BackgroundMailer is injected into your MailNotificationHandler, while the Mailer class is resolved by Hangfire when the background thread is started. The registration of the Mailer is optional, but advisable, since it has become a root object, and since it has dependencies, we want Simple Injector to be aware of this type to allow it to verify and diagnose this registration.
I hope you agree that from perspective of the MailNotificationHandler, the application is much cleaner now.
Our company is using Ninject for DI. I have to create a WPF App with MVVM and want to use Catel.
Because our services which have the DB DataContext are injected with Ninject, I don't know where to start.
I've started with a prepared skeleton project.
This is what App.xaml.cs contains:
public partial class App : Application
{
public IKernel Container;
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
ConfigureContainer();
ComposeObjects();
Current.MainWindow.Show();
}
private void ConfigureContainer()
{
var modules = new INinjectModule[]
{
new ServiceModule()
};
Container = new StandardKernel(modules);
}
private void ComposeObjects()
{
Current.MainWindow = Container.Get<MainWindow>();
Current.MainWindow.Title = "DI with Ninject";
}
}
The ServiceModule is inherited from NinjectModule.
With that code I can use this constructor of my MainWindow:
public MainWindow(IAuthenticationService authenticationService)
{
InitializeComponent();
ViewModel = new MainWindowViewModel(authenticationService);
DataContext = ViewModel;
}
The IAuthenticationService is injected via App.xaml.cs and Ninject. In my opinion this solution is hard to maintain, because if I need a new service, I have to add it to the constructor of my MainWindow.
Now I need the same thing to work with Catel, but I haven't found something in the documentation.
EDIT:
I've found on the documentation that I can register an external IoC container.
How do I create my own component (doc: Replacing the default components) which works with the Ninject's standard kernel?
Also is this a good approach of DI or are there better solutions?
Please see the recommended approach on how to replace the default IoC components:
https://catelproject.atlassian.net/wiki/display/CTL/Replacing+the+default+components
To create your own component, let the Ninject kernel implement the right interface (for example, IDependencyResolver or IServiceLocator) and all should be set.
I am in the process of learning Caliburn.Micro and Autofac at the same time. I am writing a simple app so I can teach myself how to properly use these two technologies.
Each time I think I am getting somewhere, I always get tripped up on how to initialize my data for my root viewmodel. This viewmodel needs to have a list of "category" viewmodels injected. This list of "category" viewmodels will be loaded during app initialization.
So, how do I register my root "Navigator" viewmodel with autofac and inform autofac that the viewmodel needs to have data injected when a new instance is created?
Without caliburn and autofac I would simply create my own startup method, load my data, and inject it into my viewmodel. With caliburn, I am attempting to override the configure method in the bootstrapper as this is where I should register my classes with the ioc container.
My root viewmodel:
public class NavigatorViewModel : Conductor<IScreen>.Collection.OneActive
{
public NavigatorViewModel(IEnumerable<CategoryViewModel> categories)
{
AddCategories(categories);
}
public void AddCategories(IEnumerable<CategoryViewModel> categories)
{
foreach (var category in categories)
{
if (Items.Contains(category))
continue;
Items.Add(category);
}
SetActiveItem();
}
private void SetActiveItem()
{
if (Items.Count < 1)
return;
ActiveItem = Items[0];
}
}
Here is how I am registering the viewModels with Autofac:
protected override void Configure()
{
var builder = new ContainerBuilder();
builder.RegisterType<CategoryViewModel>().AsSelf();
builder.RegisterType<NavigatorViewModel>().AsSelf();
...
container = builder.Build();
}
When registering my NavigatorViewModel how do I tell autofac that it needs to have the list of CategoryViewModels injected as well?
I am guessing that I will load my data within the caliburn bootstrapper, but I am just unsure how to hook it all up at this point.
This looks like a question about AutoFac rather than MVVM or Caliburn.Micro, anyway as far as i know AutoFac supports Auto-Wiring for sequences (IEnumberable) automatically so it should fill up the list all by it self when it tries to resolve your Navigator, but if you need to provide custom work for data loading you can do it like this:
builder.RegisterType<NavigatorViewModel>()
.AsSelf()
.WithParameter(
(p, c) => true,
(p, c) => new[]
{
// Load your CategoryViewModels here or any other data that you would like
});
you can check AutoFac documentation on the WithParameter method.