您的位置:首页 > 其它

Castle项目简介--第二部分

2005-11-26 11:07 323 查看

IntroductionCastle2

Thisarticlesisacontinuationof:IntroducingCastlePartI.I'llgoonexplaininghowinversionofcontrolcansimplifythedevelopmentofapplications,butnowpayingspecialattentiontowebapplications.

这篇文章是:IntroducingCastlePartI的后续篇。在这里将继续学习如何使用IOC来简化应用程序的开发,但是我们会将关注点切换到WEB应用开发上来。

I'llalsoexplainmoreaboutCastle'scontainernamedWindsorandhowitsfacilitiescansimplifythedeveloper'slife.Youcanseealistofimplementedfacilitieshere.

同时也会更加深入的探讨Windsor容器以及相关能简化开发人员工作的Facilities。在这里你可以获取已经实现好的Facility列表。

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
HomeController
andwillinvokethemethodnamed
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.railshttp://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
Controller
class,youcanalsousethe
SmartDispatcherController
.Thisspecific
Controller
willtrytomatchtheparametersontherequesttoyourmethodarguments,forexample:

除了继承Controller类,还可以继承SmartDispatcherController。这个Controller可根据你请求的参数来匹配带相应参数定义的方法,如:

publicclassAccountController:SmartDispatcherController
{
publicvoidNew()
{
}
publicvoidCreateAccount(Stringname,Stringlogin,Stringpassword)
{
...
}
}

Fortheabovecontroller,youcanusetheURL:http://yourserver/account/createaccount.rails?name=hammett&login=hammett&password=mypass.

使用http://yourserver/account/createaccount.rails?name=hammett&login=hammett&password=mypass这个URL使用上面这个Controller了。

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:包括ControllersDAOsServices

Castle.Applications.MiniDump.tests:使用它去测试DAOs,Services以及Controllers

Castle.Applications.MindDump.Web:包含的ViewsASP.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
WindsorContainer
justtoprovideyourinitializationsemanticsandpreferencesandinasmallreusableunit.Forexample,forthisproject,I'vecreatedthe
MindDumpContainer
class.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's
Session
,performyourwork,andfinallycloseit.Castle'sNHibernateFacilitycantakecareofthisrepetitivetask,youjustneedtousethe
UsesAutomaticSessionCreation
attribute.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
Session
automatically.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
SkipFilter
sotheycanbeaccessedwithouttheauthenticationchecking.

注意一些方法标注了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

InordertouseCastleonRailswiththe
WindsorContainer
,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很少会改变。团队也因互相交流在不断壮大。

PleasecontacttheteamthroughthemailinglistonCastleProjectsite.

History

25-Jan-2005-Initialversion.

AboutHamiltonVerissimo

HamiltonVerissimohasbeenworkingwithsoftwaredevelopmentfor9years.He'sinvolvedwithalotofopensourceprojectstofillhissparetime.

ClickheretoviewHamiltonVerissimo'sonlineprofile.

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