In this series of posts about Caliburn Micro we’ve learned that the toolkit includes many built in helpers and services, that are automatically registered when, in the bootstrapper, you call the RegisterPhoneServices() method of the PhoneContainer class. We’ve seen many examples of these services, like NavigationService and EventAggregator. We’ve been able to get a reference to these services by simply adding a parameter in the view model’s constructor.
But what to do if we need to register your own classes and services? Let’s say that you’re developing a RSS reader application, that is able to parse the XML of a RSS feed and display it in a Windows Phone application. In this case, you may have a service (that is simply a dedicated class) that takes care of downloading the RSS and to convert it from a plain string to real objects. One approach would be to create an instance of the service directly in the ViewModel and use it, for example:
public MainPageViewModel() { IFeedService feedService = new FeedService(); // do something with the feed service }
But this approach comes with a downside. Let’s say that you need to do some testing with fake data and, as a consequence, you want to switch the FeedService class with a fake feed service, that takes the data from a local XML file instead of using the real RSS feed. In a real scenario, probably your FeedService is used across multiple view models: in this case, you will have to go in every ViewModel and change the class that is used, like this:
public MainPageViewModel() { IFeedService feedService = new FakeFeedService(); // do something with the fake feed service }
A good approach would be to use the same one that has been adopted by Caliburn Micro: services are registered at startup using the built in dependency container so that it’s enough to add a parameter in the ViewModel to have the service automatically resolved at runtime and injected into the ViewModel. This way, if we need to change the implementation of our service, we will do it just in the bootstrapper, when the services are registered: automatically every ViewModel will start to use it.
Here is how the ViewModel definition will change:
public MainPageViewModel(IFeedService feedService) { //do something with the feed service }
Register your services
Let’s see how to implement your own services and register them, so that you’ll able to use the approach I’ve explained. First you need to create an interface for your service, that describes the methods that the class will expose. This is an important step because it will allow you to easily switch the implementation of the class with another one just by changing the way it’s registered in the bootstrapper. Let’s take the previous example about the fake feed reader class: both the real and fake feed service will inherit from the IFeedService interface and, in the ViewModel constructor, we will ask for a reference to that interface. This way, when we change the implementation in the boostrapper, everything will continue to work fine.
Let’s see a sample of the interface for our FeedService class:
public interface IFeedService { Task<List<FeedItem>> GetNews(string url); }
The interface describes just one method, that we will use to get the news list from the RSS feed: as parameter, it takes the URL of the RSS feed and returns a collection of FeedItem object, that is a simple class that describes some basic properties of a news item:
public class FeedItem { public string Title { get; set; } public string Description { get; set; } public Uri Url { get; set; } }
And here is the real implementation of the FeedService class:
public class FeedService: IFeedService { public async Task<List<FeedItem>> GetNews(string url) { WebClient client = new WebClient(); string content = await client.DownloadStringTaskAsync(url); XDocument doc = XDocument.Parse(content); var result = doc.Descendants("rss").Descendants("channel").Elements("item").Select(x => new FeedItem { Title = x.Element("title").Value, Description = x.Element("description").Value }).ToList(); return result; } }
In the class we actually write the real implementation of the GetNews method: it uses the WebClient class to download the RSS file (we use the DownloadStringTaskAsync() method that has been added by installing the Async pack using NuGet) and then, thanks to LINQ to XML, we extract the information we’re looking for (the title and the description of the news) and we store them in a new FeedItem object. At the end of the process, we have a collection of FeedItem objects: each one of them contains a news that was stored in the RSS file.
Now it’s time to register our service, so that it can be used by our ViewModel. The registration is made in the bootstrapper and you should be already familiar with it, since we’ve learned to register our view models every time we have added a new page to our project. We do it in the Configure() method of the bootstrapper class:
protected override void Configure() { container = new PhoneContainer(RootFrame); container.RegisterPhoneServices(); container.PerRequest<MainPageViewModel>(); container.PerRequest<IFeedService, FeedService>(); AddCustomConventions(); }
We can see a difference: since our FeedService has an interface, we need to register it in a different way than we did for the MainPageViewModel. In fact, we have to tell to the container that, every time a ViewModel requires an IFeedService object, we want to provide a FeedService implementation. For this reason, the container exposes a PerRequest<T, Y> overload, where T is the base interface and Y is the real implementation of the interface.
Now we are able to just use it in the view model of our page, to display the list of news. Here is a sample XAML of the page:
<StackPanel> <Button Content="Load website" x:Name="LoadWebsite"></Button> <ListBox x:Name="News"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel> <TextBlock Text="{Binding Path=Title}"></TextBlock> <TextBlock Text="{Binding Path=Description}"></TextBlock> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </StackPanel>
We have a button, that will execute the LoadWebsite method of the ViewModel (that will use our service to load the data), and we have a ListBox, which ItemTemplate simply displays the tile and the description of the news, one below the other.
And here is the ViewModel:
public class MainPageViewModel: Screen { private readonly IFeedService feedService; private List<FeedItem> news; public List<FeedItem> News { get { return news; } set { news = value; NotifyOfPropertyChange(() => News); } } public MainPageViewModel(IFeedService feedService) { this.feedService = feedService; } public async void LoadWebsite() { News = await feedService.GetNews("http://feeds.feedburner.com/qmatteoq_eng"); } }
Nothing special here, except that we’ve added in the constructor a parameter which type is IFeedService: since we’ve registered it in the boostrapper, the parameter will contain a FeedService object, that we can use in the LoadWebsite() method to get the list of news using the GetNews() method we’ve defined in the service. This sample, we are parsing the RSS feed of this blog.
Use your own service to pass data between pages
When we talked about navigation we learned that Caliburn exposes a way to pass data between pages, that relies on the query string parameter that are supported by the OS. The problem of this approach is that, since we’re talking about parameters that are added to the navigation url, we can only send text parameters, like strings and numbers. In most of the cases, instead, we need to pass complex object. Take as example the RSS reader app we’ve just built: we want to implement a detail page, that displays all the information about the news selected by the user. In this case, when we navigate to the detail page, we want to carry the whole FeedItem object that has been selected.
One approach is to use your own service to store the data and pass it to every ViewModel that needs the information, exactly like we did for the FeedService. Here is a sample of a DataService class:
public class DataService { public FeedItem SelectedItem { get; set; } }
As you can see it’s really simple, since it will be used just to store the FeedItem object that has been selected by the user in the ListBox.
Now we need to register it in the boostrapper:
protected override void Configure() { container = new PhoneContainer(RootFrame); container.RegisterPhoneServices(); container.PerRequest<MainPageViewModel>(); container.PerRequest<DetailPageViewModel>(); container.PerRequest<IFeedService, FeedService>(); container.Singleton<DataService>(); AddCustomConventions(); }
And here comes something new: we’re not registering it using the familiar PerRequest<T> method, but with the Singleton<T> method exposed by the PhoneContainer class. Which is the difference? When a class is registered using the PerRequest<T> method every time a ViewModel asks for a reference, it gets a new instance of the object. It’s the best approach when we’re dealing with view models: think about the DetailPageViewModel we’ve registered, that is connected to the page that displays the details of the selected news. In this case, every detail page will be different, because we’ll have to display a different news: by creating a new instance of the view model every time the user navigates to the detail page we make sure that the fresh data is correctly loaded.
This is not the case for our DataService: in this case we need to maintain a single instance across the application, because we want to take back and forth the FeedItem object selected by the user. If we would have registered it using the PerRequest<T> method, we would have lost the value stored in the SelectedItem property as soon as the user navigates away from the main page. The answer is using the Singleton<T> method: this way we’ll always get the same object in return when a ViewModel asks for it.
Now we just need to add a parameter which type is DataService in the constructor of both our view models: the main page one and the detail page one.
public class MainPageViewModel: Screen { private readonly IFeedService feedService; private readonly INavigationService navigationService; private readonly DataService dataService; private List<FeedItem> news; public List<FeedItem> News { get { return news; } set { news = value; NotifyOfPropertyChange(() => News); } } private FeedItem selectedNew; public FeedItem SelectedNew { get { return selectedNew; } set { selectedNew = value; dataService.SelectedItem = value; navigationService.UriFor<DetailPageViewModel>().Navigate(); NotifyOfPropertyChange(() => SelectedNew); } } public MainPageViewModel(IFeedService feedService, INavigationService navigationService, DataService dataService) { this.feedService = feedService; this.navigationService = navigationService; this.dataService = dataService; } public async void LoadWebsite() { News = await feedService.GetNews("http://feeds.feedburner.com/qmatteoq_eng"); } }
We’ve added a property called SelectedNew: if you remember what I’ve explained in this post, you’ll know that by using this naming convention we are able to get automatically, in the SelectedNew property, the item selected by the user in the ListBox that is connected to the News collection.
Inside the setter of this property we do two additional things: the first one is to store the selected value in the SelectedItem property of the DataService class. The second is to redirect the user to the detail page, using the built in NavigationService.
What about the second page? The view it’s very simple, since it’s used just to display the Title and Description properties of the selected item.
<StackPanel> <TextBlock x:Name="Title" /> <TextBlock x:Name="Description" /> </StackPanel>
And the ViewModel is really simple too:
public class DetailPageViewModel: Screen { private string title; public string Title { get { return title; } set { title = value; NotifyOfPropertyChange(() => Title); } } private string description; public string Description { get { return description; } set { description = value; NotifyOfPropertyChange(() => Description); } } public DetailPageViewModel(DataService dataService) { Title = dataService.SelectedItem.Title; Description = dataService.SelectedItem.Description; } }
We add a parameter in the constructor which type is DataService: this way the bootstrapper will give us the correct instance that, since has been registered as singleton, will be the same that was used by the MainPageViewModel. This way, the SelectedItem property of the DataService will contain the item selected by the user in the main page: we simply use it to set the Title and Description properties, so that they are displayed in the page.
That’s all for this topic! But don’t be afraid, there are some other subjects to talk about in the next posts
The Caliburn Micro posts series
Very nice as all your posts about caliburn.
It would be great if you could explain how to handle the scenario with different service implementations for the same interface in the bootstrapper. E.g. if you have one real implementation and one with fake data for tests. How do I inject these into main ViewModel? I can’t get that to work.
Hi, if you see the code samples I register my own service in the boostrapper using the following method:
container.PerRequest();
you just have to replace FeedService with your fake implementation. This way, since in all your ViewModels you’ve added a reference to the IFeedService interface, they will be injected with the fake implementation instead of the real one.
For example:();
container.PerRequest
But how can I do this for example. I have one interface for file services. But I have three implementations and want to inject these implementations into the appropriate views.
E.g.
container.PerRequest();
container.PerRequest();
container.PerRequest();
Now I have three ViewModel and want to inject an interface implementations in each of the ViewModels. But in the constrcutor I specify only the interface.
I cannot see how to do this.
Hi, I don’t think your scenario is supported, at least if you want to automatically inject the implementation. Using the RegisterInstance() method of the container you’re able to register multiple instances of the same class or interface, but then you’ll have to resolve it manually using the GetInstance() method of the bootstrapper, otherwise Caliburn will simply take the first one.
If, for example, in your bootstrapper you have something like this:
container.RegisterInstance(typeof (PersistHelper), "PersistHelper", new PersistHelper("Sessions.xml"));
container.RegisterInstance(typeof(PersistHelper), "PersistHelper2", new PersistHelper("Sessions2.xml"));
and you have a ViewModel with a parameter in the constructor which type is PersistHelper, the instance that will be injected is the first one, since it’s the first that have been registered.
Otherwise, you should have, in your ViewModel, to do something like:
container.GetInstance(typeof(PersistHelper), “PersistHelper2”)
This way you are able to retrieve a specific implementation.
What a great post. Thank you so much!
Thanks a lot 🙂
Matteo, thank you for the article. Quick question. What is the best way to pass data back from the child page (DetailPageViewModel) to the parent page (MainPageViewModel)? I’m thinking about a global variable in the App class or using of EventAggregator. Neither of these approaches I like. Please suggest.
Hi Andrei, an approach that I typically use is to create a class to hold these data, that I register as a singleton in the dependency container of the project. This way, since it’s registered as a singleton, the values are kept until the app is closed or suspended.