Populate initial view model data using caliburn micro and autofac - c#

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.

Related

Passing parameters to the ViewModel using a custom INavigationService Implementation?

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.

Adding Autofac to WPF MVVM application

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.)

How to link Autofac to UnitTesting

Within my Web API I have linked Autofac as IoC container, and I do it like this:
Domain level
public class Autofac
{
protected ContainerBuilder Builder { get; set; }
public Autofac()
{
this.Builder = new ContainerBuilder();
}
public virtual IContainer Register()
{
// Register dependencies
SetUpRegistration(this.Builder);
// Build registration.
var container = this.Builder.Build();
// End
return container;
}
private static void SetUpRegistration(ContainerBuilder builder)
{
// === DATALAYER === //
// MyRepository
builder.RegisterType<MyRepository>()
.As<IMyRepository>()
.InstancePerLifetimeScope();
// === DOMAIN === //
// MyManager
builder.RegisterType<MyManager>()
.As<IMyManager>()
.InstancePerLifetimeScope();
}
}
Web API
public class Autofac : Domain.IoC.Autofac
{
public IContainer Register(HttpConfiguration config)
{
// Register your Web API controllers.
base.Builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
// OPTIONAL: Register the Autofac filter provider.
base.Builder.RegisterWebApiFilterProvider(GlobalConfiguration.Configuration);
// Complete registration and get container instance.
var container = base.Register();
// Set the dependency resolver to be Autofac.
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
// Done.
return container;
}
}
As you see it inherits from the base class from Domain and sets up Web API specific config.
Usage
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
new IoC.Autofac().Register(GlobalConfiguration.Configuration);
}
Which is at global.asax, as you know.
The question
This works fine for Web API, but I haven't got a clue what I need to do to register all this within a UnitTest project context.
The idea is that I would create a similar implementation to the Autofac class at Web API level, but than with mocks (completely ignoring the base class from Domain).
Any pointers?
Personally I never see the need (and I struggle to comprehend how viable or helpful it would be) to setup my IoC container directly within a unit test.
As a unit test is used to test a logical piece of code that can be quickly built, easily ran and doesn't require much (I'd advocate no) tear-down. It should not require all of your application to be be setup for the test to run.
Remember that your unit test is simply testing the flow of data through the system i.e that your DomainManager is actually going to call a IRepository when you expect that it should. Then you would have separate test classes for all your repositories to determine that they would correctly add to the database etc.
I'm not sure how you use the DBContext class but as an example of a wrapper this is what it would sort of look like.
interface IDBSetWrapper
{
object Add(object entity);
}
interface IDBContextWrapper
{
...
IDBSet Set(Type entityType);
...
}
class DBContextWrapper : IDBContextWrapper
{
private readonly DBContext context;
public DBContextWrapper()
{
context = new DBContext();
}
...
public IDBSet Set(Type entityType)
{
var dbSet = context.Set(entityType);
return new DBSetWrapper(dbSet);
}
...
}
It's not much but I hope that it demonstrates what I mean about a thin wrapper. Basically the wrapper is the DBContext and will contain an instance of it within the class, the actual DBContext will be called when you request the wrapper to do anything.
I have shown what would happen when returning another object (in this case a DBSet), this will also be wrapped in a separate object with an interface. This is so that you can mock the returns from this class easily.
You can add this new wrapper into your IoC a little better now as it provides an interface.
One thing to note is that you won't be able to and probably wouldn't wish to test the wrapper class, there would be very little point as I see it. But previously I've seen colleagues do an integration test on these sort of classes.

MVVM - Using entities in ViewModels

