您的位置:首页 > 编程语言 > ASP

【转】【英文】ASP.NET MVC View Model Patterns

2010-09-04 09:07 429 查看
SinceMVChasbeenreleasedIhaveobservedmuchconfusionabouthowbesttoconstructviewmodels.Sometimesthisconfusionisnotwithoutgoodreasonsincetheredoesnotseemtobeatonofinformationoutthereonbestpracticerecommendations.Additionally,thereisnota“onesizefitsall”solutionthatactsasthesilverbullet.Inthispost,I’lldescribeafewofthemainpatternsthathaveemergedandthepros/consofeach.Itisimportanttonotethatmanyofthesepatternshaveemergedfrompeoplesolvingreal-worldissues.

Anotherkeypointtorecognizeisthatthequestionofhowbesttoconstructviewmodelsis*not*uniquetotheMVCframework.ThefactisthatevenintraditionalASP.NETwebformsyouhavethesameissues.Thedifferenceisthathistoricallydevelopershaven’talwaysdealtwithitdirectlyinwebforms–insteadwhatoftenhappensisthatthecode-behindfilesendupasmonolithicdumpinggroundsforcodethathasnoseparationofconcernswhatsoeverandiswiringupviewmodels,performingpresentationlogic,performingbusinesslogic,dataaccesscode,andwhoknowswhatelse.MVCatleastfacilitatesthedevelopertakingacloserlookathowtomoreelegantlyimplementSeparationofConcerns.

Pattern1–Domainmodelobjectuseddirectlyastheviewmodel

Consideradomainmodelthatlookslikethis:

publicclassMotorcycle

[code]{
publicstringMake{get;set;}

publicstringModel{get;set;}

publicintYear{get;set;}

publicstringVIN{get;set;}

}

[/code]
Whenwepassthisintotheview,itofcourseallowsustowritesimpleHTMLhelpersinthestyleofourchoosing:

<%=Html.TextBox("Make")%>

[code]<%=Html.TextBoxFor(m=>m.Make)%>
[/code]
Andofcoursewithdefaultmodelbindingweareabletopassthatbacktothecontrollerwhentheformisposted:

publicActionResultSave(Motorcyclemotorcycle)


Whilethisfirstpatternissimpleandcleanandelegant,itbreaksdownfairlyquicklyforanythingbutthemosttrivialviews.Wearebindingdirectlytoourdomainmodelinthisinstance–thisoftenisnotsufficientforfullydisplayingaview.

Pattern2–Dedicatedviewmodelthat*contains*thedomainmodelobject

StayingwiththeMotorcycleexampleabove,amuchmorereal-worldexampleisthatourviewneedsmorethanjustaMotorcycleobjecttodisplayproperly.Forexample,theMakeandModelwillprobablybepopulatedfromdropdownlists.Therefore,acommonpatternistointroduceaviewmodelthatactsasacontainerforallobjectsthatourviewrequiresinordertorenderproperly:

publicclassMotorcycleViewModel

[code]{
publicMotorcycleMotorcycle{get;set;}

publicSelectListMakeList{get;set;}

publicSelectListModelList{get;set;}

}

[/code]
Inthisinstance,thecontrolleristypicallyresponsibleformakingsureMotorcycleViewModeliscorrectlypopulatedfromtheappropriatedataintherepositories(e.g.,gettingtheMotorcyclefromthedatabase,gettingthecollectionsofMakes/Modelsfromthedatabase).OurHtmlHelperschangeslightlybecausetheyrefertoMotorcycle.MakeratherthanMakedirectly:

<%=Html.DropDownListFor(m=>m.Motorcycle.Make,Model.MakeList)%>


Whentheformisposted,wearestillabletohaveastrongly-typedSave()method:

publicActionResultSave([Bind(Prefix="Motorcycle")]Motorcyclemotorcycle)


NotethatinthisinstancewehadtousetheBindattributedesignating“Motorcycle”astheprefixtotheHTMLelementswewereinterestedin(i.e.,theonesthatmadeuptheMotorcycleobject).

Thispatternissimpleandelegantandappropriateinmanysituations.However,asviewsbecomemorecomplicated,italsostartstobreakdownsincethereisoftenanimpedancemismatchbetweendomainmodelobjectsandviewmodelobjects.

Pattern3–Dedicatedviewmodelthatcontainsacustomviewmodelentity

Asviewsgetmorecomplicateditisoftendifficulttokeepthedomainmodelobjectinsyncwithconcernsoftheviews.Inkeepingwiththeexampleabove,supposewehadrequirementswhereweneedtopresenttheuseracheckboxattheendofthescreeniftheywanttoaddanothermotorcycle.Whentheformisposted,thecontrollerneedstomakeadeterminationbasedonthisvaluetodeterminewhichviewtoshownext.Thelastthingwewanttodoistoaddthispropertytoourdomainmodelsincethisisstrictlyapresentationconcern.Insteadwecancreateacustom“viewmodelentity”insteadofpassingtheactualMotorcycledomainmodelobjectintotheview.We’llcallitMotorcycleData:

publicclassMotorcycleData

[code]{
publicstringMake{get;set;}

publicstringModel{get;set;}

publicintYear{get;set;}

publicstringVIN{get;set;}

publicboolAddAdditionalCycle{get;set;}

}

