Post on 16-Apr-2017
1
Denis Voituron.Net Software Architect
MVVMPrésentation et bonnes pratiques
dvoituron@outlook.com www.dvoituron.be @denisvoituron
2
About meDenis Voituron
Civil engineer (Mons)Company founderDeveloper: VB3, VB.Net, C#.Net Software Architect (Trasys)BloggerSpeaker (DevApps.be)
www.dvoituron.be
3
Agenda• Introduction• Getting Started
• Model• View• ViewModelBase
• Services and DesignServices• Localization• Commands• Messenger• Unit Tests and TestServices
4
Introduction
5
Model
Pattern
ViewModelViewBinding
Methods callMethods call
Events
Database
6
Pattern
ModelBusiness Logic
ViewPresentation UI
ViewModel
Presentation Logic
CommandBinding
Method call
Event
MVVMMODEL VIEW VIEWMODEL
7
Pattern
ModelBusiness Logic
ViewPresentation UI
ViewModel
Presentation Logic
CommandBinding
Method call
Event
Friend
FirstNameLastNameDateOfBirthPictureUrl
ObservableObject
FriendPage<Page DataContext="{Binding ViewModel}"> <TextBlock Text="{Binding FullName}" /> <TextBlock Text="{Binding Age}" /> <Image Source="{Binding Photo}" /></Page>
FriendViewModel
FullNameAgePhoto
ViewModelBase
8
Why?• Code behind is not always bad,
but can complicate things.• MVVM is a variation of MVC.• The goal is to decouple
the View from the Model.• Easier to maintain.• Easier to test• Allow to create design time data.
@LBugnion
9
MVVM Light v5Toolkit to help MVVM developments
Windows Presentation Foundation (3.5, 4, 4.5, 4.5.1)Silverlight (4 and 5)Windows Phone (7.1, 8, 8.1 Silverlight, 8.1 RT)Windows Store (8, 8.1)Universal App Plateform (UAP – Windows 10)Xamarin AndroidXamarin iOSXamarin Forms
Open Source project
Supported by Microsoft
http://www.mvvmlight.net
10
11
Getting Started
12
Project File StructureImages and icons.Reusable UI controls, without view models.
Classes and resources.for application localization.
Model and domain classes.(For bigger project, move it to a separate project)
View models classes.(For bigger project, move it to a separate project)
Contains the views.Including a ‘Dialogs’ folder if necessary.With suffix (Window, Dialog, Page or View).Entry point.
13
Modelpublic class Friend{ public int ID { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Picture { get; set; } public string Location { get; set; } public string Message { get; set; }}
public class Friend : ObservableObject{ private string _firstName = String.Empty; public string FirstName { get { return _firstName; } set { Set(() => this.FirstName, ref _firstName, value); } }
Model
14
Modelpublic interface IDataService{ Task<Friend[]> GetFriendsAsync();}
public class DataService : IDataService{ public async Task<Friend[]> GetFriendsAsync() { ... }}public class DesignDataService : IDataService{ public async Task<Friend[]> GetFriendsAsync() { ... }}
Model
15
IoC – Inversion of Controlpublic class MainViewModel{ public MainViewModel() { _dataService = new DataService(); }}
public class MainViewModel{ public MainViewModel(IDataService dataservice) { _dataService = dataservice; }}
DataService for Test, for Production, for Design, …
• An IoC container is• Responsible to create services when needed.• Responsible for injecting them.• Responsible for caching the objects.• And providing access to them.
16
ViewModelLocatorpublic class ViewModelLocator{ public ViewModelLocator() { // SimpleIoC ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
// Models if (ViewModelBase.IsInDesignModeStatic) SimpleIoc.Default.Register<IDataService, DataService>(); else SimpleIoc.Default.Register<IDataService, DesignDataService>();
// ViewModels SimpleIoc.Default.Register<ViewModels.MainViewModel>(); }}
ViewMode
l
<Application xmlns:locator="using:SampleMvvmLight.ViewModels" ... > <Application.Resources> <locator:ViewModelLocator x:Key="ViewModelLocator" /> </Application.Resources></Application>
17
public abstract class ViewModelBase : GalaSoft.MvvmLight.ViewModelBase{ // Default constructor used by the Design or Production Mode public ViewModelBase() : this(ServiceLocator.Current.GetInstance<Models.IDataService>(), ServiceLocator.Current.GetInstance<IDialogService>(), ServiceLocator.Current.GetInstance<INavigationService>()) { if (ViewModelBase.IsInDesignModeStatic) OnLoadedAsync(); }
// Default constructor with all usable services protected ViewModelBase(IDataService dataservice, IDialogService dialogService, INavigationService navigationService) { this.DateService = dataservice; this.DialogService = dialogService; this.NavigationService = navigationService; }}
ViewModelBase ViewMode
l
18
NotifyPropertyChanged
Snippet ‘mvvminpcsetlambda’
ViewMode
l
public class MainViewModel : ViewModelBase{ protected async override Task OnLoadedAsync() { this.Friends = await this.DateService.GetFriendsAsync(); }
private Friend[] _friends = null; public Friend[] Friends { get { return _friends; } set { Set(() => Friends, ref _friends, value); } }}
19
private ViewModels.MainViewModel ViewModel{ get { return ((MainViewModel)Resources["ViewModel"]); }}
View View
<Page xmlns:vm="using:SampleMvvmLight.ViewModels">
<!-- Create a new instance of the associated ViewModel --> <Page.Resources> <vm:MainViewModel x:Key="ViewModel" /> </Page.Resources>
<!-- Content --> <Grid DataContext="{StaticResource ViewModel}"> <ListView Margin="10,33,10,10" ItemsSource="{Binding Friends}" /> </Grid>
</Page>
20
Best practicesRules for views and view models
• Separate the user interface from the application logic.• Always a one-to-one relationship between the view and
view model.• Not allowed to directly access the view from the view
model.• If a view becomes too large, split it up into multiple views.• Use a ViewModelBase class to simplify implementations.
User controls vs templated controls
• User control when implementing an application specific view
• Templated control when implementing a reusable UI control
@ricosuter
21
Best practicesView model instantiation
• View model should be instantiated in XAML.
• NOT set the DataContext in the XAML code where the view (User Control) is instantiated
• To pass parameters to a view, always use dependency properties
• Handling the view model life cycle
<MySubView DataContext="{Binding MySubViewModel}" />
<MySubView Project="{Binding SelectedProject}" />
public MyView(){ InitializeComponent(); Loaded += async delegate { await ViewModel.OnLoaded(); }; Unloaded += async delegate { await ViewModel.OnUnloaded(); };}
22
Services
23
Data ServiceInterface for testing, designing and running
Sample
public interface IDataService{ Task<Friend[]> GetFriendsAsync();}
public class DataService : IDataService{ const string UrlBase = "http://xxx.azurewebsites.net/friends.aspx";
public async Task<Friend[]> GetFriendsAsync() { var client = new HttpClient(); string json = await client.GetStringAsync(new Uri(UrlBase));
var result = JsonConvert.DeserializeObject<ListOfFriends>(json); return result.Data.ToArray(); }
24
Dialog Service
SimpleIoc.Default.Register<IDialogService, DialogService>();
await this.DialogService.ShowMessage ("My message", "My title");
await this.DialogService.ShowMessageBox("My message", "My title");
await this.DialogService.ShowError(MyException, ...);
25
Navigation Serviceprivate INavigationService CreateNavigationService(){ var navigationService = new NavigationService(); navigationService.Configure(MAIN_PAGE, typeof(MainPage)); navigationService.Configure(DETAIL_PAGE, typeof(DetailPage)); return navigationService;}
...
SimpleIoc.Default.Register<INavigationService>(() => CreateNavigationService());
public class NavigationService : INavigationService{ public void NavigateTo<T>(string pageKey, T parameter) { ... }
public void GoBack() { ... }}
26
Navigation ServiceNavigate to the second page
Retrieve parameters
public class FirstViewModel : ViewModelBase{ ... this.NavigationService.NavigateTo<string>(ViewModelLocator.DETAIL_PAGE, "ABC");}
public class SecondViewModel : ViewModelBase{ public DetailViewModel() { this.NavigationRegistering<int>(); }
protected async override Task OnNavigationFrom(object parameter) { this.Friend = await this.DateService.GetFriendAsync((int)parameter); }}
27
LocalizationResource files• Resw
Need a T4 file
• ResxConfigurable
MyResources.Culture = new CultureInfo("fr");
CultureInfo.CurrentCulture = new CultureInfo("fr");
28
LocalizationThe following prefixes or postfixes are recommended:(bigger projects, the keys should start with a module name. e.g. ‘Search_ButtonOK’)
• Label*: Labels which belong to an input control• Button*: Button content texts• Message*Title: Titles for message boxes• Message*Content: Content messages for message boxes• Log*: Log messages• Header*: Header texts to use in window titles, tab
headers and similar locations
• GridHeader*: Data grid headers• Menu*: Strings to use in menu items (e.g. context
menu)• RibbonHeader*: Labels used in the ribbon tab• RibbonButton*: Headers for ribbon buttons
@ricosuter
29
Commands
30
Relay CommandCommands are instantiated in constructor.
• Name "xxxCommand"• "xxxExecute" and "CanXxxExecute"
public MainViewModel(){ DisplayDetailCommand = new RelayCommand<Friend> (DisplayDetailExecute, CanDisplayDetailExecute);}
public RelayCommand<Friend> DisplayDetailCommand { get; private set; }
public void DisplayDetailExecute(Friend parameter){ ...}
public bool CanDisplayDetailExecute(Friend parameter){ return true;}
<Button Command="{Binding DisplayDetailCommand}" CommandParameter="{Binding SelectedFriend}" />
Snippet ‘mvvmrelaymethodcanexecute’
31
Messenger
32
What does the Messenger do?• It is an "event bus".
• A message distribution system.• One object broadcasts a message.• Other objects register to receive these messages.• The sender doesn’t know who receives the messages.• The receiver doesn’t know who sent the messages.
• One default instance (Messenger.Default)
@LBugnion
33
MessengerMessenger.Default.Send<string>("Hello World");
Messenger.Default.Register<string> ( this, (message) => { ... } );
Messenger.Default.Unregister<string>(this);
34
Unit Tests
35
Test Helpers• TestDataService• TestDialogService• TestNavigationService
• TestServiceRegisterpublic static void Registering(){ ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<IDataService, TestDataService>(); SimpleIoc.Default.Register<IDialogService, TestDialogService>(); SimpleIoc.Default.Register<INavigationService, TestNavigationService>();}
36
Unit Test• Initialization
• Test
[TestClass]public class MainViewModelTests{ [TestInitialize] public void Initialize() { TestServiceRegister.Registering(); }}
[TestMethod]public async Task ComputeNumberOfFriends(){ MainViewModel main = new MainViewModel(); await main.CallOnLoaded();
Assert.AreEqual(3, main.Friends.Length);}
37
Conclusion
38
Conclusion• MVVM Pattern• Project File Structure• Rules for views and view models• View model instantiation
ViewModelBase
• Services and DesignServicesDataServiceDialogServiceNavigationService
• Localization• Commands• Messenger• Unit Tests and TestServices
39
Ressources• http://www.mvvmlight.net
• http://mvvmlight.codeplex.com
• https://www.pluralsight.com/courses/mvvm-light-toolkit-fundamentals
• http://blog.rsuter.com/recommendations-best-practices-implementing-mvvm-xaml-net-applications
• https://blog.rsuter.com/winrt-access-string-resources-in-a-stongly-typed-way-c