I develop an application using WPF with MVVM pattern and Prism. The views are added to the ModuleCatalog and the viewmodels are registered to a unity container. For that I'm using a Bootstrapper which is responsible creating the shell, configuring the unity container and the module catalog.
The question is now, how injecting my EntityContext to the several viewmodels.
First the Bootstrapper:
public class Bootstrapper : UnityBootstrapper
{
protected override DependencyObject CreateShell()
{
Shell shell = Container.Resolve();
shell.Show();
return shell;
}
protected override void ConfigureContainer()
{
base.ConfigureContainer();
Container.RegisterType<EntityContext >("Context");
Container.RegisterType<PersonViewModel>(new InjectionConstructor(
new ResolvedParameter<EntityContext >("Context")));
}
protected override IModuleCatalog GetModuleCatalog()
{
ModuleCatalog catalog = new ModuleCatalog();
catalog.AddModule(typeof(PersonModule));
return catalog;
}
The viewmodel looks like that (excerpt)
public class PersonViewModel : ViewModelBase, IDataErrorInfo
{
private Person _person;
private PersonRepository _repository;
readonly EntityContext _context;
public PersonViewModel(EntityContext context)
{
_context = context;
_person = new Person();
_repository = new PersonRepository(context);
}
The module:
public class PersonModule : IModule
{
private readonly IRegionManager regionManager;
public PersonModule(IRegionManager regionManager)
{
this.regionManager = regionManager;
}
public void Initialize()
{
regionManager.RegisterViewWithRegion("PersonData", typeof(PersonView));
}
}
The view code-behind:
public partial class PersonView : UserControl
{
private PersonViewModel _vm;
public PersonView()
{
InitializeComponent();
}
[Dependency]
public PersonViewModel VM
{
get
{
return this.DataContext as PersonViewModel;
}
set
{
_vm = value;
this.DataContext = _vm;
}
}
}
I'm not sure if my approach is working in principle but for the sake of saving changes to the database I need my context in knowledge of changes made to it. Right now it is obviously not working bacause an ModuleInitializeException occurs. Stacktrace:
An exception occurred while initializing module 'PersonModule'.
- The exception message was: An exception has occurred while trying to add a view to region 'PersonData'.
- The most likely causing exception was was: 'System.InvalidOperationException: The type EntityContext has multiple constructors of length 1. Unable to disambiguate.
bei Microsoft.Practices.ObjectBuilder2.ConstructorSelectorPolicyBase1.FindLongestConstructor(Type typeToConstruct)
bei Microsoft.Practices.ObjectBuilder2.ConstructorSelectorPolicyBase1.SelectConstructor(IBuilderContext context)
bei Microsoft.Practices.ObjectBuilder2.DynamicMethodConstructorStrategy.PreBuildUp(IBuilderContext context)
bei Microsoft.Practices.ObjectBuilder2.StrategyChain.ExecuteBuildUp(IBuilderContext context)'.
But also check the InnerExceptions for more detail or call .GetRootException().
- The Assembly that the module was trying to be loaded from was:App, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
Check the InnerException property of the exception for more information. If the exception occurred while creating an object in a DI container, you can exception.GetRootException() to help locate the root cause of the problem.
If there are other solutions for that problem I'm open-minded to it, but I want to use the basic structure presented more or less.
Thanks in advance.
You have to configure the container to disambiguate the construction of EntityContext:
Container.RegisterType<EntityContext >("Context", new InjectionConstructor(...))
Related
I tried this with Prism 8.0.0.1909 and dotnet core 3.1 and 5.
For my dialog I have a view:
<UserControl
x:Class="xzy.Modules.CapillaryBatchwise.Dialogs.NewBatchDialog"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
...
</UserControl>
and a View Model with nothing really in it for now:
namespace zxy.Modules.CapillaryBatchwise.ViewModels
{
public class NewBatchDialogViewModel : BindableBase, IDialogAware
{
...
public string Title => "MyTitle";
public event Action<IDialogResult> RequestClose;
public bool CanCloseDialog() => true;
public void OnDialogClosed()
{ }
public void OnDialogOpened(IDialogParameters parameters)
{ }
}
}
I registered the View and View Model in my App.xaml.cs
namespace xyz.CapillaryJournal
{
public partial class App
{
...
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterDialog<NewBatchDialog, NewBatchDialogViewModel>();
}
}}
And then call it from my actual ViewModel
public class CapillaryBatchNavigationViewModel : BindableBase
{
private readonly IDialogService dialogService;
public CapillaryBatchNavigationViewModel(//...
IDialogService dialogService)
{
///...
ShowNewBatchDialogCommand = new DelegateCommand(ShowNewBatchDialog);
this.dialogService = dialogService;
//...
}
public DelegateCommand ShowNewBatchDialogCommand { get; }
private void ShowNewBatchDialog()
{
dialogService.ShowDialog(nameof(ShowNewBatchDialog));
}
//...
}
However when I call ShowNewBatchDialogCommand from my View I get this exception, that I can't make any sense of:
Prism.Ioc.ContainerResolutionException: 'An unexpected error occurred while resolving 'System.Object', with the service name 'ShowNewBatchDialog''
Inner Exception
ContainerException: code: Error.UnableToResolveFromRegisteredServices;
message: Unable to resolve Resolution root Object {ServiceKey="ShowNewBatchDialog"}
from container without scope
with Rules with {TrackingDisposableTransients, UseDynamicRegistrationsAsFallbackOnly, FuncAndLazyWithoutRegistration, SelectLastRegisteredFactory} and without {ThrowOnRegisteringDisposableTransient, UseFastExpressionCompilerIfPlatformSupported}
with FactorySelector=SelectLastRegisteredFactory
with Made={FactoryMethod=ConstructorWithResolvableArguments}
with normal and dynamic registrations:
("NewBatchDialog", {FactoryID=160, ImplType=xyy.Modules.CapillaryBatchweise.Dialogs.NewBatchDialog, Reuse=TransientReuse}) ("TaskPresenter", {FactoryID=177, ImplType=xyy.Modules.CapillaryBatchweise.Views.TaskPresenter, Reuse=TransientReuse})
This is basically slightly modified what was done in this doc: https://prismlibrary.com/docs/wpf/dialog-service.html
I can't spot what is wrong with my code.
You should call dialogService.ShowDialog(nameof(NewBatchDialog)); since the name of the view's NewBatchDialog while ShowNewBatchDialog is the name of some unrelated method.
Or you can register the view with a specific name like containerRegistry.RegisterDialog<NewBatchDialog, NewBatchDialogViewModel>( "ShowNewBatchDialog" );...
My base content class. I used this class as a theme for my project. I do not know this info relevant or not. In here I create an abstract method that would overload the navigation method.
public abstract class BaseContentPage : ContentPage
{
public readonly BaseViewModel BaseViewModel;
public BaseContentPage(BaseViewModel baseViewModel)
{
BaseViewModel = baseViewModel;
}
public abstract void Navigate(SelectedItemChangedEventArgs e);
}
In my locator where I build the dependency Injection public class Locator. in this class mainly focus on adding this class to the container to make the all code loss coupling
private readonly ContainerBuilder _builder;
public Locator()
{
_builder = new ContainerBuilder();
RegisterTypes();
Container = _builder.Build();
}
public IContainer Container { get; set; }
private void RegisterTypes()
{
_builder.RegisterType<WardListService>().As<IWardListService>();
_builder.RegisterType<WardListPageViewModel>();
_builder.RegisterType<WardListPage>();
_builder.RegisterType<PatientService>().As<IPatientService>();
_builder.RegisterType<PatientListPageViewModel>();
_builder.RegisterType<PatientListViewPage>();
_builder.RegisterType<PatientDetailsPageViewModel>();
_builder.RegisterType<PatientDetailsViewPage>(); }
In my app.Xaml.Cs file
public App()
{
InitializeComponent();
Locator locator = new Locator();
Container = locator.Container;
MainPage = new NavigationPage(Container.Resolve<WardListPage>());
}
public static IContainer Container;
I used this method for navigation in my view code behind page
public async override void Navigate(SelectedItemChangedEventArgs e)
{
PatientDetailsViewPage patientDetailsViewPage = App.Container.Resolve<PatientDetailsViewPage>();
patientDetailsViewPage.BaseViewModel.SelectedPatient = e.SelectedItem as PatientViewModel;
await Navigation.PushAsync(patientDetailsViewPage);
}
This code is working perfectly but this only can navigate to one page.meaning as an example on one page we have two buttons that navigate to two different pages. I don't know how to implement this task using above navigate overloader. How to do it can anyone give suggestion to overcome the problem?. Also, I used autofac for dependency injection Thank you
You can define container in your CustomNavigationPage and use in every navigation page instance.
public class CustomNavigationPage : NavigationPage
{
public static IContainer Container;
public CustomNavigationPage()
{
Locator locator = new Locator();
locator.RegisterTypes();
Container = locator.Container();
}
}
It is dummy code what i mentioned.
You creating a navigation page that customized. So you can use this navigating your pages for example:
CustomNavigationPage.PushASync(new TestPage(Container.Resolve<WardListPage>())):
If use this your custom navigation page will be resolve your dependencies every call.
To improve performance you can register your dependencies with
singleton pattern. When the app started, dependencies will be registered.
After you use this registered dependencies.
There is an improvement : You define a static locator with singleton pattern it registers dependencies in app.cs
public sealed class Locator
{
private static Locator locator = null;
private static readonly object padlock = new object();
Locator()
{
//your registries
}
public static Locator Locator
{
get
{
lock (padlock)
{
if (locator == null)
{
locator = new Locator();
}
return locator;
}
}
}
}
And your app.cs :
public App()
{
InitializeComponent();
Locator locator = new Locator();
Container = locator.Container;
.
.
}
public static IContainer Container;
This way you only one time register your dependencies. There is no duplication of code. Only one instance will be used.
I am having a weird problem using Unity as an IOC container and im out of ideas of what could cause it. I have a service dependency in my webapi controller but it randomly fails to resolve this dependency. Sometimes i have to start my application 3 or 4 times and then it suddenly works again.
The error I am getting is:
Resolution of the dependency failed, type = "Base.WebApi.Controllers.ApiUsersController", name = "(none)". Exception occurred while: while resolving. Exception is: InvalidOperationException - The type IApiUserService does not have an accessible constructor. ----------------------------------------------- At the time of the exception, the container was: Resolving Base.WebApi.Controllers.ApiUsersController,(none) Resolving parameter "apiUserService" of constructor Base.WebApi.Controllers.ApiUsersController(Base.BLL.Services.User.IApiUserService apiUserService) Resolving Base.BLL.Services.User.IApiUserService,(none)
For initializing and registering my types in unity i use the following:
public static void RegisterTypes(IUnityContainer container)
{
var myAssemblies = AppDomain.CurrentDomain.GetAssemblies().Where(a => a.FullName.StartsWith("Base") && !a.FullName.StartsWith("Base.WebApi")).ToArray();
container.RegisterType(typeof(Startup));
container.RegisterTypes(
UnityHelpers.GetTypesWithCustomAttribute<UnityIoCSingletonLifetimedAttribute>(myAssemblies),
WithMappings.FromMatchingInterface,
WithName.Default,
WithLifetime.ContainerControlled,
null
).RegisterTypes(
UnityHelpers.GetTypesWithCustomAttribute<UnityIoCTransientLifetimedAttribute>(myAssemblies),
WithMappings.FromMatchingInterface,
WithName.Default,
WithLifetime.Transient);
}
As you can see i am using singletone and transient named attributes to define the way my dependencies should be resolved.
My controller looks like this:
public class ApiUsersController : ODataController
{
private readonly IApiUserService _apiUserService;
public ApiUsersController(IApiUserService apiUserService)
{
_apiUserService = apiUserService;
}
public IQueryable<ApiUserEntity> Get()
{
return this._apiUserService.GetUsers();
}
}
as you can see it has a dependency on user service which looks like this:
[UnityIoCTransientLifetimed]
public class ApiUserService : BaseService, IApiUserService
{
private readonly IUserRepository _userRepository;
public ApiUserService(IUserRepository userRepository, IUnitOfWork uow) : base(uow)
{
_userRepository = userRepository;
}
}
The api user repository looks like this:
[UnityIoCTransientLifetimed]
public class UserRepository : GenericRepository<ApiUserEntity>, IUserRepository
{
public UserRepository(IUnitOfWork unitOfWork, IDomainContext context) : base(unitOfWork, context)
{
}
Extending the following GenericRepository:
public class GenericRepository<T> : IGenericRepository<T> where T : class
{
protected readonly BaseContext Context;
public GenericRepository(IUnitOfWork unitOfWork, IBaseContext context)
{
// register this repository with the unit of work.
unitOfWork.Register(this);
Context = (BaseContext)context;
}
With my unit of work that looks like this:
[UnityIoCSingletonLifetimed]
public class UnitOfWork : IUnitOfWork
{
private readonly Dictionary<string, IRepository> _repositories;
// unit of work class is responsible for creating the repository and then dispossing it when no longer needed.
public UnitOfWork()
{
_repositories = new Dictionary<string, IRepository>();
}
}
However it sometimes works and sometimes it doesnt and i cant figure out why or where to look.
Finally solved it thanks to some suggestions. Looking at the documentation for
AppDomain.CurrentDomain.GetAssemblies()
it says the following:
Gets the assemblies that have been loaded into the execution context of this application domain.
Which basically means that it only loads the assemblies when they are actually needed.
The way i solved it was by using the more reliable GetReferencedAssemblies which loads all assemblies even if they are not being used.
var allAssemblies = new ReadOnlyCollection<Assembly>(
BuildManager.GetReferencedAssemblies().Cast<Assembly>().ToList());
Restarted tons of times and not one resolve crash :) Thanks everyone! For everyone looking for more information check out this SO answer: Difference between AppDomain.GetAssemblies and BuildManager.GetReferencedAssemblies
My app is an IDE add-on, so the project is a wpf user control. I want to use Prism to refector it now. When I debug the app, it threw such an exception in InitializeComponent(); of the MainView's constructor:
"ServiceLocationProvider must be set."
I also found a similar thread here: Strange exception in Prism application
but there's no solution.
This is my bootstrapper class:
class Bootstrapper : UnityBootstrapper
{
protected override System.Windows.DependencyObject CreateShell()
{
return ServiceLocator.Current.GetInstance<MainView>();
}
protected override void ConfigureModuleCatalog()
{
base.ConfigureModuleCatalog();
ModuleCatalog catalog = (ModuleCatalog)this.ModuleCatalog;
catalog.AddModule(typeof(ModuleInit));
}
}
This is my ModuleInit class:
public class ModuleInit : IModule
{
IRegionManager regionManager;
IUnityContainer container;
public ModuleInit(IUnityContainer container, IRegionManager regionManager)
{
this.container = container;
this.regionManager = regionManager;
}
public void Initialize()
{
this.container.RegisterType<ViewModelA>(new ContainerControlledLifetimeManager());
this.container.RegisterType<MainViewModel>(new ContainerControlledLifetimeManager());
this.regionManager.RegisterViewWithRegion(Models.RegionNames.ARegion, () => this.container.Resolve<ViewModelA>());
}
}
Because this is a user control project, so there's no App.xaml and App.xaml.cs file. I can bootstrapper's Run method in MainView's constructor:
public MainView()
{
InitializeComponent();
Bootstrapper strapper = new Bootstrapper();
strapper.Run();
}
But the exception is thrown in the first line of the constructor. Anyone can help?
Furthermore, is there a way to use Region without modularity? My app only contains one project, it needn't modularity. If yes, where can I register the views, services and viewmodels?
Somewhere, for example in your bootstrapper before init code, you have to define your IoC container.
This is how it's done in ViewModelLocator by default:
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
I have a Presenter that takes a Service and a View Contract as parameters in its constructor:
public FooPresenter : IFooPresenter {
private IFooView view;
private readonly IFooService service;
public FooPresenter(IFooView view, IFooService service) {
this.view = view;
this.service = service;
}
}
I resolve my service with Autofac:
private ContainerProvider BuildDependencies() {
var builder = new ContainerBuilder();
builder.Register<FooService>().As<IFooService>().FactoryScoped();
return new ContainerProvider(builder.Build());
}
In my ASPX page (View implementation):
public partial class Foo : Page, IFooView {
private FooPresenter presenter;
public Foo() {
// this is straightforward but not really ideal
// (IoCResolve is a holder for how I hit the container in global.asax)
this.presenter = new FooPresenter(this, IoCResolve<IFooService>());
// I would rather have an interface IFooPresenter so I can do
this.presenter = IoCResolve<IFooPresenter>();
// this allows me to add more services as needed without having to
// come back and manually update this constructor call here
}
}
The issue is FooPresenter's constructor expects the specific Page, not for the container to create a new one.
Can I supply a specific instance of the view, the current page, to the container for just this resolution? Does that make sense to do, or should I do this another way?
The way to solve passing what I like to call data parameters when resolving dependencies in Autofac is by using generated factories.
(Update: this question discusses the same problem and my article shows how you can avoid large amounts of factory delegates).
The solution to your problem will look something like this:
First, declare a factory delegate thath only accepts the data parameters:
public delegate IFooPresenter FooPresenterFactory(IFooView view);
Your presenter goes unchanged:
public FooPresenter : IFooPresenter {
private IFooView view;
private readonly IFooService service;
public FooPresenter(IFooView view, IFooService service) {
this.view = view;
this.service = service;
}
}
Next the Autofac container setup:
var builder = new ContainerBuilder();
builder.Register<FooService>().As<IFooService>().FactoryScoped();
builder.Register<FooPresenter>().As<IFooPresenter>().FactoryScoped();
builder.RegisterGeneratedFactory<FooPresenterFactory>();
Now in your page you can in two lines of code resolve the presenter by first getting the factory and then calling the factory to do the resolution for you:
public partial class Foo : Page, IFooView {
private FooPresenter presenter;
public Foo() {
var factory = IoCResolve<FooPresenterFactory>();
this.presenter = factory(this);
}
}
I actually solved this exact problem and built a framework around it. I used Autofac parameters to pass existing views to the presenter resolution call.
First, I defined a custom resolution interface derived from Autofac's:
public interface IMvpContext : IContext
{
T View<T>();
}
which allowed me to register a presenter which resolves the view:
builder.RegisterPresenter(c => new FooPresenter(
c.View<IFooView>(),
c.Resolve<IFooService>()));
using an extension method which wraps Autofac's IContext in an implementation of IMvpContext:
public static IConcreteRegistrar RegisterPresenter<T>(
this ContainerBuilder builder,
Func<IMvpContext, T> creator)
{
return builder
.Register((context, parameters) => creator(new MvpContext(context, parameters)))
.FactoryScoped();
}
I defined a parameter type representing the view parameter:
public class MvpViewParameter : NamedParameter
{
public static readonly string ParameterName = typeof(MvpViewParameter).AssemblyQualifiedName;
public MvpViewParameter(object view) : base(ParameterName, view)
{}
}
It uses its own assembly-qualified type name as the parameter name. This has a very low likelihood of conflicting with legitimate parameters.
MvpContext passes all standard resolution calls to the base context. For the view, it resolves the parameter with the well-known name:
public sealed class MvpContext : IMvpContext
{
private IContext _context;
private IEnumerable<Parameter> _resolutionParameters;
public MvpContext(IContext context, IEnumerable<Parameter> resolutionParameters)
{
_context = context;
_resolutionParameters = resolutionParameters;
}
#region IContext
// Pass through all calls to _context
#endregion
#region IMvpContext
public T View<T>()
{
return _resolutionParameters.Named<T>(MvpViewParameter.ParameterName);
}
#endregion
}
The call to resolve the presenter provides the view parameter:
public partial class Foo : Page, IFooView
{
private readonly FooPresenter presenter;
public Foo()
{
this.presenter = IoCResolve<IFooPresenter>(new MvpViewParameter(this));
}
}