Castle项目简介--第二部分
2005-11-26 11:07
323 查看
IntroductionCastle2
Thisarticlesisacontinuationof:这篇文章是:
I'llalsoexplainmoreaboutCastle'scontainernamedWindsorandhowitsfacilitiescansimplifythedeveloper'slife.Youcanseealistofimplementedfacilities
同时也会更加深入的探讨Windsor容器以及相关能简化开发人员工作的Facilities。在
TheMindDumpApplication
Ifwejusttalkaboutinversionofcontrolwithoutareallifeexample,thingswillsoongetboring.Thus,let'screateasimpleyetfunctionalwebapplication.Whynotanotherblogapplication?Afewrequirementsthatweneedtoaccomplish:讨论IOC如果没有有现实意义的示例将变得毫无意义。所以,让我们来创建一个简单但是却有实际意义的Web应用。为什么不是其它的Blog应用,因为我们仅仅需要完成下列需求:
Theusermustcreateanaccountandablogtoaccessthepublishingareaoftheapplication.
Theusermustbeloggedontoposttextsonhisblog.
TheblogsmustbeaccessiblefromafriendlyURL.
Thesystemmust"remember"theuserforafewdays,andyetprovidetheminimumofsecurity.
Eachblogcanhaveitsowntheme.
用户必须创建一个账户才可以访问Blog发布区;
用户发须登录后才可以发布新的Blog;
所有的Blog均可通过友好的Url来浏览;
系统可以记住用户一段时间,同时也需要提供最小的安全支持。
要求每个Blog可以有不同的主题;
Thereareafewoptionstopersistdatanowadays,butlet'sstickwiththeoldandwell-knowndatabases.I'mgoingtouseMySQLforthedevelopment.
如今保存数据有许多方式,我们仍选择传统众所周知的数据库。在这里我们将使用MySQL来做这件事。
Thisisthescreenshotofthefirstpage:
下图是首页的快照:
Thisisthemaintenanceareawheretheusercanpublishnewentriesoreditoldentries:
Thefollowingaretwodifferentthemesappliedtothesameblog:
下面是相同的Blog应用两个不同主题的结果:
NHibernate
Oneofthefamousanti-patternsistheNIH,whichstandsforNotInventedHere.Sometimes,reinventingthewheelisimportant,evenasanexercise,soyoucanhaveadeeperknowledgeaboutsomethingyoudon'tknowmuchabout.Forexample,trytocreateyourowncompressionalgorithm,trytoimplementyourowndockingcodeforyourwindows,andsoon.NIH是一个著名的anti-patterns(有谁知道它们是什么,请告诉我),但这里并不准备这样做。有的时候,从0开始也是很重要,因为那时作为一种经历,你能更深入的了解你未知的领域。比如写个属于你的压缩算法,实现窗体停靠等等。
However,reinventingthewheelforeverythingisarecipefordisaster,sowedon'twanttodothat.OneofCastle'sgoalistoprovideintegrationwithgoodprojectsouttherefordealingwithspecificconcerns.Forexample,fordatabaseaccess,wehavefacilitiesforNHibernateprojectandanotheroneforiBatis.Netproject.Forthisapplication,we'regoingtousetheNHibernatefacility.
但是如果一切从0开始那将是一场灾难,所以我们不必这样。Castle目标之一就是整合好的项目使你只需关注特别的领域。举例来说,对于数据库访问我们有NHibernate和iBatis.Net的Facility的项目。本例中会使用NHibernateFacility。
WebFramework
Everytimesomeoneapproachesmewithanewsuperframeworkthatissupposedtobethenewwayofdoingwhatever,Iturnonmyultra-skepticalmode.That'sreasonableforeveryone,andIhadthemturnedonwhenIstartedtodevelopsomeapplicationwithRubyandthenwasintroducedtoRubyonRailswebframework.Fewhourslater,Iwasastonished.Howcansomeonecomeupwithsuchagreatideaanddidn'twintheNobelofScience?每次当有人告诉我一个新的超级框架可以使用一种新的方式来做任何事时我都极端怀疑。这是有原因的,但是当有人给介绍RubyonRails并且当我使用它开发一些应用时我开始转变。短短的时间,令我自已都很惊讶。为什么一个人有如此伟大的创意却没有获得诺贝尔奖呢?
RubyonRailsisagreatframework,butinmyhumbleopinion,notparticularlybecauseitsolvesalotofcommondailythings,butbecauseofitssimplicity.It'ssosimplethatitbecomespredictable,whichisextremelygoodinaframework.Youdon'thavetoreadthousandsofPDFpagestouseit.Onceyougetthebasicidea,yougetusedtoitnaturally.
RubyonRails真的是一个伟大的框架,但是对于孤陋的我来说,并不是因为它能解决许多平常问题,而是它足够简单。如此简单才使得可估测,这对于一个框架来说非常好。使用它你不必阅读成千上万的文档。一旦你有了想法,你就可以方便的使用它。
"Andhowitworks?"youmayask.Basically,it'saMVCframework,butwithoutconfigurationfilestoconnectthecontrollerwithactionsandwithviewsand...Yougotit.WithRails,you'llhavejustthreeentitiestopayattention:controllers,modelsandviews.FromtheURLbeingaccessed,Railswillinferthecontrollernameandtheactionbeingcalled.Actionsarenothingbutpublicmethodsonthecontroller.SosupposeyouaccessthefollowingURL:
“它是怎样工作的呢?”基本上它就是一个MVC框架,但是它不需任何配置文件来连接actions与views…,你就可以使用。在Rails中,你只需关注三个实体:controllers,models和views.根据访问的URL,Rails将可以推断controller名称与要调用的action.Actions是指controller的公共方法。假设你通过下的URL来访问:
http://yoursite/home/index
ThisURLwillinstantiatethecontrollernamed
HomeControllerandwillinvokethemethodnamed
index.There'safolderforviewswhichusesexactlythesamehierarchicaldefinition:controllernameplustheactionname.Ifyourmethoddoesn'tspecifyanythingdifferent,theviewchosenwillmatchthecontroller/action.Youractioncanuseadifferentview,though.
这个URL表示名为HomeController的controller,并将调用用名为index的方法。这里也有一个controller的名称/action名称的文件夹来保存views。如果方法没有其它不同,那么这个view将会是controller/action。当然你的action也可以使用其它的view.
Well,RubyonRailssupportsmuchmorethanthis,butyougottheidea.Youmightbethinkingabouthowvaluablethisconceptis,andI'llsay,frommystandpoint,thatthisisextremelyvaluableforapplications.Itenforcessimpledesignanddistinctresponsibilities:controllerswillorchestratetheinvocationofthebusinessobjectsandobtaindatafromthemodel;viewswillonlypresentthedata.
RubyonRail还支持更多的特性,这仅仅是你了解的其中一点。你可能会问这有什么价值呢?从我的观点来说,这特别有用。它强制设计简单并使功能责任明确:controllers调用业务对象并从模型获取数据而views仅仅是显示数据。
IfyoucomparethisapproachwithASP.NET,youwillseehowthosevalueswereputoff.EachASP.NETWebFormislikeaVisualBasicFormthathandleseverything:businessobjectinvocations-ifyou'relucky-dataostentation,databinding,inputvalidation,generationofscripts,andatlast,thepresentation.Don'tgetmewrong,IlikeASP.NET,thewholeideabehindcontrolslifecycleisamazing,andtheviewstatethatsimulatesstateinaninherentstatelessenvironmentisevengreater.
将它与ASP.NET比较,你可能会明白这些好处。对于每一个ASP.NET的WebForm和VB的表单差不多,它处理一切:业务对象调用,数据绑定,输入校验,客户端校本生成到最后的显示。不要误解我的意思,我也非常喜欢ASP.NET,它整个代码后置的思想令人惊讶,使用viewstate使无状态成为有状态就更有意义。
ButASP.NETWebFormsdonotpromotegooddesign.Youhavetobeverypickytonotputlogicthatdoesn'tbelongtothepresentationontheWebFormsoyoudon'tendupwithanunmanageableapplication.
但是ASP.NETWebForms不能带来良好的设计。你不得不吹毛求疵将处理逻辑与表现层分开,但那将变得难以管理。
CastleonRails
CastleonRailsisanattempttoprovidesomethingsimilartoRubyonRailsforthe.NETworld,butit'snotablindport.AsRubyoffersallthebeautiesofadynamiclanguage,.NEToffersthebeautiesofstatictypesandattributes.Sothegoalistryingtocombinebothworlds.CastleonRails是一个尝试在.Net里去提供一些与RubyonRails相近但不瞎同的项目。对于Ruby提供了优秀的动态语言支持,而.Net则提供了静态类型与特性支持。所以我们的目标是结合两者的优点。
I'veusedCastleonRailsonthreeapplicationsandithassimplifiedmylifeandsimplifiedwhatIcode.Mycontrollersendupwithlittlemethodsandlessthanahundredlinesofcode.
我已经在三个项目中使用CastleonRails,它使我的开发工作变得很简单。我的Controller仅仅少量的方法,节省了几百行代码。
PleasenotethatCastleonRailsdoesnotintendtoreplaceWebForms,infact,itusesWebFormsasoneoftheavailableviewsengine.ItalsosupportsNVelocityasaviewengine.MypersonalchoiceisNVelocityasitstandsexactlyasitissupposedtobe-justpresentationprocessing.
注意CastleonRails并不是要取代WebForms,实际上它也使用WebForms使为其中的一个views引擎。同时它也支持NVelocity,我个人选择NVelocity来处理表现层。
Howitworks
TheworkingofRailsisprettystraightforwardandintentionallysimilartotheoriginalRubyonRails:youhavetoprovideControllersandViews.Eachcontrollershoulddealwithonespecificconcernofyourwebapplication.Thepublicmethodsexposedbythecontrollerareaccessibleactions.Thefollowingisasamplecontrollercode:它与RubyonRail类似:你可以提供Controllers与Views。每个Controller应该只处理Web应用一个特定的问题。公共方法就是这个Controller可访问的Actsions。例如:
publicclassHomeController:Controller
{
publicvoidIndex()
{
}
publicvoidContactUs()
{
}
}
ThisspecificcontrollercanbeaccessedfromtheURL:http://yourserver/home/index.railsorhttp://yourserver/home/contactus.rails(it'scaseinsensitive).Theactionbodyshouldperformanylogic(gatheringdata,creatingorupdatingtablerows,andsoon),andthen,afterthemethodprocess,theframeworkwillexecutetheview.Fortheabovecontroller,CastleonRailswillsearchforanASPXontheviewdirectory:views\home\index.aspx.Ifyou'reusingtheNVelocityviewengine,thenitwilllookforviews\home\index.vm.Theviewmustexist,otherwisetheframeworkwillthrowanexception.
这个Controller可以通过http://yourserver/home/index.rails或http://yourserver/home/contactus.rails来访问(这里大小写不敏感)。action应该进行执行一些任务,如收集数据,创建或更新数据表的记录等等。做完这些,框架将执行view。对于这个Controller,CastleonRail将会在views目录中查找:views\home\index.aspx。如果你使用NVelocity作为view引擎,那它将会查找views\home\index.vm。这个view必须存在,否则框架将会抛出一个异常。
Thecontrollercanuseadifferentview,though.Thecodebelowwillrendertheindexview,ignoringtheinvokedaction:
Controller可以使用不同的View。下面的代码就会忽略调用的action,而呈现indexview。
publicclassHomeController:Controller
{
publicvoidIndex()
{
}
publicvoidContactUs()
{
RenderView("index");
}
}
Insteadofextendingthe
Controllerclass,youcanalsousethe
SmartDispatcherController.Thisspecific
Controllerwilltrytomatchtheparametersontherequesttoyourmethodarguments,forexample:
除了继承Controller类,还可以继承SmartDispatcherController。这个Controller可根据你请求的参数来匹配带相应参数定义的方法,如:
publicclassAccountController:SmartDispatcherController
{
publicvoidNew()
{
}
publicvoidCreateAccount(Stringname,Stringlogin,Stringpassword)
{
...
}
}
Fortheabovecontroller,youcanusetheURL:
使用
CastleonRailsalsosupports:
CastleonRails同时支持:
Filtersallowingyoutoassociateapreandpostprocessingoftheaction.
Rescuesallowingyoutoassociateanerrorpagepercontrollerandpermethod.
Layoutsallowingyoutoassociateamasterpage.
Filters:使你可以在action处理过程前后联系;
Rescures:如果Controller执行失败,你可以关联特定的错误而到特定的Controller甚至方法;
Layouts:使关联到一个master页。
Wearegoingtousemostofthesefeaturestoachievetherequirementforourapplication,sorestensuredthattheywillbecoveredsomehow.CastleonRailscanbeusedasastandaloneframework,orintegratedwith
WindsorContainer.Thisallowsyoutousethejuicyabilitiesofaninversionofcontrolcontainer.
为了实现我们的需求,我们将使这些功能。CastleonRails是一个能独立使用的框架,同时你也将它与WindsorContainer集成,这样的话你同时可以使用许多IOC容器的有趣功能。
Thedamnsimple"architecture"
Nofancythings,please!Wejustneedafewlayers:无需多想,我们需要:
1.Presentation:composedofControllersandFilters.
2.Services:justabusinessabstractionlayerbetweenthePresentationandDataaccess.It'salsoresponsibleforthetransactionboundaries.
3.DataAccessObjects:orsimpleDAOs.We'regoingtouseNHibernatetoactuallysave,updateandrequestdata.
1.表现层:包括Controllers和Filters;
2服务层:位于表现层与数据访问层之间的业务抽象层。包括处理事务的职责;
3.数据访问对对象:或者简单的DAO对象。在这里我们将使用NHibernate来保存更新与获取数据。
However,we'lluseafancypublisher/subscriberjusttoknowwhenanewblogwascreated.Thisisimportantsowecanregisteranewcontrollerforit.
然而我们将假设当一个Blog创建之后,publisher/subscriber是知道它的。这非常重要,因为只有这样我们才可以注册一个新的Controller给这个Blog。
Thefollowingpicturedepictsthecomponentsandtheirinteraction:
下图描述了这些组件及它们之间的关系。
Bytheway,thepictureisascreenshotofadesktopapplicationcalledCastle'sComponentViewer.Itinstantiatesyourapplication'scontainerandobtainsitsComponentGraph.TheComponentViewerisstillonearlystages,butwillbeagoodadditiontothewholeCastleProject.
插一句,这个图片是Castle’sComponentViewer这个桌面应用程序的截屏。它可以将你的应用容器与其中的组件的图形化表示。虽然这个组件查看器还处在起步阶段,但它将成为整个Castle项目的一部分。
Theprojectstructure
Ihopeyouagreewithme,butIthinkweneedthefollowingseparatedprojects:为了实现它们,我们需要三面三个工程:
Castle.Applicatins.MindDump:toholdControllers,DAOs,Services.
Castle.Applicatins.MindDump.Tests:totesttheDAOsandServices-wecouldalsotesttheControllers.
Castle.Applicatins.MindDump.Web:ASP.NETapplicationwiththeviews.
Castle.Applications.MindDump:包括Controllers,DAOs和Services;
Castle.Applications.MiniDump.tests:使用它去测试DAOs,Services以及Controllers。
Castle.Applications.MindDump.Web:包含的Views的ASP.NET应用;
Nowthatwe'veagreed,let'sgoon.
好了,让我们开始吧。
CreatingtheMindDumpapplication
Ofcourse,Iwon'texplaineachsteptakentocreatetheapplication,butIintendtoillustrateafewofthemsoyoucaneasilylearntherestfromthesourcecodeprovided.我当然不会一步步进讲述怎样创建这个应用,但是我会特别指出其中一些地方以方便你学习附件中的源码。
Firstofall,I'vecreatedtwoconfigurationfiles,oneforproductionandoneforthetestcases.I'vealsousedtwoCatalogsonMySQL,sothetestcasescoulddeletealltherowsbeforestartingthetests.
首先,我创建了两个配置文件,其中一个是给应用程序准备的,另一个为了测试案例准备的。
TheconfigurationfileswereusedtoconfiguretheNHibernatefacility,seebelow:
它们是给NHibernate的Facility使用的,请看:
<facilities>
<facilityid="nhibernate">
<factoryid="nhibernate.factory">
<settings>
<itemkey="hibernate.connection.provider">
NHibernate.Connection.DriverConnectionProvider
</item>
<itemkey="hibernate.connection.driver_class">
NHibernate.Driver.MySqlDataDriver
</item>
<itemkey="hibernate.connection.connection_string">
Database=minddump;DataSource=localhost;
UserId=theuser;Password=user
</item>
<itemkey="hibernate.dialect">
NHibernate.Dialect.MySQLDialect
</item>
</settings>
<resources>
<resourcename="..\bin\Author.hbm.xml"/>
<resourcename="..\bin\Blog.hbm.xml"/>
<resourcename="..\bin\Post.hbm.xml"/>
</resources>
</factory>
</facility>
</facilities>
Agoodpracticetosimplifyyourdevelopmentistoextendthe
WindsorContainerjusttoprovideyourinitializationsemanticsandpreferencesandinasmallreusableunit.Forexample,forthisproject,I'vecreatedthe
MindDumpContainerclass.Ifyoujustinstantiateit,itwillusetheproductionconfigurationfile.Oryoucanusetheotherconstructortospecifyadifferentfile-thetestcasesdoexactlythat.
继承WindsorContainer来提供初始化选项并将它们在小范围内重用来简化开发是一个好主意。如是果你这样,它就会使用这个配置文件。你也可以使用其它的方法来指向特定的文件,测试用例就是这样做的。
publicclassMindDumpContainer:WindsorContainer
{
publicMindDumpContainer():
this(newXmlConfigurationStore("../app_config.xml"))
{
}
publicMindDumpContainer(IConfigurationStorestore):base(store)
{
Init();
}
publicvoidInit()
{
RegisterFacilities();
RegisterComponents();
}
...
DAOsandServices
Now,IassumethatyouhavesomeknowledgeaboutNHibernate.Toaccessthedatabase,youneedtocreateandopenaNHibernate'sSession,performyourwork,andfinallycloseit.Castle'sNHibernateFacilitycantakecareofthisrepetitivetask,youjustneedtousethe
UsesAutomaticSessionCreationattribute.Followsthe
AuthorDao'scode:
在这里,我假设你已经有一些NHibernate的知识。如访问数据库,需要新建并打开一个Session,完成后需要关闭它。Castle’sNHibernateFacility注意到了这些重复的工作,你只需使用UseAutomatiocSessionCreation特性就可以了。就像下面AuthorDao的代码一样:
[UsesAutomaticSessionCreation]
publicclassAuthorDao
{
publicvirtualAuthorCreate(Authorauthor)
{
ISessionsession=SessionManager.CurrentSession;
if(author.Blogs==null)
{
author.Blogs=newArrayList();
}
session.Save(author);
returnauthor;
}
publicvirtualIListFind()
{
returnSessionManager.CurrentSession.Find("fromAuthor");
}
...
}
TheothersDAOsdon'thaveanythingmuchdifferentfromthisone.Wearenowfreetocodetheservicelayer.IftheclassesinvolvemodificationinmorethanoneDAO,ormorethanonemodificationonthesameDAO,it'snecessarytousetransactions.TheNHibernateFacilitydetectsatransactiononthethreadandenliststhe
Sessionautomatically.Sohereistheserviceresponsibleforcreatinganaccountandablog:
其它的DAOs与它类似。现在让我看Service层的代码:当修改多个DAO对象或者对同一个DAO作多个修改时,需要事务的支持。NHibernateFacility可以在线程别自动进行事务处理。所以服务类只需关心创建用户帐号与Blog就可以了。
[Transactional]
publicclassAccountService
{
privateAuthorDao_authorDao;
privateBlogDao_blogDao;
publicAccountService(AuthorDaoauthorDao,BlogDaoblogDao)
{
_authorDao=authorDao;
_blogDao=blogDao;
}
[Transaction(TransactionMode.Requires)]
publicvirtualvoidCreateAccountAndBlog(Blogblog)
{
_authorDao.Create(blog.Author);
_blogDao.Create(blog);
}
...
}
Easy,huh?
简单吧,哈哈!
TheControllers
ThestartpointofourapplicationliesontheIntrocontroller.Itdoesdomuch,justpresentsalistofrecentblogsandrecentposts.Pleaserememberthattheconstructor'sargumentswillberesolvedbythecontainer.我们这个应用的入口是Intro这个Controller。它需要列表最近的Blog与Posts。请注意构造函数的参数将会由IOC容器来处理。
[Layout("default")]
publicclassIntroController:Controller
{
privateBlogService_blogService;
publicIntroController(BlogServiceblogService)
{
_blogService=blogService;
}
publicvoidIndex()
{
PropertyBag.Add("blogs",_blogService.ObtainLatestBlogs());
PropertyBag.Add("posts",_blogService.ObtainLatestPosts());
}
}
Onthisapplication,we'reusingtheNVelocityViewEngine,sofollowsthecontentsoftheviewIntro\Index.vm:
我们在这个应用中使用NVelocity视图引擎,下面是Intro\Index.vm这个View的内容:
<p>
MindDumpisasimpleblogapplication.Tocreateyourblogandstarttodump
yourthoughtsandopinions,pleasecreate
<ahref="/account/new.rails">anaccount</a>.
</p>
<p>
Ifyouhaveanaccount,
please<ahref="/account/authentication.rails">logon</a>
</p>
<br>
<fontsize="+1">Lastupdates</font>
<br>
<p>
<tablewidth="100%"border="1">
#foreach($postin$posts)
<tr>
<td>
<ahref="/${post.blog.author.login}/view.rails?entryid=${post.id}"
>$post.title
</td>
<td>$post.date</td>
</tr>
#end
</table>
</p>
<br>
<fontsize="+1">Blogsregistered</font>
<br>
<p>
<tablewidth="100%"border="1">
#foreach($blogin$blogs)
<tr>
<td><ahref="/$blog.author.login/view.rails">$blog.name</td>
<td>$blog.description</td>
</tr>
#end
</table>
Asyoucansee,thisisjustthechildcontentsofabiggerpage.That'sbecausetheControllerisusingtheLayoutfeature.Asyoucansee,it'susingthe"default"layout.Inpractice,theNVelocitywilllookfora"default.vm"underthelayoutsdirectory:
正如你所见,这个页面有子内容。这是因为使用了Layout。它使用了Default这个Layout,运行时,NVelocity将会在Layouts目录中查找Defalut.vm。
<html>
<head>
<title>MindDump</title>
<metahttp-equiv="Content-Type"content="text/html;charset=iso-8859-1">
<linkhref="/css/main.css"rel="stylesheet"type="text/css">
</head>
<body>
<div>
<imgsrc="http://images.cnblogs.com/logo.jpg">
</div>
<br><br>
$childContent
<p>
<br><br>
<hrwidth="80%"size="1"noshade>
<divalign="center">
Copyright(c)2005-CastleProjectTeam</div>
</p>
</body>
</html>
Securedareas
Wehavethefollowingrequirementtokeepinmind:"theusermustbeloggedontoposttextsonhisblog".Wecanperformthischeckonthesensitiveactionsoneachcontrollerorwecanthinkaboutamorecleversolution.Well,I'dsaythatthebestwaytosolvethisproblemisbycreatingafilterthatcheckswhethertheusertryingtoaccesstheControllerisauthenticated.Ifhe'snot,thefilterredirectstheusertoaloginpageandreturnsfalse,thuspreventingtheactionofbeingprocessed.我们必须记得“用户只在登录到他的Blog后才可以发表文章”。这可以在每个相关的actions中来执行这个验证,要不我们就要想个更好的办法了。是的,我们可以通过创建一个Filter来访问Controller的用户是否授权了,如果没有,将重定向到用户登录页面并返回一个False值,以使停止相应的Action。
publicclassAuthenticationCheckFilter:IFilter
{
privateEncryptionService_encryptionService;
privateAccountService_accountService;
publicAuthenticationCheckFilter(AccountServiceaccountService,
EncryptionServiceencryptionService)
{
_accountService=accountService;
_encryptionService=encryptionService;
}
publicvirtualboolPerform(ExecuteEnumexec,
IRailsEngineContextcontext,Controllercontroller)
{
if(!PerformAuthentication(context))
{
context.Response.Redirect("account","authentication");
returnfalse;
}
returntrue;
}
protectedboolPerformAuthentication(IRailsEngineContextcontext)
{
Stringcontents=
context.Request.ReadCookie("authenticationticket");
if(contents==null)
{
returnfalse;
}
Stringlogin=_encryptionService.Decrypt(contents);
Authorauthor=_accountService.ObtainAuthor(login);
if(author==null)
{
returnfalse;
}
context.CurrentUser=newPrincipalAuthorAdapter(author);
returntrue;
}
}
Thenwecreateabaseclassforeverycontrollerthatneedsanauthenticateduserandthat'sit:
然后就可以为每一个需要使用用户验证的Controller创建一个基类:
[Filter(ExecuteEnum.Before,typeof(AuthenticationCheckFilter))]
publicabstractclassAbstractSecureController:SmartDispatcherController
{
}
ThemostcomplexControllerisexactlytheoneresponsibleforcreating,changinganaccountandperformingtheauthentication.Ithas120linesofcode:-)
最复杂的是创建,修改帐号并执行用户验证的Controller,它有120行代码:-)
Alsonotethatsomeofthemethodsaredecoratedwith
SkipFiltersotheycanbeaccessedwithouttheauthenticationchecking.
注意一些方法标注了SkipFilter特性,这样它们就可以不用验证用户授证就可以使用的。
[Layout("default")]
publicclassAccountController:AbstractSecureController
{
privateAccountService_accountService;
privateAuthenticationService_authenticationService;
privateEncryptionService_encryptionService;
publicAccountController(AccountServiceaccountService,
AuthenticationServiceauthenticationService,
EncryptionServiceencryptionService)
{
_accountService=accountService;
_authenticationService=authenticationService;
_encryptionService=encryptionService;
}
[SkipFilter]
publicvoidNew()
{
}
[SkipFilter]
[Rescue("errorcreatingaccount")]
publicvoidCreateAccount(Stringlogin,Stringname,Stringemail,
Stringpwd,Stringpwd2,Stringblogname,
Stringblogdesc,Stringtheme)
{
//Performsomesimplevalidation
if(!IsValid(login,name,email,pwd,
pwd2,blogname,blogdesc,theme))
{
RenderView("new");
return;
}
Authorauthor=newAuthor(name,login,pwd);
Blogblog=newBlog(blogname,blogdesc,theme,author);
_accountService.CreateAccountAndBlog(blog);
//Done,nowletslogonintothesystem
PerformLogin(login,pwd);
}
[SkipFilter]
publicvoidAuthentication()
{
}
[SkipFilter]
publicvoidPerformLogin(Stringlogin,Stringpwd)
{
if(!_authenticationService.Authenticate(login,pwd))
{
Context.Flash["errormessage"]=
"Usernotfoundorincorrectpassword.";
RenderView("Authentication");
}
else
{
DateTimetwoWeeks=DateTime.Now.Add(newTimeSpan(14,0,0,0));
Context.Response.CreateCookie("authenticationticket",
_encryptionService.Encrypt(login),twoWeeks);
Redirect("Maintenance","newentry");
}
}
...
}
I'mafraidnowyouknowasmuchaboutCastleonRailsasIdo:-)
恐怕你现在了解CastleonRails和我一样多了吧:-)
Thetestcases
Testingalooselycoupledapplicationisverysimple.Forexample,followsafewtestcasesforoneoftheDAOs:测试非常简单,比如一个DAO可以有几个测试用例:
[TestFixture]
publicclassAuthorTestCase:BaseMindDumpTestCase
{
[Test]
publicvoidCreate()
{
ResetDatabase();
AuthorDaodao=(AuthorDao)Container[typeof(AuthorDao)];
Assert.AreEqual(0,dao.Find().Count);
Authorauthor=newAuthor("hamiltonverissimo","hammett","mypass");
dao.Create(author);
IListauthors=dao.Find();
Assert.AreEqual(1,authors.Count);
Authorcomparisson=(Author)authors[0];
Assert.AreEqual(author.Name,comparisson.Name);
Assert.AreEqual(author.Login,comparisson.Login);
Assert.AreEqual(author.Password,comparisson.Password);
}
...
I'velivedinhellfiveyearsagowhenwehadtocopewithacomplexanduntesteableapplicationinC++.It'sincrediblehowmuchonecanlearnfromtheirbadexperiences...;-)
五年前,对付复杂难以测试的C++应用就象生活在地狱中一样。那种学习经历还习想象…J
TheWebApplication
InordertouseCastleonRailswiththeWindsorContainer,youneedto:
为了和WindsorContainer一起使用CastleonRails,你需要:
Addafewentriestoweb.config.
Makethecontaineravailablethroughthe
HttpApplication.
需要在Web.config中添加一配置项。
确保WindsorContainer容器在HttpApplication中有效。
Mightsoundcomplex,butitisn't.
听起也许很复杂,其实不然。
First,theweb.config.YouneedtosaytoCastleonRailswhichViewengineyouwishtouseandwhichControllerandFilterFactoriesitshoulduse.Don'tforgettoassociatetheextension"rails"withtheASP.NETISAPIifyou'reusingIIS.Followstheweb.configcontents:
首先,web.config需要配置项来让CastleonRails是使用那个View引擎以及使用那个Controller与Filter工厂类。如果使用IIS同时不要忘记将扩展名“rails”与ASP.NETISAPI关联:
<?xmlversion="1.0"encoding="utf-8"?>
<configuration>
<configSections>
<sectionname="rails"
type="Castle.CastleOnRails.Engine.Configuration.RailsSectionHandler,
Castle.CastleOnRails.Engine"/>
</configSections>
<rails>
<viewEngine
viewPathRoot="views"
customEngine=
"Castle.CastleOnRails.Framework.Views.NVelocity.NVelocityViewEngine,
Castle.CastleOnRails.Framework.Views.NVelocity"/>
<customFilterFactory
type="Castle.CastleOnRails.WindsorExtension.WindsorFilterFactory,
Castle.CastleOnRails.WindsorExtension"/>
<customControllerFactory
type="Castle.CastleOnRails.WindsorExtension.WindsorControllerFactory,
Castle.CastleOnRails.WindsorExtension"/>
</rails>
<system.web>
<httpHandlers>
<addverb="*"path="*.rails"
type="Castle.CastleOnRails.Engine.RailsHttpHandlerFactory,
Castle.CastleOnRails.Engine"/>
</httpHandlers>
</system.web>
</configuration>
Now,justcreateaglobal.asaxifyoudon'thaveone,andassociatethefollowingcodewithit:
如果还没有global.asax文件就创建一个,并将它与下的代码关联:
publicclassMindDumpHttpApplication:HttpApplication,IContainerAccessor
{
privatestaticWindsorContainercontainer;
publicvoidApplication_OnStart()
{
container=newMindDumpContainer();
}
publicvoidApplication_OnEnd()
{
container.Dispose();
}
publicIWindsorContainerContainer
{
get{returncontainer;}
}
}
Conclusion
Thesamplecodewillprobablyloweryourskepticallevel.Inthenextarticle,I'lltalkabouttheActiveRecordFacilityandanythingelseyoumightconsiderimportant.示例代码可能会降低你的怀疑度。下一篇文章,我会谈及ActiveRecordFacility以及其它可能认为比较重的东东。
Castlestillisonthebetastage,butthepublicAPIisveryunlikelytochange.Theteamisgrowingaswedevelopahealthycommunity.
Castle仍然在beta阶段,但公开的API很少会改变。团队也因互相交流在不断壮大。
Pleasecontacttheteamthroughthemailingliston
History
25-Jan-2005-Initialversion.AboutHamiltonVerissimo
HamiltonVerissimohasbeenworkingwithsoftwaredevelopmentfor9years.He'sinvolvedwithalotofopensourceprojectstofillhissparetime. Click |
相关文章推荐
- 第二部分:深入Maven,POM配置,构建多模块项目
- 第三篇——第二部分——第一文 SQL Server镜像简介
- 项目总结(Ajax+Struts+Spring+Hiberante+SQLServer2000) 第二部分
- Windows Phone 8初学者开发—第10部分:数据绑定应用程序和透视应用程序项目模板简介
- 认识 Atom 发布协议,第 3 部分: Apache Abdera 项目简介
- Castle项目简介--第一部分(译)
- EasyDarwin开源音频解码项目EasyAudioDecoder:EasyPlayer Android音频解码库(第二部分,封装解码器接口)
- Team Architect Edition 应用程序设计器简介,第二部分
- maven 项目(四) spring集成springMVC开发统一接入API(准备工作:第二部分)
- Windows Phone 8初学者开发—第10部分:数据绑定应用程序和透视应用程序项目模板简介
- 一些做过的项目相关图文简介(部分内容)
- Cocos2d-x 3.2 大富翁游戏项目开发-第二部分片头动画
- EasyDarwin开源音频解码项目EasyAudioDecoder:EasyPlayer Android音频解码库(第二部分,封装解码器接口)
- Django官方教程(四)【创建你的第一个 Django 项目,第二部分】
- PowerDesigner UML 简介(第二部分)
- 应用 Rational 工具简化基于J2EE的项目 第二部分: 启动项目
- 项目总结(Ajax+Struts+Spring+Hiberante+SQLServer2000) 第二部分
- 项目总结(Ajax+Struts+Spring+Hiberante+SQLServer2000) 第二部分
- 逻辑数据中心设计器简介 — 第二部分
- 第三篇——第二部分——第一文 SQL Server镜像简介