I am really just starting out with MVVM, IoC and Dependency Injection and I've hit a stumbling block that I don't know how to solve but I do understand why it's happening.
I am using Castle Windsor for DI and IoC functionality and MVVM Light as my MVVM framework in a WPF application. Using this tutorial I have managed to get Castle Windsor to create a MainPageViewModel that has a IGroupRepository injected into the constructor. I have registered a mock implementation of this in Castle Windsor.
The following is the only other code in the MainPageViewModel class besides the constructor.
public ObservableCollection<GroupViewModel> Groups
{
get
{
var groupVms = new ObservableCollection<GroupViewModel>();
IEnumerable<Group> groups = _repository.GetAllGroups();
foreach (Group g in groups)
{
var vm = new GroupViewModel(g);
groupVms.Add(vm);
}
return groupVms;
}
}
The intention is to create a view model for each of the groups in the repository. However, doing this causes Castle Windsor to give the following exception:
Can't create component 'Planner.ViewModel.GroupViewModel' as it has dependencies
to be satisfied.
'Planner.ViewModel.GroupViewModel' is waiting for the following dependencies:
Service 'Planner.Models.Group' which was not registered.
I understand this exception - Castle Windsor is responsible for constructing my view models but it has no way of handling my entity.
I have done plenty of Googling but have found very few answers or suggestions to this problem which makes me think that what I am doing is wrong. This Stack Overflow question has two answers which suggest that having an entity on the view model is ok but I'm beginning to wonder if that's true. Other questions, such as this one suggest that the entity should be nowhere near the view model.
What is the correct way to resolve this problem?
Update: As requested, this is the stacktrace for the exception:
at Castle.MicroKernel.Handlers.DefaultHandler.AssertNotWaitingForDependency()
at Castle.MicroKernel.Handlers.DefaultHandler.ResolveCore(CreationContext context, Boolean requiresDecommission, Boolean instanceRequired, Burden& burden)
at Castle.MicroKernel.Handlers.DefaultHandler.Resolve(CreationContext context, Boolean instanceRequired)
at Castle.MicroKernel.Handlers.AbstractHandler.Resolve(CreationContext context)
at Castle.MicroKernel.DefaultKernel.ResolveComponent(IHandler handler, Type service, IDictionary additionalArguments, IReleasePolicy policy)
at Castle.MicroKernel.DefaultKernel.Castle.MicroKernel.IKernelInternal.Resolve(Type service, IDictionary arguments, IReleasePolicy policy)
at Castle.MicroKernel.DefaultKernel.Resolve(Type service, IDictionary arguments)
at Castle.Windsor.WindsorContainer.Resolve(Type service)
at Planner.ViewModel.ViewModelResolver.Resolve(String viewModelName) in D:\Planner\Planner\Planner\ViewModel\ViewModelResolver.cs:line 27
at Planner.ViewModel.ViewModelLocator.get_Item(String viewModelName) in D:\Planner\Planner\Planner\ViewModel\ViewModelLocator.cs:line 21
I thought that this was the correct behaviour because of the following code which (I believe) intercepts any calls to the constructor of a view model and injects them as appropriate.
public class ViewModelResolver : IViewModelResolver
{
private IWindsorContainer _container;
public object Resolve(string viewModelName)
{
if (_container == null)
{
_container = new WindsorContainer();
_container.Install(new WindsorViewsInstaller());
_container.Install(new WindsorRepositoriesInstaller());
}
var viewModelType =
GetType()
.Assembly
.GetTypes()
.Where(t => t.Name.Equals(viewModelName))
.FirstOrDefault();
return _container.Resolve(viewModelType);
}
}
Update 2: I think this answers Ritch's query:
public class ViewModelLocator : DynamicObject
{
public IViewModelResolver Resolver { get; set; }
public ViewModelLocator()
{
Resolver = new ViewModelResolver();
}
public object this[string viewModelName]
{
get
{
return Resolver.Resolve(viewModelName);
}
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = this[binder.Name];
return true;
}
}
I think I understand this a little more now. The problem isn't actually with the original code I posted. The problem is occurring actually setting up Windsor isn't it? I'm still not sure how I solve that problem though.
The property Groups on MainPageViewModel creates a bunch of VMs that aren't in the container, but your stacktrace is the Locator looking to bind/create an instance of the GroupViewModel (Why? I cannot tell at this point with the code you've posted.).
You have a disconnect in how the GroupViewModels are created and what the container is doing. You either need to let Windsor create these through a factory interface Factory Interface Documentation or remove them from the container entirely and manage them yourself. Based on gut feeling, I would lean towards the factory interface.
Ritch's answer led me in the right direction but I wanted to post a separate answer so that I can show the code that I ended up with in the hope that it will be useful to the next person trying to do this.
As well as Ritch's answer, this Stack Overflow question and this blog post by one of the main contributers to Castle Windsor, Krzysztof Koźmic, all helped me to solve this problem in what I believe is the correct way.
As Ritch said, I shouldn't have been calling the constructor for my view model directly - that is what the container is there for. So the way to do it is to create a class which will help Windsor to create your view model. This is known as a typed factory facility. The good thing about these classes is that you don't actually need to implement them - they are just interfaces. This is the code for the class that will eventually be used to create the view model:
public interface IGroupViewModelFactory
{
GroupViewModel Create(Group group);
}
This is injected into the constructor for the view model that will create the GroupViewModel which in my case was the MainWindowViewModel class. Here is the code for that class:
public class MainWindowViewModel : ViewModelBase
{
private readonly IGroupRepository _repository;
private readonly IGroupViewModelFactory _groupViewModelFactory;
public MainWindowViewModel(IGroupRepository repository, IGroupViewModelFactory groupViewModelFactory)
{
_repository = repository;
_groupViewModelFactory = groupViewModelFactory;
}
public ObservableCollection<GroupViewModel> Groups
{
get
{
var groupVms = new ObservableCollection<GroupViewModel>();
IEnumerable<Group> groups = _repository.GetAllGroups();
foreach (Group g in groups)
{
var vm = _groupViewModelFactory.Create(g);
groupVms.Add(vm);
}
return groupVms;
}
}
}
The final step is to register the factory class with Windsor and that is done by this piece of code:
_container.AddFacility<TypedFactoryFacility>();
_container.Register(
Component.For<Group>(),
Component.For<IGroupViewModelFactory>()
.AsFactory());
It's worth noting that the question I linked to earlier did not have the Component.For<Group>(), line in the above piece of code. Without that I was getting an exception from Windsor but unfortunately I didn't keep the details and I can no longer replicate it so there was probably something else amiss in my application.
Through the magic of Castle Windsor, you can now create view models from entities in a repository!

