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

ASP.NET MVC with NHaml - F# Edition

2010-05-21 17:42 253 查看
AspartofsomeofmyadventureswithF#,I'veseenalotofinterestingthingscomingfromotherswithregardstoSharePoint,ASP.NETandothertechnologies.ThishadmethinkingofanypossibilitiesandramificationsofusingF#withASP.NETMVC.Wasitpossible,andbetterquestion,whatmightmakesomeoneusethisovertheirexistingtoolsets.Thosearesomeofthequestionstoexplore.But,inthemeantime,let'stakethejourneyofF#andASP.NETMVC.

GettingStarted

First,let'scoverwhatittakestogetF#toworkwithASP.NETMVC.Therequireddownloadsare:

F#September2008CTP
ASP.NETMVCPreview5
MVCContribforASP.NETMVCPreview5

Sidebyside,Ithinkit'seasiertofirstcreateasampleC#ASP.NETMVCproject,soit'seasytocutandpastetheconfigurationfileinformation.WhatworksbetteristoopentheNHamlViewEnginesamplefromMVCContrib.Also,createastandardF#library,andinmycase,IcalleditMvcFSharp.

Ithenaddreferencestothefollowingassemblies:

Microsoft.Web.Mvc.dll
MvcContrib.dll
MvcContrib.NHamlViewEngine.dll
System.Web.dll
System.Web.Abstractions.dll
System.Web.Extensions.dll
System.Web.Mvc.dll
System.Web.Routing.dll

SincetheF#projectsdonotsupportcreatingfolders,thisnextsteprequiressomeVisualNotepadsupport.

ModifyingtheProjectFile

First,createtheModels,Controllers,ContentandViewsdirectoriesthroughWindowsExplorer.Createdummyfilesineachfolderisprobablytheeasiestthingtodo.Whenyouaredone,yourprojectfilecontainthis:

<ItemGroup>
<CompileInclude="Models\ListViewData.fs"/>
<CompileInclude="Controllers\HomeController.fs"/>
<CompileInclude="Default.aspx.fs">
<DependentUpon>Default.aspx</DependentUpon>
<SubType>ASPXCodeBehind</SubType>
</Compile>
<CompileInclude="Global.asax.fs">
<DependentUpon>Global.asax</DependentUpon>
</Compile>
<ContentInclude="Default.aspx"/>
<ContentInclude="Global.asax"/>
<ContentInclude="Content\Site.css"/>
<ContentInclude="Content\MicrosoftAjax.js"/>
<ContentInclude="Content\MicrosoftAjax.debug.js"/>
<ContentInclude="Content\MicrosoftMvcAjax.js"/>
<ContentInclude="Content\MicrosoftMvcAjax.debug.js"/>
<ContentInclude="Views\Home\About.haml"/>
<ContentInclude="Views\Home\Index.haml"/>
<ContentInclude="Views\Home\Numbers.haml"/>
<ContentInclude="Views\Masters\Application.haml"/>
<ContentInclude="Web.config"/>
</ItemGroup>

Inthisactualcase,thesearetherealfileslistedforourfirstF#ASP.NETMVCapplication.OnceyoureloadtheprojectfilefromVisualStudio,youshouldbereadytogo.OnethingtokeepinmindwithyourprojectcompilationisthatinF#,orderoffilesdoesmatter.Anyfurtherworkwhereordermaymattercouldforceyoutochangetheprojectfileconfigurationwithnotepadonceagain.Oncetheprojectfilehasbeenmodified,let'smoveontomodifyingtheweb.configtoreflectusingF#asacompiler.

ModifyingtheConfiguration

ThereareseveralitemsweneedtoaddinordertogetbothNHamlandF#toworkasoneinsideourweb.config.AsIsaidearlier,it'sprobablyeasiesttocopy/pastesomebasicinformationfromtheNHamlViewEnginesamplefromMVCContribtosaveyourselffromhavingtoreferenceadditionalthings.First,let'saddNHamlsupporttoourprojectfile.WeneedtomodifytheconfigSectionsareatoaddnhamlViewEnginesupport.AddthefollowingtexttoyourconfigSections:

<configSections>
<sectionname="nhamlViewEngine"
type="MvcContrib.NHamlViewEngine.Configuration.NHamlViewEngineSection,
MvcContrib.NHamlViewEngine,
Version=0.0.1.159,
Culture=neutral,
PublicKeyToken=null"/>

Now,addthenhamlViewEngineinformationinasfollows.NotethatI'maddingsomereferencestoF#.ThereasonbeingisthatshouldIreturnatypethatisF#specificsuchasalist,seqorotherwise,NHamlwillnotbeabletoreferenceproperlywithoutaccesstotheFSharp.Core.dll.

