MvvmCross Viewmodel creation issue - inversion-of-control

I'm not able to instantiate the viewmodel when the viewmodel dependencies spread across different projects.
The ViewModel constructor is as follows:
public MyViewModel(IMyBusinessLogic businessLogic)
{
...
}
public class App : Cirrious.MvvmCross.ViewModels.MvxApplication
{
public override void Initialize()
{
CreatableTypes().
EndingWith("BusinessLogic").
AsInterfaces().
RegisterAsLazySingleton();
RegisterAppStart<MyViewModel>();
}
}
IMyBusinessLogic is in different project of the same solution. The actual implementation of this interface is in a different project.
I have added business logic interface project as a reference in viewmodel project.
Can anyone help in resolving this issue?
The Error log:
mvx:Diagnostic: 0.12 Showing ViewModel MyViewModel 'TaskHost.exe'
(CoreCLR: Silverlight AppDomain): Loaded
'C:\Data\Programs{9E891FD9-C43E-4ED3-9EDC-153E29371D89}\Install\BusinessLogic.Interface.DLL'.
Symbols loaded. mvx:Warning: 0.38 Problem creating viewModel of type
MyViewModel - problem MvxIoCResolveException: Failed to resolve
parameter for parameter myBusinessLogic of type IMyBusinessLogic when
creating ViewModel.Core.MyViewModel
at Cirrious.CrossCore.IoC.MvxSimpleIoCContainer.GetIoCParameterValues(Type
type, ConstructorInfo firstConstructor) at
Cirrious.CrossCore.IoC.MvxSimpleIoCContainer.IoCConstruct(Type type)
at Cirrious.CrossCore.Mvx.IocConstruct(Type t) at
Cirrious.MvvmCross.ViewModels.MvxDefaultViewModelLocator.TryLoad(Type
viewModelType, IMvxBundle parameterValues, IMvxBundle savedState,
IMvxViewModel& viewModel) 'TaskHost.exe' (CoreCLR: Silverlight
AppDomain): Loaded
'C:\windows\system32\en-US\mscorlib.debug.resources.dll'. Module was
built without symbols.
# Stuart:
As it was mentioned in the wiki, i have override the method GetViewModelAssemblies() in the Setup.cs which exist in the platform specific UI project.
protected override Assembly[] GetViewModelAssemblies()
{
var list = new List<Assembly>();
list.AddRange(base.GetViewModelAssemblies());
list.Add(typeof(BusinessLogic.Core.MyBusinessLogic).Assembly);
list.Add(typeof(BusinessLogic.Interface.IMyBusinessLogic).Assembly);
return list.ToArray();
}
But now what i have observed is the sequence of call is first App.Initialize() and then Setup.GetViewModelAssemblies(). so again i'm seeing the same issue that it is not able to find the required type from IoC to craete the viewmodel. Any suggestion on this?

For looking up ViewModels in multiple assemblies, you can use https://github.com/MvvmCross/MvvmCross/wiki/Customising-using-App-and-Setup#providing-additional-view-and-viewmodel-assemblies
protected override Assembly[] GetViewModelAssemblies()
{
var list = new List<Assembly>();
list.AddRange(base.GetViewModelAssemblies());
list.Add(typeof(SomeTypeFromAdditionalViewModelAssembly1).Assembly);
list.Add(typeof(SomeTypeFromAdditionalViewModelAssembly2).Assembly);
list.Add(typeof(SomeTypeFromAdditionalViewModelAssembly3).Assembly);
// ...
return list.ToArray();
}
For IoC registering services from different assemblies, there's an Assembly extension method available - see https://github.com/MvvmCross/MvvmCross/wiki/Service-Location-and-Inversion-of-Control#bulk-registration-by-convention
If you're looking to understand how IoC works in MvvmCross, then https://github.com/MvvmCross/MvvmCross/wiki/Service-Location-and-Inversion-of-Control is a good starting point <- recommended reading.
you can you can also, of course, use the same type of registration logic on assemblies other than Core - e.g.:
typeof(Reusable.Helpers.MyHelper).Assembly.CreatableTypes()
.EndingWith("Helper")
.AsInterfaces()
.RegisterAsDynamic();