Dependency Injection with Cyclic Dependency

Let me have two very basic objects like:
public class View
{
public View(Controller controller)
{
// Use the model exposed by the controller here
}
}
public class Controller
{
private readonly IView view;
public Controller()
{
this.view = new View(this);
}
public Controller(View v)
{
this.view = v;
}
}
Later I decide to inject View object into the Controller via DI but there I have a cyclic dependency (i.e. I can't use var ctrl = new Controller(new View(ctrl));). How would you go about injectin the dependency in this case?
The most common solution is to use a dependency property to solve circular dependencies. i.e. create a new property in one of the classes and let the IoC container assign it.
If you are using Unity you should add [Dependency] to that property.
A sidenote: A View should not have a dependency to a controller. It should not be aware of it at all.
Update in reply to comment
You can't. That's the problem with circular dependencies. The only other solution is to use composition. That is to break out the common functionality into a separate class and include it in both the controller and the view.
I actually found a nice solution using Ninject.
public class Controller
{
private readonly View view;
public Controller(ViewModule viewModule)
{
using (IKernel kernel = new StandardKernel(viewModule))
{
this.view = kernel.Get<View>(new ConstructorArgument("controller", this);
}
}
}
Where the ViewModule is a pre-configured Ninject module to resolve the particular view dependency (GUI, CLI, etc.) Minor problem here is that, I'm now dependent on the particular DI framework.
You can't do that at all with constructor-injection
If you change the constructor of the controller to
public Controller(IView view)
in which order would you create the two objects?
View needs the controller Instance and the controller needs the view.
However, you can make the IView Property of the controller public, and set the Property after creation (Some DI-Containers can do this for you automatically when you set the correct attribute).

Categories

Resources