[/code]
Thispatternrequiresmoreworkanditalsorequiresa“mapping”translationlayertomapbackandforthbetweentheMotorcycleandMotorcycleDataobjectsbutitisoftenwellworththeeffortasviewsgetmorecomplex.ThispatternisstronglyadvocatedbytheauthorsofMVCinAction(abookahighlyrecommend).TheseideasarefurtherexpandedinapostbyJimmyBogard(oneoftheco-authors)inhispostHowwedoMVC–ViewModels.IstronglyrecommendedreadingBogard’spost(therearemanyinterestingcommentsonthatpostaswell).InithediscussesapproachestohandlingthispatternincludingusingMVCActionfiltersandAutoMapper(IalsorecommendcheckingoutAutoMapper).

Let’scontinuetobuildoutthispatternwithouttheuseofActionfiltersasanalternative.Inreal-worldscenarios,theseviewmodelscangetcomplexfast.NotonlydoweneedtomapthedatafromMotorcycletoMotorcycleData,butwealsomighthavenumerouscollectionsthatneedtobepopulatedfordropdownlists,etc.Ifweputallofthiscodeinthecontroller,thenthecontrollerwillquicklyendupwithalotofcodededicatedjusttobuildingtheviewmodelwhichisnotdesirableaswewanttokeepourcontrollersthin.Therefore,wecanintroducea“builder”classthatisconcernedwithbuildingtheviewmodel.

publicclassMotorcycleViewModelBuilder

[code]{
privateIMotorcycleRepositorymotorcycleRepository;


publicMotorcycleViewModelBuilder(IMotorcycleRepositoryrepository)

{

this.motorcycleRepository=repository;

}


publicMotorcycleViewModelBuild()

{

//codeheretofullybuildtheviewmodel

//withmethodsintherepository

}

}

[/code]
Thisallowsourcontrollercodetolooksomethinglikethis:


publicActionResultEdit(intid)

[code]{
varviewModelBuilder=newMotorcycleViewModelBuilder(this.motorcycleRepository);

varmotorcycleViewModel=viewModelBuilder.Build();

returnthis.View();

}

[/code]
Ourviewscanlookprettymuchthesameaspattern#2butnowwehavethecomfortofknowingthatwe’reonlypassinginthedatatotheviewthatweneed–nomore,noless.Whentheformispostedback,ourcontroller’sSave()methodcannowlooksomethinglikethis:


publicActionResultSave([Bind(Prefix="Motorcycle")]MotorcycleDatamotorcycleData)

[code]{
varmapper=newMotorcycleMapper(motorcycleData);

Motorcyclemotorcycle=mapper.Map();

this.motorcycleRepository.Save(motorcycle);

returnthis.RedirectToAction("Index");

}

[/code]
Conceptually,thisimplementationisverysimilartoBogard’spostbutwithouttheAutoMapattribute.TheAutoMapattributeallowsustokeepsomeofthiscodeoutofthecontrollerwhichcanbequitenice.Oneadvantagetonotusingitisthatthecodeinsidethecontrollerclassismoreobviousandexplicit.Additionally,ourbuilderandmapperclassesmightneedtobuildtheobjectsfrommultiplesourcesandrepositories.Internallyinourmapperclasses,youcanstillmakegreatuseoftoolslikeAutoMapper.

Inmanycomplexreal-worldcases,somevariationofpattern#3isthebestchoiceasitaffordsthemostflexibilitytothedeveloper.

Considerations

Howdoyoudeterminethebestapproachtotake?Herearesomeconsiderationstokeepinmind:

CodeRe-use–Certainlypatterns#1and#2lendthemselvesbesttocodere-useasyouarebindingyourviewsdirectlytoyourdomainmodelobjects.Thisleadstoincreasedcodebrevityasmappinglayersarenotrequired.However,ifyourviewconcernsdifferfromyourdomainmodel(whichtheyoftenwill)options#1and#2begintobreakdown.

Impedancemismatch–Oftenthereisanimpedancemismatchbetweenyourdomainmodelandtheconcernsofyourview.Inthesecases,option#3givesthemostflexibility.

MappingLayer–Ifcustomviewentitiesareusedasinoption#3,youmustensureyouestablishapatternforamappinglayer.Althoughthismeansmorecodethatmustbewritten,itgivesthemostflexibilityandtherearelibrariesavailablesuchasAutoMapperthatmakethiseasiertoimplement.

Validation–Althoughtherearemanywaystoperformvalidation,oneofthemostcommonistouselibrarieslikeDataAnnotations.Althoughtypicalvalidations(e.g.,requiredfields,etc.)willprobablybethesamebetweenyourdomainmodelsandyourviews,notallvalidationwillalwaysmatch.Additionally,youmaynotalwaysbeincontrolofyourdomainmodels(e.g.,insomeenterprisesthedomainmodelsareexposedviaservicesthatUIdeveloperssimplyconsume).Sothereisalimittohowyoucanassociatevalidationswiththoseclasses.Yes,youcanuseaseparate“metadata”classtodesignatevalidationsbutthisduplicatessomecodesimilartohowaviewmodelentityfromoption#3wouldanyway.Therefore,option#3givesyoutheabsolutemostcontroloverUIvalidation.

Conclusion

Thefollowinghasbeenasummaryofseveralofthepatternsthathaveemergedindealingwithviewmodels.AlthoughthesehaveallbeeninthecontextofASP.NETMVC,theproblemwithhowbesttodealwithviewmodelsisalsoanissuewithotherframeworkslikewebformsaswell.Ifyouareabletobinddirectlytodomainmodelinsimplecases,thatisthesimplestandeasiestsolution.However,asyourcomplexitygrows,havingdistinctviewmodelsgivesyouthemostoverallflexibility.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