Related

How to use AutoFac with .NET MAUI

A few years ago I created a suite of nuget packages that I use for CQRS which use AutoFac modules to wire up the internals. I'd like to use that in my .NET MAUI development so I've updated them to .NET 6.0 and they link in with my MAUI project nicely, but I'm uncertain what's missing from my registrations. My framework's AutoFac Module registers an IDateTimeService but when I add that to a registered class' constructor it can't be resolved.
So, following the AutoFac guide for .NET Core I've added the Populate call and then Load the AutoFac module.
using Autofac;
using Autofac.Extensions.DependencyInjection;
using Pages;
using Perigee.Framework.Services;
using Services;
using ViewModels;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
builder.Services.AddSingleton<IAppNavigationService, AppNavigationService>();
builder.Services.AddSingleton<AppShellViewModel>();
builder.Services.AddTransient<MainPageViewModel>();
builder.Services.AddSingleton<AppShell>();
builder.Services.AddSingleton<MainPage>();
// Hook in AutoFac for the PerigeeFramework services
var autofacBuilder = new ContainerBuilder();
autofacBuilder.Populate(builder.Services);
autofacBuilder.RegisterModule(new ServicesModule());
autofacBuilder.Build(); // Invokes the Load method on the registered Modules.
return builder.Build();
}
}
The AutoFac Module starts like this:
public class ServicesModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.Register(c =>
{
var config = c.IsRegistered<IDateTimeConfig>() ? c.Resolve<IDateTimeConfig>() : null;
return new DateTimeService(config);
}).As<IDateTimeService>().InstancePerLifetimeScope();
and this is the definition of AppShellViewModel
public AppShellViewModel(IDateTimeService dateTimeService)
which is injected into the AppShell:
public partial class AppShell : Shell
{
public AppShell(AppShellViewModel viewModel)
{
InitializeComponent();
BindingContext = viewModel;
At run time the IDateTimeService doesn't resolve. I've also tried just registering with AutoFac without a module and it won't resolve:
// Hook in AutoFac for the PerigeeFramework services
var autofacBuilder = new ContainerBuilder();
autofacBuilder.Populate(builder.Services);
autofacBuilder.RegisterType<DateTimeService>().As<IDateTimeService>().SingleInstance();
var cont = autofacBuilder.Build();
return builder.Build();
}
The key reason I needed something other than .NET DI was because the architecture leverages decorators, which SimpleInjector and AutfoFac provide out of the box so I chose AutoFac. In either case I need to use this "crosswire" approach to use AutoFac and .NET DI as MAUI is using the built in one. Does anyone know what step I'm missing that is preventing the registrations from an AutoFac module from appearing in the IServiceCollection, or can I completely replace the .NET DI with AutoFac on the MauiApp?
EDIT:
I've put together a trimmed down version of my app. I figured maybe I need to pass a new AutoFacServiceProvider through to the App and the ISomeService does resolve when registered with AutoFac
But the call to MainPage = serviceProvider.GetService<AppShell>() fails to resolve if I try to inject ISomeService into another registered class. If the service is registered with the standard DI it will work.
Anyone know how to propogate the AutoFac Service Provider as the one Maui will use? The project is here
The MauiAppBuilder (called from the MauiProgram) has a method called ConfigureContainer, it takes an IServiceProvider factory that Autofac provides as AutofacServiceProviderFactory and optionally it can take an Action<ContainerBuilder> delegate, where you can define your configuration.
In your case that could look like this:
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
})
.ConfigureContainer(new AutofacServiceProviderFactory(), autofacBuilder => {
// Registrations
// Don't call the autofacBuilder.Build() here - it is called behind the scenes
});
return builder.Build();
}
}

EF Core Migrations in Azure Function startup

