Closed abrasat closed 7 years ago
Yes, the FrameworkContainer is located in the .NET Standard "Codon" package. It can and is used for all supported platforms. To use it in your Forms project, access it via the Codon.Dependency class. It's initialized upon first use.
I was reluctant to create a Forms sample because of its rate of change and dependencies. I may put something together, but can't promise.
How would the "Bootstrap" code for Xamarin.Forms (with .NET Standard) look like ? I am not sure where to put the dependency resolve code and the navigation route initialization code in this case.
Here's a sample solution with Xamarin Forms.
You'll notice it includes a FormsNavigationService, which was needed to utilize the Forms Navigation system. I'll merge that with the code-base at a later time. I also included a .NET Standard project for the app logic. While not necessary, I find that working with shared projects and pre-processor directives can make refactoring troublesome.
I've smoke tested the Android and UWP apps, but not the iOS.
Thank you for the sample. The navigation works fine, but the show dialog and publish message do not work (I tested it with the VS android emulator). I tried to debug and the Page1ViewModel constructor is not called. Not sure what Page1ViewModel instance does the dependency resolver in MainPage return (default constructor ?). Do the DialogService and SettingsService also have to be registered in the bootstrapper code?
Regarding the bootstraper code, would be possible to have it also as .NET standard project, and inject somehow the platform specific dependencies over the App() constructor ? Or is better to leave it as shared code ?
I've attached an updated sample.
You need to let the DialogService for Android know how to resolve the current Activity.
You can do this in the MainActivity class, like so:
Dependency.Register<Activity>(this);
Because we're using a custom service used by the Page1ViewModel, we need to ensure that the Bootstrapper has finished running. You can do that in the MainPage.xaml.cs, as shown:
protected override void OnAppearing()
{
base.OnAppearing();
if (BindingContext == null)
{
BindingContext = Dependency.Resolve<Page1ViewModel>();
}
}
Looks better now, thanks !
Are the Register() calls asynchronous ? What happens if Resolve() cannot get object instance ? I mean, what would happen if in OnAppearing() the dependency is still not resolvable ?
Register calls are synchronous. If Resolve is called using an interface, like so:
Dependency.Resolve<IMyInterface>();
and no registration is found for IMyInterface, a TypeResolutionException is thrown.
In the same situation, if Resolve is called using a concrete type argument, then a new instance of that type is returned.
The problem was that the bootstrapper didn't have a chance to register the FormsNavigationService before the MainPage attempted to resolve Page1ViewModel, which in turn caused INavigationService to be resolved via dependency injection. This resulted in the default platform specific navigation service being passed to the Page1ViewModel constructor, and not the FormsNavigationService.
I have an application where I would like to inject more other dependencies in the ViewModel (kind of web services where for instance I pass the interface to the view model constructor; I can then either register the "real" implementation, or a mock implementation). These dependencies are using .Net standard libraries (for instance NewtonsoftJson). The problem is that the boostrapper is running in "shared" code, that means in Android environment, where NewtonsoftJson is not available, and thus the dependency cannot be registered. But in principle that would occur also in other cases, for instance if there is a ILog implementation for .Net standard, which does not exist in Android. How can I register it ? Is there any possibility to set Codon dependencies also in the .Net standard (or PCL for earlier Xamarin projects) environment ? That would probably imply that the pages are later created, as they need the view model as binding context.
Yes, you can do that. The only reason the bootstrapper is sitting in shared code is that the routes need to refer the the pages in the app. Create another class and place the routing configuration in it. Move the bootstrapper to your .NET Standard library. You app class in the shared library will now call your routing config class and your bootstrapper's Run method.
I should also mention that Codon's FrameworkContainer supports default type and type name attributes, which allows it to resolve the implementation if no mapping registration is made. You can decorate your interfaces with a
[Codon.InversionOfControl.DefaultType(typeof(MyConcreteTypeThatImplementsThisInterface), Singleton=true)]
or a
[Codon.InversionOfControl.DefaultTypeName("MyNamespace.FooClass, MyAssembly", Singleton = true)]
or even a
[System.ComponentModel.DefaultValue(typeof(MyConcreteTypeThatImplementsThisInterface))]
which doesn't require a Codon reference. When using the built-in DefaultValue attribute, Codon assumes you want a singleton.
These attributes are the reason that Codon doesn't require any initialization from your app.
Thank you for the information. The attributes are a nice option although I personally prefer the "bootstrap" style of setting the dependencies at one place in code.
What happens if both kinds of dependency are necessary ? I mean some for the different platform-dependent implementations (for Android, UWP, iOS), and also some for different .NET standard implementations. Do you eventually have any samples about how to organize the bootstrap for such a scenario ?
Attributes provide a fall-back mechanism. If there is no mapping registration, the FrameworkContainer looks for the attributes. The Xamarin Forms sample demonstrates how to register a type. I'm not sure what you're asking.
I understand how to register the types. What I cannot figure out is how to change the code in view to able to pass an additional interface to the view model constructor, which is registered in the .NET standard code, something like this:
public Page1ViewModel( IDialogService dialogService, INavigationService navigationService, ISettingsService settingsService, IMyWebService myWebService)
Where must be placed this .NET standard code to register the additional type, as this must be executed before constructing the view model ?
Dependency.Register<IMyWebService>(() => new MyWebService(), true);
The Page1ViewModel is actually already constructed and resolved in the shared project, in MainPage OnAppearing:
BindingContext = Dependency.Resolve<Page1ViewModel>();
At this point, MyWebService is not registered yet.
Place the dependency registration in your bootstrapper. The FrameworkContainer selects the constructor of your view-model that has the most number of parameters (or is decorated with a InjectDependencies attribute). Your IMyWebService will be resolved automatically and passed to your view-model's constructor.
Is there any possibility to set breakpoints and debug the code from the Page1ViewModel or Page2ViewModel from the last sample above ? The application is working correctly, but the breakponts are hit for instance in the Bootstrapper code, but not in the ViewModels (I am using the VisualStudio Android Emulator).
I just verified that I am able to debug the Page1ViewModel. I set a breakpoint in the constructor and ShowMessage method, and the debugger did break. (VS Android Emulator) I have, however, seen this issue before with Xamarin. I recall there was an issue resolved some time ago. I suggest you ensure that VS 2017 and Xamarin are up to date via the Tools/Extension and Updates option in VS.
Thank you for the reply, Found meanwhile the workaround here Are you using the Android emulator integrated in VisualStudio (under Tools), or the stand-alone VisualStudio Emulator for Android ?
I need to use the Environment.GetFolderPath functionality in my Xamarin.Forms project. Do I need to implement a DependencyService like described here , or can I solve it with the Codon FrameworkContainer registration/resolve mechanisms (my bootstrapper code is actually in a .NetStandard library). If possible with Codon, could you please put a code snippet about how to do it ?
What happens if constructing and initializing a service (lets say a connection to some cloud, database or whatever) might take some time. If such a service would be registered in the bootstrapper would block the startup of the application. Wouldn't be better to initialize and register such services asynchronously in the application OnStart() method ? In this case the problem is that the view models are already created and thus the Resolve() for such a service cannot be called in the view model constructor, as not yet available to that point. Is there any possibility in Codon to publish an event when some object (service in our case) has been registered ? Or is there a better way to implement such a scenario ?
Codon's FrameworkContainer does not have an event for registrations.
There are several ways you can do this. The way I usually do this is that I create an EnsureInitializedAsync method in the service that must be awaited before using the service. That method can be called when the app is starting or when the service is required; depending on the UI thread requirements. Alternatively, you could publish a message via the IMessenger, announcing that the service is available when it is initialized.
Thanks for the reply. If I understand correctly, the best way to handle the creation and initialization of services requiring asynchronous access is directly in the application (by using some asynchronous factories), without using the Codon framework Register/Resolve methods.
Yes, since the IoC container works synchronously.
Could you please extend the Sample001 with the implementation for a Xamarin.Forms based project ? Does the Codon framework also have a IoC container implementation (.NET Standard) that can be used for dependency injection for Xamarin.Forms projects ?