I am using autofac in an UWP application. In my App
instance, I am setting up the dependency, like this:
public sealed partial class App
{
private readonly IFacade m_facade;
public App()
{
InitializeComponent();
m_facade = InitializeDependencies();
Suspending += OnSuspending;
}
private IFacade InitializeDependencies()
{
var containerBuilder = new ContainerBuilder();
// Registers all the platform-specific implementations of services.
containerBuilder.RegisterType<LoggingService>()
.As<ILoggingService>()
.SingleInstance();
containerBuilder.RegisterType<SQLitePlatformService>()
.As<ISQLitePlatformService>()
.SingleInstance();
containerBuilder.RegisterType<DiskStorageService>()
.As<IDiskStorageService>()
.SingleInstance();
...
containerBuilder.RegisterType<Facade>()
.As<IFacade>();
// Auto-magically resolves the IFacade implementation.
var facadeContainer = containerBuilder.Build();
var facadeLifetimeScope = m_facadeContainer.BeginLifetimeScope();
return facadeLifetimeScope.Resolve<IFacade>();
}
}
I need to pass my IFacade
instance around to the different Page
s to reach my view-models. Here is an example of one of my pages:
internal sealed partial class SomePage
{
public SomePageViewModel ViewModel { get; }
public SomePage()
{
ViewModel = new SomePageViewModel(/* need an IFacade implementation here!! */);
InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
ViewModel.LoadAsync();
base.OnNavigatedTo(e);
}
}
UWP is responsible for the Page
s instantiation so it limits my options. Here is how the navigation from one page to another is done in UWP. From the App
instance:
rootFrame.Navigate(typeof(MainPage), e.Arguments);
Or from a Page
instance:
ContentFrame.Navigate(typeof(SomeOtherPage));
Question
What would be the proper way to pass my IFacade
instance around to the view-models (without doing anything hacky obviously)?
Autofac is an IoC container for Microsoft . NET 4.5, Silverlight 5, Windows Store apps, and Windows Phone 8 apps. It manages the dependencies between classes so that applications stay easy to change as they grow in size and complexity. This is achieved by treating regular .
Autofac is an addictive Inversion of Control container for . NET Core, ASP.NET Core, . NET 4.5. 1+, Universal Windows apps, and more.
Because UWP is responsible for the Page
's instantiation it removes the ability to explicitly inject dependencies into views.
The simplest approach would be to have an accessible service locator and register your dependencies with it.
public sealed partial class App {
public App() {
InitializeComponent();
Container = ConfigureServices();
Suspending += OnSuspending;
}
public static IContainer Container { get; set; }
private IContainer ConfigureServices() {
var containerBuilder = new ContainerBuilder();
// Registers all the platform-specific implementations of services.
containerBuilder.RegisterType<LoggingService>()
.As<ILoggingService>()
.SingleInstance();
containerBuilder.RegisterType<SQLitePlatformService>()
.As<ISQLitePlatformService>()
.SingleInstance();
containerBuilder.RegisterType<DiskStorageService>()
.As<IDiskStorageService>()
.SingleInstance();
containerBuilder.RegisterType<Facade>()
.As<IFacade>();
//...Register ViewModels as well
containerBuilder.RegisterType<SomePageViewModel>()
.AsSelf();
//...
var container = containerBuilder.Build();
return container;
}
//...
}
And then resolve dependencies as needed in the views.
internal sealed partial class SomePage {
public SomePage() {
InitializeComponent();
ViewModel = App.Container.Resolve<SomePageViewModel>();
this.DataContext = ViewModel;
}
public SomePageViewModel ViewModel { get; private set; }
protected override void OnNavigatedTo(NavigationEventArgs e) {
ViewModel.LoadAsync();
base.OnNavigatedTo(e);
}
}
Another more complicated way would be to use a convention base approach and tapping into the application's Frame navigation.
The plan would be to subscribe to the NavigatedTo
event
public interface INavigationService {
bool Navigate<TView>() where TView : Page;
bool Navigate<TView, TViewModel>(object parameter = null) where TView : Page;
}
public class NavigationService : INavigationService {
private readonly Frame frame;
private readonly IViewModelBinder viewModelBinder;
public NavigationService(IFrameProvider frameProvider, IViewModelBinder viewModelBinder) {
frame = frameProvider.CurrentFrame;
frame.Navigating += OnNavigating;
frame.Navigated += OnNavigated;
this.viewModelBinder = viewModelBinder;
}
protected virtual void OnNavigating(object sender, NavigatingCancelEventArgs e) { }
protected virtual void OnNavigated(object sender, NavigationEventArgs e) {
if (e.Content == null)
return;
var view = e.Content as Page;
if (view == null)
throw new ArgumentException("View '" + e.Content.GetType().FullName +
"' should inherit from Page or one of its descendents.");
viewModelBinder.Bind(view, e.Parameter);
}
public bool Navigate<TView>() where TView : Page {
return frame.Navigate(typeof(TView));
}
public bool Navigate<TView, TViewModel>(object parameter = null) where TView : Page {
var context = new NavigationContext(typeof(TViewModel), parameter);
return frame.Navigate(typeof(TView), context);
}
}
and once there using the navigation argument to pass the view model type to be resolved and data bind to the view.
public interface IViewModelBinder {
void Bind(FrameworkElement view, object viewModel);
}
public class ViewModelBinder : IViewModelBinder {
private readonly IServiceProvider serviceProvider;
public ViewModelBinder(IServiceProvider serviceProvider) {
this.serviceProvider = serviceProvider;
}
public void Bind(FrameworkElement view, object viewModel) {
InitializeComponent(view);
if (view.DataContext != null)
return;
var context = viewModel as NavigationContext;
if (context != null) {
var viewModelType = context.ViewModelType;
if (viewModelType != null) {
viewModel = serviceProvider.GetService(viewModelType);
}
var parameter = context.Parameter;
//TODO: figure out what to do with parameter
}
view.DataContext = viewModel;
}
static void InitializeComponent(object element) {
var method = element.GetType().GetTypeInfo()
.GetDeclaredMethod("InitializeComponent");
method?.Invoke(element, null);
}
}
The Frame
is accessed via a wrapper service that extracts it from the current window
public interface IFrameProvider {
Frame CurrentFrame { get; }
}
public class DefaultFrameProvider : IFrameProvider {
public Frame CurrentFrame {
get {
return (Window.Current.Content as Frame);
}
}
}
And the following extension classes provide utility support
public static class ServiceProviderExtension {
/// <summary>
/// Get service of type <typeparamref name="TService"/> from the <see cref="IServiceProvider"/>.
/// </summary>
public static TService GetService<TService>(this IServiceProvider provider) {
return (TService)provider.GetService(typeof(TService));
}
/// <summary>
/// Get an enumeration of services of type <paramref name="serviceType"/> from the <see cref="IServiceProvider"/>
/// </summary>
public static IEnumerable<object> GetServices(this IServiceProvider provider, Type serviceType) {
var genericEnumerable = typeof(IEnumerable<>).MakeGenericType(serviceType);
return (IEnumerable<object>)provider.GetService(genericEnumerable);
}
/// <summary>
/// Get an enumeration of services of type <typeparamref name="TService"/> from the <see cref="IServiceProvider"/>.
/// </summary>
public static IEnumerable<TService> GetServices<TService>(this IServiceProvider provider) {
return provider.GetServices(typeof(TService)).Cast<TService>();
}
/// <summary>
/// Get service of type <paramref name="serviceType"/> from the <see cref="IServiceProvider"/>.
/// </summary>
public static object GetRequiredService(this IServiceProvider provider, Type serviceType) {
if (provider == null) {
throw new ArgumentNullException("provider");
}
if (serviceType == null) {
throw new ArgumentNullException("serviceType");
}
var service = provider.GetService(serviceType);
if (service == null) {
throw new InvalidOperationException(string.Format("There is no service of type {0}", serviceType));
}
return service;
}
/// <summary>
/// Get service of type <typeparamref name="T"/> from the <see cref="IServiceProvider"/>.
/// </summary>
public static T GetRequiredService<T>(this IServiceProvider provider) {
if (provider == null) {
throw new ArgumentNullException("provider");
}
return (T)provider.GetRequiredService(typeof(T));
}
}
public class NavigationContext {
public NavigationContext(Type viewModelType, object parameter = null) {
ViewModelType = viewModelType;
Parameter = parameter;
}
public Type ViewModelType { get; private set; }
public object Parameter { get; private set; }
}
public static class NavigationExtensions {
public static bool Navigate<TView>(this Frame frame) where TView : Page {
return frame.Navigate(typeof(TView));
}
public static bool Navigate<TView, TViewModel>(this Frame frame, object parameter = null) where TView : Page {
var context = new NavigationContext(typeof(TViewModel), parameter);
return frame.Navigate(typeof(TView), context);
}
}
You could configure the application like you would before at start up but the container will be used to initialize the navigation service and it will handle the rest.
public sealed partial class App {
public App() {
InitializeComponent();
Container = ConfigureServices();
Suspending += OnSuspending;
}
public static IContainer Container { get; set; }
private IContainer ConfigureServices() {
//... code removed for brevity
containerBuilder
.RegisterType<DefaultFrameProvider>()
.As<IFrameProvider>()
.SingleInstance();
containerBuilder.RegisterType<ViewModelBinder>()
.As<IViewModelBinder>()
.SingleInstance();
containerBuilder.RegisterType<AutofacServiceProvider>()
.As<IServiceProvider>()
containerBuilder.RegisterType<NavigationService>()
.AsSelf()
.As<INavigationService>();
var container = containerBuilder.Build();
return container;
}
protected override void OnLaunched(LaunchActivatedEventArgs e) {
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame == null) {
rootFrame = new Frame();
rootFrame.NavigationFailed += OnNavigationFailed;
if (e.PreviousExecutionState == ApplicationExecutionState.Terminated) {
//TODO: Load state from previously suspended application
}
// Place the frame in the current Window
Window.Current.Content = rootFrame;
}
//Activating navigation service
var service = Container.Resolve<INavigationService>();
if (e.PrelaunchActivated == false) {
if (rootFrame.Content == null) {
// When the navigation stack isn't restored navigate to the first page,
// configuring the new page by passing required information as a navigation
// parameter
rootFrame.Navigate<SomePage, SomePageViewModel>();
}
// Ensure the current window is active
Window.Current.Activate();
}
}
public class AutofacServiceProvider : IServiceProvider
public object GetService(Type serviceType) {
return App.Container.Resolve(serviceType);
}
}
//...
}
By using the above convention the Frame
extensions allow some generic navigation.
ContentFrame.Navigate<SomeOtherPage, SomeOtherPageViewModel>();
Views can be as simple as
internal sealed partial class SomePage {
public SomePage() {
InitializeComponent();
}
public SomePageViewModel ViewModel { get { return (SomePageViewModel)DataContext;} }
protected override void OnNavigatedTo(NavigationEventArgs e) {
ViewModel.LoadAsync();
base.OnNavigatedTo(e);
}
}
As the navigation service would also set the data context of the view after navigation.
Navigation can also be initiated by view models that have the INavigationService
injected
public class SomePageViewModel : ViewModel {
private readonly INavigationService navigation;
private readonly IFacade facade;
public SomePageViewModel(IFacade facade, INavigationService navigation) {
this.navigation = navigation;
this.facade = facade;
}
//...
public void GoToSomeOtherPage() {
navigation.Navigate<SomeOtherPage, SomeOtherPageViewModel>();
}
//...
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With