Closed will-vertigo closed 8 years ago
I verified that Autofac works as a replacement for SimpleIoc, so there is definitely an issue with SimpleInjector.
Can you describe in more detail how it fails at design time. Please post the exception and stack trace.
Since this is design-time I cannot debug and there is no human-readable exception or stack trace to give other than the AppServices line in App.xaml (shown above) has a red squiggle underneath and if you hover it says something about a failed HCOMResult. The views open up blank in the designer . My guess is that the container fails to build in the static ctor for some reason.
It is worth mentioning that I'm using the very latest builds of Windows 10 and VS 2015.
It is also possible with SimpleInjector. I just tested this using VS2015 on Windows 10 in a WPF application. Although I did not use MVVM light, it shouldn't make any difference. Not completely sure why you got this to work with other IOC containers and not with SimpleInjector, because I think I didn't do anything out of the ordinary to get this to work.
It is offcourse a POC project, so the setup is pretty simple. The code...
First of all, start the application from Main to get rid of all WPF magic:
class Program
{
[STAThread]
static void Main()
{
var container = Bootstrap(isInDesignMode: false);
RunApplication(container);
}
private static void RunApplication(Container container)
{
try
{
var app = new App();
var mainWindow = container.GetInstance<MainWindow>();
app.Run(mainWindow);
}
catch (Exception ex)
{
// log message or something
throw;
}
}
public static Container Bootstrap(bool isInDesignMode)
{
var container = new Container();
if (isInDesignMode)
{
container.RegisterSingleton<IUserRepository, DesignUserRepository>();
}
else
{
container.RegisterSingleton<IUserRepository, SqlUserRepository>();
}
container.Register<MainWindow>();
container.Register<MainWindowViewModel>();
container.Verify();
return container;
}
}
Notice that I'm telling the Bootstrapper class if it needs to register dependencies for DesignMode or not. In the normal flow is should be false
offcourse.
Secondly I added a ViewModelLocator
class which is responsible for resolving the ViewModels at designtime.
public class ViewModelLocator
{
private Container container;
public ViewModelLocator()
{
this.container = Program.Bootstrap(isInDesignMode: true);
}
public MainWindowViewModel MainWindowViewModel
{
get { return this.container.GetInstance<MainWindowViewModel>(); }
}
}
In this case I create the container with dependencies needed for usage at DesignTime by supplying true
to the isInDesignMode
. The ViewModelLocator
is created in the app.xaml
by added it as a resource:
<Application.Resources>
<local:ViewModelLocator x:Key="ViewModelLocator"/>
</Application.Resources>
You can now bind to this instance properties for the DesignTimeDataContext
as follows:
<Window x:Class="SIDesignTimeDataContextInWpf.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SIDesignTimeDataContextInWpf"
mc:Ignorable="d"
d:DataContext="{Binding MainWindowViewModel, Source={StaticResource ViewModelLocator}}"
>
<Grid>
<TextBox Text="{Binding Path=User, Mode=OneWay}" />
</Grid>
</Window>
For the sake of completeness, the implementations of the MainWindow
code behind, MainWindowViewModel
and DesignUserRepository
.
public partial class MainWindow : Window
{
public MainWindow(MainWindowViewModel viewModel)
{
InitializeComponent();
this.DataContext = viewModel;
}
}
public class MainWindowViewModel
{
private IUserRepository repository;
public MainWindowViewModel(IUserRepository repository)
{
this.repository = repository;
}
public string User
{
get { return this.repository.UserName; }
}
}
internal class DesignUserRepository : IUserRepository
{
public string UserName { get { return "DesignTimeUser is here"; } }
}
But a word of advice: First off all, the ViewModelLocator class will grow rapidly. Or you need to find a way to do this in smarter way using an IEnumerable or Dictionary.
Secondly, instead of using the container to build your mocked ViewModels, just make these ViewModels plain and simple and implement only the properties which need to be bound. These implementations would be typically very small considering that creating a SOLID design which would lead to small ViewModels. Such a viewmodel can be bound to the view far easier at designtime using a DesignInstance like this:
d:DataContext="{d:DesignInstance local:YourMockedViewModel, IsDesignTimeCreatable=True}"
Last but not least. You can leave the design time support all together and since your already using a MVVM toolkit to bind your controls using Convention Over Configuration the only thing that is missing is writing a unit test which checks if all properties are actually in the viewmodel and thus can bind at runtme. This will provide a great feeling of flexibility, speed of development and still ensures all binding will be there at runtime. How the view will look at runtime is missing in this setup, but from my experience it is hardly ever possibly to create testdata that will actually mimic the data you get to show in production.
That is all well and good and SimpleInjector may work fine in WPF, but we are essentially doing the same thing as far as building up the container, however the original problem still remains.
Maybe my post was not clear that this is specifically a problem with Windows 10 apps (and more than likely Win 8/8.1). As you know these apps are quite different from WPF.
Indeed that was unclear.
I never used universal apps, so I'm not an expert at all at this, but I just tested this with the exact same setup and it works just the same!
Only adjustment made is that I'm not using the void main as entry point as it seems this is impossible for universal development.
And for some reason we (us developers) are back to square one because the page behind does not support constructor arguments. It seems there is no way to let an IoC container create the page for you using the promoted Frame.Navigate()
method. But this is lack of experience on my part, you probably use MVVM light to set the datacontext and let MVVM call back into your container to create only the viewmodel.
Conclusion I have a lot to learn about Universal Apps AND: the same setup as above works also in Universal Apps....!
Did you follow my exact setup?
@will-vertigo Did you have any luck in fixing this issue. After rereading I found 2 major differences in your setup and mine:
DataContext
property instead of the DesignTimeDataContext
. Using the DataContext
at designtime is not supported IMO. So I think the main difference is that MVVM light does some magic under the covers to redirect the DataContext
to the DesignTimeDataContext
when designing.
Say I'm using MvvmLight, which is a Model-View-ViewModel framework commonly used by C#/XAML/WPF/Win10 apps. I can use MvvmLight's built-in "SimpleIoc" container at both run-time and design-time. If I replace SimpleIoc with SimpleInjector, then my app runs perfectly at run-time, but fails at design-time in the VS designer.
FYI, I typically create an "AppServices" class which builds up my IOC container in a static constructor and exposes my services and view models as instance properties. This class gets created as an application resource, so it's accessible by my views at design-time.
The AppServices class gets created in App.xaml:
I can then access my view models from my view (e.g. HomePage.xaml):