<nhamlViewEngineproduction="false">
<views>
<assemblies>
<addassembly="FSharp.Core,
Version=1.9.6.2,
Culture=neutral,
PublicKeyToken=a19089b1c74d0809"/>
</assemblies>
<namespaces>
<addnamespace="Microsoft.FSharp.Core"/>
</namespaces>
</views>
</nhamlViewEngine>

Inorderforustocompileourdefault.aspx.fsandglobal.asax.fsfile,weneedtoaddsupportfortheF#compilerinourweb.config.AddthefollowingsectionofXMLtoyourcompilerssection,rightnexttoyourC#compilerregistration.

<compilerlanguage="F#;f#;fs;fsharp"
extension=".fs"
warningLevel="4"
type="Microsoft.FSharp.Compiler.CodeDom.FSharpAspNetCodeProvider,
FSharp.Compiler.CodeDom,Version=1.9.6.2,
Culture=neutral,
PublicKeyToken=a19089b1c74d0809">
<providerOptionname="CompilerVersion"value="v3.5"/>
<providerOptionname="WarnAsError"value="false"/>
</compiler>

WenowhavetheF#ASP.NETCodeProviderinstalledinourweb.config,soourfocusnowshiftstoproperregistrationinourglobal.asax.fsanddefault.aspx.fs.

ModifyingtheDefaults

Nowweturnourattentiontothetwodefaultsforourapplication,theglobal.asaxandthedefault.aspx.First,modifytheglobal.asaxtoindicatethefollowing:

<%@ApplicationCodeBehind="Global.asax.fs"Inherits="MvcFSharp.MvcApplication"Language="F#"%>

Ifyoufollowedtheprojectstructurefromabove,opentheglobal.asax.fsfileandmodifyittolooklikethis.

#light

namespaceMvcFSharp

openSystem.Web.Mvc
openSystem.Web.Routing
openMvcContrib.ControllerFactories
openMvcContrib.NHamlViewEngine

typeMvcConstraint2={action:string;id:string}
typeMvcConstraint3={controller:string;action:string;id:string}

typeMvcApplication()=
inheritSystem.Web.HttpApplication()

staticmemberRegisterRoutes(routes:RouteCollection)=
routes.Add(
newRoute("{controller}.mvc/{action}/{id}",
newMvcRouteHandler(),
Defaults=newRouteValueDictionary({newMvcConstraint2withaction="index"andid=""})))

routes.Add(
newRoute("Default.aspx",
newMvcRouteHandler(),
Defaults=newRouteValueDictionary({controller="Home";action="index";id=""})))

memberx.Application_Start()=
MvcApplication.RegisterRoutesRouteTable.Routes
ViewEngines.Engines.Add(newNHamlViewFactory())

Asyoucanseefromabove,Ihadtoaddtworecordtypes,calledMvcConstraint2andMvcConstraint3.ThereasonbeingisthatF#doesnotdoanonymoustypesasC#does.Instead,youdefineasimplerecordtypetoholdthedataasneeded.Sincetherearetwodifferentneeds,onewithtwofieldsandonewiththree,thereisaneedtodefinetwoseparateinstances.MuchasyouwouldintheC#code,theregistrationshouldnotlookallthatdifferent.But,IkepttheRegisterRoutesfunctionsothatIcantestmyroutesinaniceTDDfashion.

Movingontothedefault.aspxfile,modifythedefault.aspxtolooklikethefollowing:

<%@PageLanguage="F#"AutoEventWireup="true"CodeBehind="Default.aspx.fs"Inherits="MvcFSharp._Default"%>

Oncethatiscomplete,moveontothedefault.aspx.fsfile.Itshouldlooklikethefollowing:

#light

namespaceMvcFSharp

openSystem
openSystem.Web.UI

type_Default()=
inheritPage()

memberx.Page_Load(sender:obj,e:EventArgs)=
x.Response.Redirect("~/Home.mvc/Index")

Wenowhaveabasicsetupinwhichtobuilduponforourapplication.Nowwecanconcentrateonthemodels,controllersandviews.

CreatingtheModel

IwantjustabasicmodeltoshowthatcreatingconciseandcompactmodelsisrelativelysimpleusingF#.AsIdidfortheMvcConstraintrecordtypesabove,Icaneasilyapplytomymodel.Sometimes,ourmodelsmaybenothingmorethanjustawriteonceoperation,sosimpleimmutablerecordtypessuffice.Othertimes,wemayneedtomakesomeofthefieldsmutable.But,that'sthebeautyofF#,isthatitallowsustodoboth.

Let'screateonetoholdjustsomenumberstodisplayonthescreen.Iffollowingtheaboveprojectstructure,yourListViewData.fsshouldlooklikethefollowing:

#light

namespaceMvcFSharp.Models

typeListViewData={Numbers:seq<int>}

