wpf - Where should the crud logic be implemented in mvvm? -
in mvvm light application search in customer list. search narrows customer list displayed in master/detail view datagrid (the master customersearchresultview) , separately defined usercontrol firstname, lastname, address etc, etc (the detail - customersearchdetailview). here main content of master/detail view:
<stackpanel minwidth="150" > <textblock text="customer search result list" /> <grid> <datagrid name="customerlist" itemssource="{binding searchresult}" selecteditem="{binding selectedrow, mode=twoway}" > ..... </datagrid> </grid> <grid grid.column="2"> <textblock text="customer details" style="{staticresource heading2}" margin="30,-23,0,0"/> <content:customersearchdetail datacontext="{binding selectedrow}" /> </grid> </grid> </stackpanel> both have corresponding viewmodels. please remark dc customersearchdetail, selectedrow - property on customersearchresultviewmodel , defined this:
private customer _selectedrow; ... public customer selectedrow { { return _selectedrow; } set { _selectedrow = value; raisepropertychanged("selectedrow"); } } ... because of have not defined dc on customersearchdetailview - set in binding on "master" view (as shown above) , seems work ok.
in model folder have created customer class in use here. implements observableobject , idataerrorinfo , have public properties raisepropertychanged events.
i run application , seems ok. note: viewmodel customersearchdetailview (that customersearchdetailviewmodel.cs) @ stage empty shell , not in use (as far can see ... constructor never accessed)
now want add save/update functionality customer in detail view. ok, add save button customersearchdetailview this:
<button content="save" command="{binding path = savecommand}" width="80" margin="0,0,15,0"/> i create "savecommand" relaycommand property in customersearchdetailviewmodel - never accessed.
hmmmmm ... after googling , forth come this:
<button content="save" command="{binding source={staticresource mycustdetails}, path = savecommand}" width="80" margin="0,0,15,0"/> i defined "mycustdetails" resource in view pointing customersearchdetailviewmodel. , voila! hit method when debugging ... alas, customer of course "null". (in fact spent 2 hours implementing commandparameter here , binding "selectedrow" property on master view - customer still "null").
more googling , searching mvvm examples, , implemented "savecommand" on customer class (the model object). , guess what? edited customer got passed along - send ef layer , seems ok ....
and - if still me - here comes questions:
1.) - , thought "proper mvvm way" of doing things - have crud/repository accessing in viewmodel. how can in scenario?
2.) have crud in place via model class (customer) - should bother question 1? in fact have deleted customersearchdetailviewmodel , runs ok. feel have invented view - model (mv) framework ... :-p
i feedback on - , apologize "wall of text".
assuming dc means datacontext
just opinion:
- first question doing special
selectedrowincustomersearchresultviewmodel?
if answer no, rid of property , have customsearchdetailview bind directly datagrid using {binding elementname=customerlist, path=selecteditem}
- now save / update commands need used
button's incustomersearchdetailview. instantly i'd inclined using separate vm view , have these command's defined there.
now mentioned these commands not accessed. answer because in program you're never creating customersearchdetailviewmodel.
normal operation view's datacontext it's vm(if requires one. in case imo cos need hold commands)
looking @ code i'd guess using mvvm light. in viewmodellocator have main property , in main view, got datacontext set using main property , source={staticresource locator} locator viewmodellocator created in app.xaml resources. thereby creates viewmodel view defining datacontext. can ofcourse same in code-behind let's not go off topic.
so in case got datacontext set selectedrow of type customer , binding's resolved using datacontext , that's why when command's defined in customer works fine when it's in vm did not.
so why did work when had commands in vm , used
<button content="save" command="{binding source={staticresource mycustdetails}, path = savecommand}" width="80" margin="0,0,15,0"/> ^^ worked because datacontext not used since source has been specified explicitly. , where-ever mycustdetails defined in resources, there vm got created.
so worked what's wrong that?
well it's quite big mess. mentioned customer details in vm null. hope can guess why now. it's because vm created in resources via x:key="mycustdetails" nothing in ever used or set apart when binding's referred explicitly
in system got commands refer either model plain wrong or vm created resource purpose. datacontext heavily linked "searchresults" view making not easy future extensions or layout updates.
if keep view <-> vm 1 <-> 1 relattion can avoid confusion. in summary can answer both question's together. while works, please don't let code , tweak better expansion future , comply basic guidelines.
so how that?
approach 1:
in
customersearchdetailview, add dependencyproperty of typecustomerlets callselectedcustomer.now replace
datacontext="{binding selectedrow}"selectedcustomer="{binding selectedrow}"incustomersearchresultviewnow set datacontext of
customerserachdetailviewit's vm similar howcustomerserachresultsviewlinks it's vm(guessing through datacontext binding in xaml usingviewmodellocator)now can have commands in
button's ofcustomerserachdetailview<button command="{binding savecommand}" ...finally because
selectedrowno longerdatacontextofcustomerserachdetailsview, bindings firstname, lastname, address appear stop working.
we got plenty of options address this.
first in each binding use relativesource findancestor binding pointing customerserachdetailsview , there via currentcustomer dp(dependencyproperty) created before appropriate field.
eg:
<textblock text={binding relativesource={relativesource findancestor, ancestortype={x:type local:customerdetailsview}}, path=currentcustomer.firstname}" /> now if have multiple properties gonna start getting annoying type. pick common ancestor(say 3 of these textblocks grouped under stackpanel) , apply it's datacontext currentcustomer element via similar binding ^^. stackpanel's children datacontext customer element in each of binding's don't have whole relativesource thing , can mention {binding path=firstname} , on.
that's it. got 2 view's own respective vm , model(customer) , each have respective tasks.
great, done? err not quite yet.
while approach 1 better started it's still "meh". better.
approach 2
mvvmlight has messenger class allow communicate between different classes in weak dependent format. need if haven't already.
so messenger?
pretty simple:
in setter of
selectedrowincustomersearchresultsviewmodelwe'll send message new incomingvaluecustomersearchdetailsviewmodel.now in
customersearchresultsviewmodelwe'll add propertycurrentcustomer, assign incoming value.in
customerserachdetailsviewno longer create dp. means no longer setselectedrowanything(datacontext or dp) incustomerserachdetailsviewcustomersearchresultsview( sweet less work :) )as way assign
datacontextofcustomerserachdetailsviewor way bindbutton.command- remain same approach 1finally actual "firstname" , binding's.
currentcustomerproperty ofcustomersearchdetailsviewmodel. binding how button bind's it's commands
^^ works fine cos datacontext textblock vm , property currentcustomer exists in it.
Comments
Post a Comment