According to https://learn.microsoft.com/en-us/azure/azure-functions/functions-dotnet-dependency-injection the service provider should not be used until AFTER the startup has completed running. Indeed, if I try to get a registered service it will fail.
Example:
[assembly: FunctionsStartup(typeof(Startup))]
namespace Fx {
public sealed class Startup : FunctionsStartup {
public override void Configure(IFunctionsHostBuilder builder) {
var configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddEnvironmentVariables();
var configuration = configurationBuilder.Build();
builder.Services.AddInfrastructure(configuration);
builder.Services.AddApplication();
var serviceProvider = builder.Services.BuildServiceProvider();
DependencyInjection.AddDatabase(serviceProvider).GetAwaiter().GetResult();
}
}
}
public static class DependencyInjection {
public static async Task AddDatabase(IServiceProvider services) {
using var scope = services.CreateScope();
var serviceProvider = scope.ServiceProvider;
var context = serviceProvider.GetRequiredService<ApplicationDbContext>();
//Error generated here
if (context.Database.IsSqlServer()) {
await context.Database.MigrateAsync();
}
await ApplicationDbContextSeed.SeedSamplePersonnelDataAsync(context);
}
public static IServiceCollection AddInfrastructure(
this IServiceCollection services,
IConfiguration configuration) {
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(configuration.GetConnectionString("DefaultConnection"),
b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName)));
services.AddScoped<IApplicationDbContext>(provider => provider.GetService<ApplicationDbContext>());
return services;
}
}
This produces the following error
Microsoft.EntityFrameworkCore: No database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider. If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions<TContext> object in its constructor and passes it to the base constructor for DbContext.
Is there a good option for migrating and seeding during startup?
The easiest way I found to run code after startup was by registering a custom IWebJobsStartup by using the WebJobsStartupAttribute (the FunctionsStartupAttribute actually also inherits from this attribute). In the WebJobsStartup class you'll need to register your extension using the AddExtension where you are able to use dependency injection and seed your database. My code:
[assembly: WebJobsStartup(typeof(DbInitializationService), "DbSeeder")]
namespace Our.Database.Seeder
{
public class DbInitializationService : IWebJobsStartup
{
public void Configure(IWebJobsBuilder builder)
{
builder.AddExtension<DbSeedConfigProvider>();
}
}
[Extension("DbSeed")]
internal class DbSeedConfigProvider : IExtensionConfigProvider
{
private readonly IServiceScopeFactory _scopeFactory;
public DbSeedConfigProvider(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public void Initialize(ExtensionConfigContext context)
{
using var scope = _scopeFactory.CreateScope();
var dbContext = scope.ServiceProvider.GetService<YourDbContext>();
dbContext.Database.EnsureCreated();
// Further DB seeding, etc.
}
}
}
According to your code, I assume that you're building something aligned to the CleanArchitecture Repository on Github. https://github.com/jasontaylordev/CleanArchitecture
The main difference between this repo and your apporach, is that you're obviously not using ASP.NET, which is not a problem at all, but requires a little bit more configuration work.
The article already mentioned (https://markheath.net/post/ef-core-di-azure-functions) refers another blogpost (https://dev.to/azure/using-entity-framework-with-azure-functions-50aa), which briefly explains that EntityFramework Migrations are not capable of auto-discovering your migrations in an Azure Function. Therefore, you need to implement an instance of IDesignTimeDbContextFactory. I also stumbled upon it in the microsoft docs:
https://learn.microsoft.com/en-us/ef/core/cli/dbcontext-creation?tabs=dotnet-core-cli#from-a-design-time-factory
You could for example place it inside your Infrastructure\Persistence\Configurations folder. (Once again, I'm only assuming that you're following the CleanArchitecture repo structure)
DI in AZURE Functions
Caveats
A series of registration steps run before and after the runtime processes the startup class. Therefore, keep in mind the following items:
The startup class is meant for only setup and registration. Avoid using services registered at startup during the startup process. For instance, don't try to log a message in a logger that is being registered during startup. This point of the registration process is too early for your services to be available for use. After the Configure method is run, the Functions runtime continues to register additional dependencies, which can affect how your services operate.
The dependency injection container only holds explicitly registered types. The only services available as injectable types are what are setup in the Configure method. As a result, Functions-specific types like BindingContext and ExecutionContext aren't available during setup or as injectable types

Do we really need viewModelFactories and viewmodelProviders when using Dagger?

So I was working on some sample MVVM project using Dagger. I have a viewmodel factory that goes like this:
class DaggerViewModelFactory #Inject constructor(private val viewModelsMap: Map<Class<out ViewModel>, #JvmSuppressWildcards Provider<ViewModel>>) :
ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
val creator = viewModelsMap[modelClass] ?:
viewModelsMap.asIterable().firstOrNull {
modelClass.isAssignableFrom(it.key)
}?.value ?: throw IllegalArgumentException("unknown model class $modelClass")
return try {
creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
A viewmodel factory module
#Module
abstract class ViewModelFactoryModule {
#Binds
abstract fun bindViewModelFactory(viewModelFactory: DaggerViewModelFactory): ViewModelProvider.Factory
}
I got a ViewModelModule:
#Module
abstract class MyViewModelModule {
#Binds
#IntoMap
#ViewModelKey(TakePicturesViewModel::class)
abstract fun bindTakePictureViewModel(takePicturesViewModel: TakePicturesViewModel): ViewModel
}
A component that goes like this:
#PerActivity
#Subcomponent(modules = [ActivityModule::class, ViewModelFactoryModule::class, MyViewModelModule::class])
interface ActivityComponent {
fun inject(mainActivity: MainActivity)
}
An a viewmodel that goes like this:
class TakePicturesViewModel #Inject constructor(app: Application): AndroidViewModel(app) {...
So I can either inject my viewmodel in my activity using a view model factory like this:
#Inject
lateinit var viewModelFactory: DaggerViewModelFactory
private lateinit var takePicturesViewModel: TakePicturesViewModel
.
.
.
takePicturesViewModel = ViewModelProviders.of(this, viewModelFactory).get(TakePicturesViewModel::class.java)
Or with not viewmodel factory at all, like this:
#Inject
lateinit var takePicturesViewModel: TakePicturesViewModel
Both ways work, so I was wondering which one is the right way to work, if using Dagger allows me to inject a viewmodel without needing a viewmodelfactory, is there a good reason to keep it?, or should I just get rid of this viewmodelfactory?
Thanks in advance for any advice.
Greetings
Both ways work, so I was wondering which one is the right way to work, if using Dagger allows me to inject a viewmodel without needing a viewmodelfactory, is there a good reason to keep it?, or should I just get rid of this viewmodelfactory?
Both ways work differently. Try rotating your screen with stored data in your ViewModel and you'll see.
Dagger can create the ViewModel, which is what you make use of in that generic ViewModelFactory. Those view models should be unscoped, thus you'll be creating a new ViewModel every single time. The Android support library will cache that ViewModel and reuse it after rotation so that you can keep your data—the factory method gets called once and there will only ever be one ViewModel created (per lifecycle). You keep your data and everything behaves as expected.
If on the other hand you use Dagger to inject your ViewModel directly none of the above will apply. Like any other dependency, a new ViewModel will be injected on creation, leading to a ViewModel being created every single time it is used—you'll not only use the data stored in it, you won't be able to share state with fragments either.
Of course you could apply a scope to the ViewModel, but that scope should be longer lived than the Activity instance (to keep state between rotations), but no longer lived than the screen is visible. So you can neither scope it to the activity nor to the application lifecycle. You can get it to work by introducing a new scope in-between, but at this point you'd be reinventing the ViewModel library.
tl;dr Inject and use the factory or you'll get a confusing/wrong ViewModel implementation.

Template10 MVVM IoC Inject ViewModel into Shell View

I'm looking for the best way to inject a ViewModel into a Shell view.
I'm using Autofac (but I can adopt code from other IoC containers if sample is available). I have got the other VMs injecting correctly - but the method that resolves the VM using ResoleForPage method of the App class.
I'm fairly new to UWP developement and any help is greatly appreciated!
Passing a ViewModel to the Shell is indeed simpler than passing it to the other pages, because the Shell is the only page that is created explicitly by us: so, it should be enough to add a parameter to the constructor of the Shell of type ShellViewModel:
public Shell()
{
Instance = this;
this.InitializeComponent();
}
public Shell(INavigationService navService, ShellViewModel model) : this()
{
navigationMenu.NavigationService = navService;
navigationMenu.RefreshStyles(App.Current.RequestedTheme, true);
this.DataContext = model;
}
then expose the DataContext in a strongly typed way, as with any other pages (useful mainly if you use x:Bind bindings in xaml):
public ShellViewModel ViewModel => DataContext as ShellViewModel;
And now you just have to pass an instance of your ViewModel class, pulling it from your IoC container, when you create the Shell. In the latest Template 10 template for VS2017, it should be in the CreateRootElement method of the App class:
public override UIElement CreateRootElement(IActivatedEventArgs e)
{
var service = NavigationServiceFactory(BackButton.Attach, ExistingContent.Include);
return new Template10.Controls.ModalDialog
{
DisableBackButtonWhenModal = true,
Content = new Shell(service, new ShellViewModel()),
};
}
of course replacing new ShellViewModel() with the code to pull it from Autofac.

MvvmCross: Does ShowViewModel always construct new instances?

Whenever I call ShowViewModel, somehow a ViewModel and a View of the requested types are retrieved and are bound together for display on the screen. When are new instances of the ViewModel and View created versus looked up and retrieved from a cache somewhere? If new instances are always created and I choose to make my own cache to prevent multiple instances, then how do I show my cached ViewModel instance?
When are new instances of the ViewModel and View created versus looked up and retrieved from a cache somewhere?
Never - for new navigations the default behaviour is always to create new instances.
if... how do I show my cached ViewModel instance?
If for whatever reason you want to override the ViewModel location/creation, then there's information available about overriding the DefaultViewModelLocator in your App.cs in:
MVVMCross Passing values to ViewModel that has 2 constructors
http://slodge.blogspot.co.uk/2013/01/navigating-between-viewmodels-by-more.html
Put simply, implement your code:
public class MyViewModelLocator
: MvxDefaultViewModelLocator
{
public override bool TryLoad(Type viewModelType, IDictionary<string, string> parameterValueLookup,
out IMvxViewModel model)
{
// your implementation
}
}
then return it in App.cs:
protected override IMvxViewModelLocator CreateDefaultViewModelLocator()
{
return new MyViewModelLocator();
}
Note that older posts like How to replace MvxDefaultViewModelLocator in MVVMCross application are still conceptually compatible - but the details in those older posts are now out of date.
In MvvmCross v3.5 you can use this Class:
public class CacheableViewModelLocator : MvxDefaultViewModelLocator{
public override IMvxViewModel Load(Type viewModelType, IMvxBundle parameterValues, IMvxBundle savedState)
{
if (viewModelType.GetInterfaces().Any(x=>x == typeof(ICacheableViewModel)))
{
var cache = Mvx.Resolve<IMvxMultipleViewModelCache>();
var cachedViewModel = cache.GetAndClear(viewModelType);
if (cachedViewModel == null)
cachedViewModel = base.Load(viewModelType, parameterValues, savedState);
cache.Cache(cachedViewModel);
return cachedViewModel;
}
return base.Load(viewModelType, parameterValues, savedState);
}}
in your App Code override this method:
protected override IMvxViewModelLocator CreateDefaultViewModelLocator(){
return new CacheableViewModelLocator();}
Create an interface "ICacheableViewModel" and implement it on your ViewModel.
Now you can share the same ViewModel instance with multiple Views.