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
{
privat
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()
.As()
.SingleInstance();
containerBuilder.RegisterType()
.As()
.SingleInstance();
containerBuilder.RegisterType()
.As()
.SingleInstance();
containerBuilder.RegisterType()
.As();
//...Register ViewModels as well
containerBuilder.RegisterType()
.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();
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() where TView : Page;
bool Navigate(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() where TView : Page {
return frame.Navigate(typeof(TView));
}
public bool Navigate(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 {
///
/// Get service of type from the .
///
public static TService GetService(this IServiceProvider provider) {
return (TService)provider.GetService(typeof(TService));
}
///
/// Get an enumeration of services of type from the
///
public static IEnumerable
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()
.As()
.SingleInstance();
containerBuilder.RegisterType()
.As()
.SingleInstance();
containerBuilder.RegisterType()
.As()
containerBuilder.RegisterType()
.AsSelf()
.As();
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();
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();
}
// 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();
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();
}
//...
}