Done!Nowthatwaseasy!Movingontothecontroller...

CreatingtheController

Wehavethemodelsnowdefined,solet'smoveontothecontrollers.Ionlywantonecontrollerduringthisexample,inthiscasetheHomeController.fs.Let'ssayIhavethreeviewsIwanttoworkwith,theIndex,AboutandData.Definingsuchacontrollerisquitesimple.Itshouldlooksomethinglikethis:

#light

namespaceMvcFSharp.Controllers

openMvcFSharp.Models
openSystem.Web.Mvc

[<HandleError>]
typeHomeController()=
inheritController()

memberx.Index()=
x.View("Index")

memberx.About()=
x.View("About")

memberx.Numbers()=
letviewData={Numbers={1..10}}
x.View("Numbers",viewData)

Inthisexample,Ididnothingmorethanjusttellthesystemtorendertheview.Eachtime,it'sbestthatyoucastittotheappropriatereturntypemuchasIdidabove.InthecaseoftheNumbersfunction,Iwantedtocreateasetofnumberstorendertothescreen,soIcreatemynewListViewDatawithmynumbersset.Then,Ipassthattotheviewtorender.

CreatingtheViews

AsI'vestatedbefore,I'minterestedinfollowingtheexamplefromMVCContribfortheNHamlViewEngineasmuchaspossible.So,theviewslook100%liketheydofromtheproject,exceptformynumbers.hamlfile.Let'slookattheviewsthatmatter.First,theindex.hamlfile.

%h2WelcometomyASP.NETMVCApplicationusingNHaml!
=Html.ActionLink("AboutUs","About","Home")
=Html.ActionLink("Data","Numbers","Home")

Let'smoveontoourNumbers.hamlfilewhichwillusethedatathatIpopulatedfromourHomeController.Itshouldlooklikethefollowing:

%h2DatafromController
%ul
-foreach(varninViewData.Model.Numbers)
%li=n

Therestshouldstaythesamemuchasbefore.AsIsaid,Ionlywantedtotryoutafewfeaturesbeforegoingintoafullfledgedapplication.

TryingitOut

Oncetheapplicationisbuilt,wecanthencreatethevirtualdirectoryinIIStohostourapplication.Oncethatiscomplete,launchingthebrowserwillgiveusthisforourIndexview.

.png]



Ouraboutpagewilllooklikethefollowing:

.png]



Andlastly,ournumberspagewilldisplaythenumbersfrom1-10inanunorderedlist:

.png]



So,asyoucansee,wenowdisplayourdatafromourF#controllerandF#models.But,isthatallofourstorytotell?Ofcoursenot?Ithinkit'simportanttoemphasizeTDDwiththisapproach.ThisworksnodifferentthanitwouldinC#,quitefrankly.

TestDrivingourSolution

MuchlikewhenyoucreateanewASP.NETMVCproject,itwillbydefaulthelpyoucreateasetofunittestsusingthexUnitframeworkofyourhoice.Inthiscase,mydefaultisxUnit.net.Let'stalkabouttestinghereonceagain.AsI'vestatedbefore,IcreatedanoverallprojectcalledFsTestwhichcreatesaDSLovertheassertionsyntax.Thisallowsmetomorenaturallytestusingfunctionalprogrammingstrengths.Usingthis,I'mabletotestallofmycodemuchasyouwouldinyourC#solution.

Let'sfirststartwithourMvcApplicationtests.Let'sgothroughoneoftheteststhatIdidearlierinregardstomyNumbersfunction.

#light

namespaceMvcFSharp.Tests.Controllers

openFsxUnit.Syntax
openMvcFSharp.Controllers
openSystem.Web.Mvc

moduleNumbersFacts=

[<Concern>]
letsets_model()=
//Arrange
letcontroller=newHomeController()

//Act
letresult=controller.Numbers()

//Assert
result.ViewData.Model|>shouldbeNonNull

ThisisjustoneinthenumberofunitteststhatIdefinedforthisapplication.Asyoucansee,it'squiteeasytouseFsxUnitinusingtheAAAsyntax.

WrappingitUp

Asyoucansee,gettingF#toworkwithASP.NETMVCwasn'tabsolutelytrivial.Butoncetheoverallprojectskeletonisdefined,modifyingittofityourapplicationiseasier.But,thequestioncomesup,whybotherdoingthis?IknowI'mgoingtogetthatquestionalot.Well,firstoff,itwasachallengetomyself.But,secondly,I'mabletousetheconciseF#syntaxtoexpresscontrollersandmodelsquiteeasilywithoutmuchpompandcircumstance.Maybeahybridapproachmayworkbetter?MaybeF#asaviewenginemayyieldbetterresults?Anyhow,feelfreetopickthroughthissampleandletmeknowyourthoughts.

I'vemadetheprojectavailablehere.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: