c# - Should I use additional container in my MVVM project? -
source sample project: https://github.com/antwanreno/navi
i did project in wpf mvvm. has 3 sub-projects: wpf app, viewmodels (pcl) , domain (pcl). wpf single window
single frame
, 2 pages
. present code, recommend clone/fork prepared sample in repo.
here code wpf client:
app.xaml.cs
namespace naviwpfapp { using system.windows; using naviwpfapp.views; using naviwpfapp.views.pages; public partial class app : application { public static navigationservice navigation; protected override void onstartup(startupeventargs e) { base.onstartup(e); mainwindow mainwindow = new mainwindow(); mainwindow.show(); navigation = new navigationservice(mainwindow.myframe); navigation.navigate<firstpage>(); } } }
app.xaml it's just:
<application x:class="naviwpfapp.app" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:naviwpfapp" x:name="application"> <application.resources> <local:viewmodellocator x:key="viewmodellocator"/> </application.resources> </application>
i have 1 main window frame, , 2 similar pages (no code-behind):
<window x:class="naviwpfapp.views.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" mc:ignorable="d" title="naviwpfapp" height="300" width="300"> <grid> <frame x:name="myframe" margin="10" /> </grid> </window> <page x:class="naviwpfapp.views.firstpage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:ignorable="d" d:designheight="300" d:designwidth="300" title="firstpage" datacontext="{binding firstpageviewmodel, source={staticresource viewmodellocator}}"> <grid> <button command="{binding gotosecondpagecommand}" height="30" content="go second page" /> </grid> </page> <page x:class="naviwpfapp.views.secondpage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:ignorable="d" d:designheight="300" d:designwidth="300" title="secondpage" datacontext="{binding path=secondpageviewmodel, source={staticresource viewmodellocator}}"> <stackpanel margin="0, 100, 0, 0"> <button command="{binding countsomethingcommand}" content="count something" height="30" /> <button command="{binding backtofirstpagecommand}" content="go page 1" height="30" /> </stackpanel> </page>
i client have 2 additional classes viewmodellocator , navigationservice - used naviation between pages:
namespace naviwpfapp { using naviwpfapp.viewmodels.pages; public class viewmodellocator { public firstpageviewmodel firstpageviewmodel => new firstpageviewmodel(app.navigation); public secondpageviewmodel secondpageviewmodel => new secondpageviewmodel(app.navigation); } } namespace naviwpfapp { using system; using system.linq; using system.reflection; using system.windows.controls; using naviwpfapp.viewmodels.common; public class navigationservice : inavigationservice { readonly frame frame; public navigationservice(frame frame) { this.frame = frame; } public void goback() { frame.goback(); } public void goforward() { frame.goforward(); } public bool navigate(string page) { var type = assembly.getexecutingassembly().gettypes().singleordefault(a => a.name.equals(page)); if (type == null) return false; var src = activator.createinstance(type); return frame.navigate(src); } public bool navigate<t>(object parameter = null) { var type = typeof(t); return navigate(type, parameter); } public bool navigate(type source, object parameter = null) { var src = activator.createinstance(source); return frame.navigate(src, parameter); } } }
here viewmodels (portable) project:
it's 2 viewmodel classes each page in ui, inavigationservice (i don't want know navigationservice implementation , ui client), myobservableobject , mycommand.
myobservableobject
, mycommand
typical implementations of inotifypropertychanged
, icommand
interfaces.
so interface , 2 viewmodels:
public interface inavigationservice { void goforward(); void goback(); bool navigate(string page); } namespace naviwpfapp.viewmodels { public class firstpageviewmodel : myobservableobject { private readonly inavigationservice navigationservice; public firstpageviewmodel(inavigationservice navigationservice) { this.navigationservice = navigationservice; } public mycommand gotosecondpagecommand { { return new mycommand(x => navigationservice.navigate("secondpage")); } } } } namespace naviwpfapp.viewmodels { public class secondpageviewmodel : myobservableobject { private readonly inavigationservice navigationservice; private readonly businesslogic businesslogic; public secondpageviewmodel(inavigationservice navigationservice, businesslogic businesslogic = null) { this.navigationservice = navigationservice; this.businesslogic = businesslogic; } public mycommand backtofirstpagecommand { { return new mycommand(x => navigationservice.navigate("firstpage")); } } public mycommand countsomethingcommand { { return new mycommand(x => businesslogic?.countsomething()); } } } }
and business logic, this:
public class businesslogic { private int counter = 0; public bool countsomething() { return ++counter > 10; } }
dependencies simple: domain knows nothing, besides own operations, viewmodel knows domain, nothing view , view... well, here problem - knows viewmodel, should view know domain? explain concerns, here meant:
first concern: can see, navigation done in viewmodel, , business logic used in second page view model. secondpage view doesn't need know logic.
second concern: because i'm trying stick dependency injection, want create domain object (which need one) @ beginning of program. in protected override void onstartup(startupeventargs e)
, in view. , have no idea, how pass second view model, created in viewmodellocator
.
so question is: how convert code, more viewmodel orientated? want inject domain object viewmodel (where belongs), not view.
thanks advice!
you can't aviod dependency because app.onstartup
composition root, means app.onstartup
knows everything. but, can avoid global prop in app: public static navigationservice navigation;
. can inject object need it.
first concern: can see, navigation done in viewmodel, , business logic used in second page view model. secondpage view doesn't need know logic.
secondpage doen't have know business object. app must know. can inject object locator , locator can inject object specific viewmodel when time comes.
second concern: because i'm trying stick dependency injection, want create domain object (which need one) @ beginning of program. in protected override void onstartup(startupeventargs e), in view. , have no idea, how pass second view model, created in viewmodellocator.
dependency injection.
here's how i'd it:
public class viewmodellocator { private navigationservice navigationservice; private businesslogic businesslogic; public void injectnavigationservice(navigationservice navigation) { navigationservice = navigation; } public void injectbusinesslogic(businesslogic logic) { businesslogic = logic; } public firstpageviewmodel firstpageviewmodel => new firstpageviewmodel(navigationservice); public secondpageviewmodel secondpageviewmodel => new secondpageviewmodel(navigationservice, businesslogic); } public partial class app : application { protected override void onstartup(startupeventargs e) { base.onstartup(e); // create/resolve objects in comoposition root: var businesslogic = new businesslogic(); // here have locator created already, mainwindow has not been created yet // retrive locator viewmodellocator locator = resources.values.oftype<viewmodellocator>().firstordefault(); if (locator == null) throw new nonullallowedexception("viewmodellocator cannot null."); mainwindow mainwindow = new mainwindow(); var navigation = new navigationservice(mainwindow.myframe); // inject logic , navigation locator locator.injectbusinesslogic(businesslogic); locator.injectnavigationservice(navigation); // set first page navigation.navigate<firstpage>(); // , show window mainwindow.show(); } }
Comments
Post a Comment