tag:blogger.com,1999:blog-65864016564168395312024-03-16T10:28:15.913+01:00Marek Blotny's BlogProgramming, Design Patterns, Software Engineering and Human FactorsUnknownnoreply@blogger.comBlogger53125tag:blogger.com,1999:blog-6586401656416839531.post-45349737979294580732010-07-09T15:04:00.000+02:002010-07-09T15:04:49.597+02:00The EndI’m closing this blog which means that this my very last post here. I'm not going to delete this blog so content which is here will stay online. (At least for a while) I also don't want to finish my career as a blogger :) I’m just moving to a different place … my new blog is available here: <a href="http://marekblotny.com/">http://marekblotny.com/</a>.Unknownnoreply@blogger.com121tag:blogger.com,1999:blog-6586401656416839531.post-49465756586475978272009-09-26T23:05:00.002+02:002009-09-26T23:09:35.203+02:00The latest news from .Net world (September 2009)<div><span>On the very last meeting of <a href="http://ms-groups.pl/pg.net/default.aspx">Poznan .Net User Group</a> I had a great pleasure to give 15 minutes talk about the latest news from .Net world. Picking the most interesting news isn't a trivial task. In the end I've decided to mention about following three topics:</span></div><div><span ><br /><br /></span></div><div><span style="font-size:large;"><b>Visual Studio 2010 and .Net 4</b></span></div><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhu3u3Wa0W1rOs6yVIWljSIQa3jr9rUyoVIBYfn3W32EB8u7vlCHGpbQzGxnwQSOrB9g1GfIT9bg__UO-5lslvR7_lbkbmkcMBRUCkqVK2_igFjP0m70j3-NhOT4YS0t27CzS1xgAPUOjjJ/s1600-h/new_microsoft_dotnet_logo_thumb.png"><img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer; width: 160px; height: 152px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhu3u3Wa0W1rOs6yVIWljSIQa3jr9rUyoVIBYfn3W32EB8u7vlCHGpbQzGxnwQSOrB9g1GfIT9bg__UO-5lslvR7_lbkbmkcMBRUCkqVK2_igFjP0m70j3-NhOT4YS0t27CzS1xgAPUOjjJ/s320/new_microsoft_dotnet_logo_thumb.png" alt="" id="BLOGGER_PHOTO_ID_5385824279940030306" border="0" /></a><br /><div><span >This was quite straightforward choice, new versions of VS and .Net Framework affects everyone so it's good to know what new features will are about to arrive. Probably the best source of information from this field is <a href="http://weblogs.asp.net/scottgu">ScootGu's blog</a>. Recently he posted <a href="http://weblogs.asp.net/scottgu/archive/2009/08/25/vs-2010-and-net-4-series.aspx">series of post about VS 2010 and .Net 4</a>.</span></div><div><span ><br /></span></div><div><span >Just to quickly name a few the most notable:</span></div><ul><li><span >.Net Framework 4 and VS 2010 can be installed side by side with older versions. </span></li><li><span >a bunch of language improvements (C#) like: dynamic keyword, named and optional parameters, covariance, better COM support. Named and optional parameters is a long awaited feature, unfortunately <a href="http://devlicio.us/blogs/krzysztof_kozmic/archive/2009/09/03/thoughts-on-c-4-0-optional-parameters.aspx">it has some limitations</a> - basically you can't assign to a parameter default value which is not compile-time constant. </span></li><li><span >web.config files will be greatly simplified</span></li><li><span >VS 2010 will have much more project templates available. It will be possible to start with completely clean project but also with simple application having the most basic features included (master page, forms authentication pages, JQuery etc.). Interesting is that new sample project are already using </span><ul><li><span >clean client side Id (no more Ids like: ctl00_topLevelNavigation_rptMenu_ctl02_rptNextLevelMenu_ctl02_liTag!)</span></li><li><span >build-in controls are using CSS based rendering</span></li></ul></li><li><span >Improved Multi-targeting support - Problem with VS 2008 is that Intellisense always shows the types and members from .Net 3.5 (regardless of targeted version of .Net). This issue has been sorted with VS 2010.</span></li><li><span >ASP.Net, HTML and JS Snippet support - finally! :) For people working a lot with the view this is great news. I was writing tags and controls like Repeater from scratch so many times that I'm sick of it. With VS 2010 it can be automated.</span></li><li><span >Auto-Start ASP.Net Applications - Currently web applications are initialized with first request. This is a bit unfortunate because during the initialization sometimes it's required to perform number of heavy operations. That's why the first response is so delayed. There are of course number of ways to workaround this problem but with .Net 4 we will get dedicated means to auto-start our applications and perform initialization before the first request. </span></li></ul><div><span ><br /></span></div><div><span ><br /></span></div><div style="font-weight: bold;"><span style="font-size:130%;">Git</span></div><div><span ><br /></span></div><div><span ><a href="http://git-scm.com/">Git</a> is a distributed version control system which is getting more and more popular in .Net world. In outside world it is used by a big, very well-known projects like:<br /></span><ul><li><span >Linux Kernel,</span></li><li><span >Perl,</span></li><li><span >Ruby on Rails,</span></li><li><span >Gnome,</span></li><li><span >Andriod,</span></li><li><span >Fedora,</span></li><li><span >Debian,</span></li><li><span >X.org</span></li></ul></div><div><span >I think the above list provides evidence that this system works well (especially for OSS projects). </span></div><div><span ><br /></span></div><div><span >Also in .Net world there are projects which decided to dump popular version control systems like SVN and are moving to Git:<br /></span></div><ul><li><span ><a href="http://fluentnhibernate.org/">Fluent NHibernate</a>,</span></li><li><span ><a href="http://ayende.com/projects/rhino-mocks.aspx">Rhino Mocks</a>,</span></li><li><a href="http://www.sharparchitecture.net/"><span >S#arp Architecture</span></a></li></ul><div><span >Justification for those decision is worth reading:</span></div><ul><li><a href="http://ayende.com/Blog/archive/2009/09/04/why-git.aspx">Why Git?</a><br /></li><li><a href="http://devlicio.us/blogs/billy_mccafferty/archive/2009/09/14/s-arp-architecture-has-moved-to-github.aspx">S#arp Architecture has Moved to GitHub</a></li></ul><div><span ><br /></span></div><div><span style="font-size:large;"><b><br />CodePlex foundation (CodePlex.org)</b></span></div><div><span ><br /></span></div><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgBIirLwFJJe0efEP8s7zy9XcDKtrWImkVFQYBQJP5jRU9aDo2yqlECy9mB_oC1y6ocf1jNu-BEz9_o5rVhdJGSlVJ0jK3qShT8tlkwR6frWSYZYi8SvoOtsUF-DnopUM0jiEOGNOi7ptc/s1600-h/image_3.png"><img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer; width: 310px; height: 95px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgBIirLwFJJe0efEP8s7zy9XcDKtrWImkVFQYBQJP5jRU9aDo2yqlECy9mB_oC1y6ocf1jNu-BEz9_o5rVhdJGSlVJ0jK3qShT8tlkwR6frWSYZYi8SvoOtsUF-DnopUM0jiEOGNOi7ptc/s320/image_3.png" alt="" id="BLOGGER_PHOTO_ID_5385824272045889858" border="0" /></a><div><span >And finally last topic which I want to mention about is <a href="http://codeplex.org/">CodePlex foundation</a>. It has been created by Microsoft "to help commercial software developers use Open Source software" and which "ultimately aiming to bring open source and commercial software developers together in a place where they can collaborate".</span></div><div><span ><br /></span></div><div><span >It's well known that big commercial software development companies are not really keen to use OSS. I think that this approach is changing with time and CodePlex foundation's main goal to help with that. In general all of that sounds really good but for number of different reasons this initiative mostly wasn't well received by the community. Still I think it's worth keeping an eye on it :)</span></div><div><span ><br /></span></div><div><span >Here you can find a few related links:</span></div><ul><li><a href="http://www.hanselman.com/blog/MicrosoftCreatesTheCodePlexFoundation.aspx"><span >Microsoft creates the CodePlex Foundation</span></a></li><li><a href="http://ayende.com/Blog/archive/2009/09/11/codeplex-foundation.aspx"><span >CodePlex Foundation</span></a></li><li><a href="http://codebetter.com/blogs/jeremy.miller/archive/2009/09/11/some-quick-thoughts-about-the-new-codeplex-oss-initiative.aspx"><span >Some quick thoughts about the new Codeplex OSS initiative</span></a></li><li><a href="http://ayende.com/Blog/archive/2009/09/19/open-source-development-model.aspx"><span >Open Source development model</span></a></li><li><a href="http://blog.scottbellware.com/2009/09/analysis-codeplex-foundation-terms-of.html"><span >Analysis: CodePlex Foundation - The Terms of Mutual Surrender</span></a></li><li><span ><a href="http://samus.typepad.com/what/2009/09/why-its-called-the-codeplex-foundation.html">Why It's Called the CodePlex Foundation</a><br /></span></li></ul><div><br /></div>Unknownnoreply@blogger.com24tag:blogger.com,1999:blog-6586401656416839531.post-86593274760643250082009-09-16T08:00:00.003+02:002009-09-16T08:00:02.381+02:00Custom styles in Rich Text Editor (part II)It wasn't so long ago when I posted '<a href="http://marekblotny.blogspot.com/2009/05/how-to-define-custom-styles-in.html">How to define custom styles in EPiServer's Rich Text Editor</a>'. A few days ago I was following my own instructions to set up my custom styles and for absolutely peculiar reason Rich Text Editor didn't want to catch my settings! It took me a few hours to figure out what was going on. <div><br /></div><div>Basically you can define custom styles in EPiServer by setting <b>uiEditorCssPaths</b> property in web.config:</div><div><br /></div><div><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRubJmvZ0PzHpp1Bh_UMsDwalOySZx68LhHdSRAHqsSgTq3-0H1YsvpBEXi4MOZbVcFV_uHvaIU1NC1jXEYmcXLqX7qdMjMWMhUnIuizIBILSIFs97t8IHJprjezFNcaJ91k13QT0ah8W-/s1600-h/site_settings.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 209px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRubJmvZ0PzHpp1Bh_UMsDwalOySZx68LhHdSRAHqsSgTq3-0H1YsvpBEXi4MOZbVcFV_uHvaIU1NC1jXEYmcXLqX7qdMjMWMhUnIuizIBILSIFs97t8IHJprjezFNcaJ91k13QT0ah8W-/s400/site_settings.png" alt="" id="BLOGGER_PHOTO_ID_5381769964703114546" border="0" /></a><br /></div><div>which is the same as using System Settings in Admin mode:</div><div><br /></div><div><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiti2tSO3XTTfHQzB3xgLeVgp1RrGGR7HbE9LNV8NeSbyawWvmO2HXXYeZCnp4jGs6ZcWz8_XFS3sb-n0tEsqS2ZGwzmGcPSUr6cWK6zfQ1HBlmSxTyjPA4i7jJ6tqrpu6CWC6xmsntCdYx/s1600-h/system_settings.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 201px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiti2tSO3XTTfHQzB3xgLeVgp1RrGGR7HbE9LNV8NeSbyawWvmO2HXXYeZCnp4jGs6ZcWz8_XFS3sb-n0tEsqS2ZGwzmGcPSUr6cWK6zfQ1HBlmSxTyjPA4i7jJ6tqrpu6CWC6xmsntCdYx/s400/system_settings.png" alt="" id="BLOGGER_PHOTO_ID_5381769972815105522" border="0" /></a></div><div><br /></div><div>But of course there are cases where it may not take any effect :) </div><div><br /></div><div>If you encounter such case then check dynamic properties. You may have dynamic property called <b>UIEditorCssPaths</b>. If this property is set then other settings are ignored.</div><div><br /></div><div><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi68pFedmDjG-kxHf3LrrCgln_tVBGviobayn6Uu8ATP9wlYf9BT2-3McVyCA9BAIUGUB7Ki1bv2ZvObMJnnTIw9jR1-SfjwSKdUzv19RbKXugMi2-MLuanbJQ-nXR0ySKLMMeEuwbLOGFU/s1600-h/dynamic_properties.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 163px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi68pFedmDjG-kxHf3LrrCgln_tVBGviobayn6Uu8ATP9wlYf9BT2-3McVyCA9BAIUGUB7Ki1bv2ZvObMJnnTIw9jR1-SfjwSKdUzv19RbKXugMi2-MLuanbJQ-nXR0ySKLMMeEuwbLOGFU/s400/dynamic_properties.png" alt="" id="BLOGGER_PHOTO_ID_5381769958709150914" border="0" /></a></div>Unknownnoreply@blogger.com46tag:blogger.com,1999:blog-6586401656416839531.post-61015753553367757752009-09-07T09:00:00.000+02:002009-09-07T09:00:01.616+02:00Moving beyond the ScrumI used to be a firm supporter of Scrum process. I was deeply convinced that Scrum's approach makes total sense and is the right one. My impressionable attitude has changed with time. Now I see that "pure" Scrum is a quite rigid process which not always fits the best our specific type of work. What is exactly on my mind?<br /><br /><span style="font-weight: bold;font-size:130%;" ><br />Scrum is stopping us from higher productivity</span><br /><br /><br />Sprint length is derived based on project's life expectancy. Having at lest 4-6 sprints within the project is an absolute minimum. Usually we are choosing from two options: one or two weeks for sprint. Unfortunately there are some issues with both options:<br /><ul><li>One week sprint gives us only 4 days of development so one day is "wasted" on Scrum related activities. 20% of our time, it's quite a lot.<br /></li><li>With two week sprints there is still one day devoted for demo, planning session, retrospective etc. but ratio here is 9 to 1. It's a significant improvement but in the same time we have to deal with new issues which are more evident for two weeks sprint. </li><ul><li>Unchangeable sprint plan - sometimes it happens that somewhere in the mid-sprint all stories are done or blocked by some external dependency with which we can't do anything. Because we can't add new stories in the middle of sprint and we can't proceed with blocked stories we are forced to fill our time with "other stuff".<br /></li><li>Another absolutely normal thing is that some new, very important and extra urgent things need to be done ASAP. We can moan that things like that shouldn't happen but in reality they happen so we have to be prepared for it! Scrum's answer to situation like this is "we will take care of that in next sprint as current one is already in progress" or "we can stop this sprint, run planning session again and incorporate this very urgent story into sprint plan". Both options are not good enough, replacing one story with another of similar size in the mid-sprint doesn't hurt the team and in the same time can address important requirement. Scrum is not very flexible here.<br /></li></ul></ul><span style="font-weight: bold;font-size:130%;" ><br /></span>Even though there are some issues with Scrum I don't think that we should look for some revolutionary changes. This process works, we just need to tweak it in order to make it more suitable for our projects. Here are a few ideas to consider:<br /><ul><li>Do we need a sprint planning sessions? For sure we need <span style="font-weight: bold;">prioritized product backlog</span> to know which stories we should work on in the first place. On planning session we will either overestimate (stories which are not done by the end of sprint will be moved to the following one) or underestimate (and then we need to add some stories in the middle of sprint), priorities may change (stories swapping) therefore plan is very likely to change. Maybe it's better to simply use product backlog as a queue and finish as many stories as we can during the sprint?<br /></li><li>Retrospective meetings - they are very beneficial when project starts but when it's closing to the deadline they are getting less and less valuable. Maybe it should be team's decision if it makes sense to have such a meeting? If something is going wrong and it's obvious to everyone why should we wait till the end of sprint with retrospective? </li><li>Demo - usually we want to have implemented functionality available to the client as soon as possible so maybe instead of delivering new stories after one big demo we can have number of small demos and this way be more responsive?<br /></li></ul><br />Probably the most important thing which I have learned from Scrum is that we should keep looking for things which can be improved and experiment with the process. So if answer to some of above questions is positive then maybe it's time to move towards more lean processes? <a href="http://leansoftwareengineering.com/ksse/scrum-ban/">Scrum-ban</a>? <a href="http://en.wikipedia.org/wiki/Kanban">Kanban</a>?<br /><br /><span style="font-weight: bold;font-size:130%;" ><br />Last words ...</span><br /><br /><br />I have been using Scrum with different deviations for about 3 years now and I still think that Scrum has a lot to offer. The thing is that it doesn't have to fit perfectly for all types of projects. I think I can say that I'm still a firm supporter but not so impressionable any more.<br /><br />It seems that more and more teams is already moving along this path:<br /><ul><li><a href="http://www.silverstripesoftware.com/blog/archives/172">From Scrum to Kanban</a></li><li><a href="http://devlicio.us/blogs/tim_barcz/archive/2009/08/06/moving-to-kanban-did-scrum-fail.aspx">Moving to Kanban, Did Scrum Fail?</a></li><li><a href="http://codemonkeyism.com/changed-scrum-implementation/">5 Practices Better to Change in Your Scrum Implementation</a></li><li><a href="http://www.infoq.com/articles/current-direction-of-agile">The Current Direction of Agile</a><br /></li></ul>Unknownnoreply@blogger.com14tag:blogger.com,1999:blog-6586401656416839531.post-69421101343724359752009-09-01T09:30:00.000+02:002009-09-01T09:30:01.692+02:00How to display Dynamic Content without Property controlRecently I was investigating EPiServer's <a href="http://world.episerver.com/en/Articles/Items/How-dynamic-content-can-be-used-to-create-a-more-flexible-web-site/">Dynamic Content</a> a bit. General impression is very positive, Dynamic Content is a great feature. From developer's point of view it's fairly easy to develop custom Dynamic Content, everything works like charm .... maybe beside one thing :)<div><br /></div><div>Unfortunately, to display property's value with dynamic content replaced with our controls it's required to use EPiServer's control:</div><!-- code formatted by http://manoli.net/csharpformat/ --><br /><div class="csharpcode"><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd"><</span><span class="html">EPiServer:Property</span> <span class="attr">PropertyName</span><span class="kwrd">="MainBody"</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="kwrd">/></span></pre><br /></div><div>Different approaches won't work.</div><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="asp"><%</span>= CurrentPage[<span class="str">"MainBody"</span>] <span class="asp">%></span></pre><pre><span class="lnum"> 2: </span><span class="asp"><%</span>= CurrentPage.Property[<span class="str">"MainBody"</span>] <span class="asp">%></span></pre><pre class="alt"><span class="lnum"> 3: </span>litMainBody.Text = CurrentPage.Property[<span class="str">"MainBody"</span>].ToWebString()</pre></div><div><br /></div><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjkkXeLQDQNE4Ffeb75DCfOSG1Ln_wzP5qKEMJeF6DDSaeSjgOm_hXlKQFZU_dpeSisK3CNuRaLkySBiP2bvACgN0-2pDbNrrgfFFbapyRNydP9GTgxpKFJSWP7K_48lIr41d3xiJYwwRfF/s1600-h/dynamic_content.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 308px; height: 400px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjkkXeLQDQNE4Ffeb75DCfOSG1Ln_wzP5qKEMJeF6DDSaeSjgOm_hXlKQFZU_dpeSisK3CNuRaLkySBiP2bvACgN0-2pDbNrrgfFFbapyRNydP9GTgxpKFJSWP7K_48lIr41d3xiJYwwRfF/s400/dynamic_content.png" alt="" id="BLOGGER_PHOTO_ID_5376393640395877026" border="0" /></a><br /><div>It is a considerable drawback as there are cases in which it's required to do something with property's value before displaying it: </div><div><ul><li>For example you may want to alter all links by adding some extra parameters</li><li>When using <a href="http://marekblotny.blogspot.com/2009/03/tdd-with-episerver.html">Model-View-Presenter</a> (flavor of <a href="http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller">MVC</a>) you want to pass only pure data to the view.<br /></li></ul></div><div>It was worth spending some time to find a workaround. In the end, solution turned out to be not so convoluted, here is a control which takes a string and displays it replacing any dynamic content with corresponding controls. </div><!-- code formatted by http://manoli.net/csharpformat/ --><br /><div class="csharpcode"><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd">public</span> <span class="kwrd">partial</span> <span class="kwrd">class</span> DynamicContent : UserControlBase</pre><pre><span class="lnum"> 2: </span>{</pre><pre class="alt"><span class="lnum"> 3: </span> <span class="kwrd">public</span> <span class="kwrd">string</span> StringToDisplay</pre><pre><span class="lnum"> 4: </span> {</pre><pre class="alt"><span class="lnum"> 5: </span> set</pre><pre><span class="lnum"> 6: </span> {</pre><pre class="alt"><span class="lnum"> 7: </span> var propertyControl = <span class="kwrd">new</span> PropertyLongStringControl</pre><pre><span class="lnum"> 8: </span> {</pre><pre class="alt"><span class="lnum"> 9: </span> Page = Page,</pre><pre><span class="lnum"> 10: </span> PropertyData = <span class="kwrd">new</span> PropertyXhtmlString(<span class="kwrd">value</span>)</pre><pre class="alt"><span class="lnum"> 11: </span> };</pre><pre><span class="lnum"> 12: </span> Controls.Add(propertyControl);</pre><pre class="alt"><span class="lnum"> 13: </span> }</pre><pre><span class="lnum"> 14: </span> }</pre><pre class="alt"><span class="lnum"> 15: </span>}</pre><br /></div><div>And the usage is really straightforward:</div><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><unit:DynamicContent runat=<span class="str">"server"</span> id=<span class="str">"dcMainBody"</span> /></pre><pre><span class="lnum"> 2: </span><br /></pre><pre class="alt"><span class="lnum"> 3: </span>dcMainBody.StringToDisplay = CurrentPage.Property[<span class="str">"MainBody"</span>].ToWebString();</pre><br /></div><div>Before calling dcMainBody.StringToDisplay you can of course change property's value in any arbitrary way.<br /><br />Hope it will save someone a bit of time :)</div>Unknownnoreply@blogger.com45tag:blogger.com,1999:blog-6586401656416839531.post-227477754735561452009-06-30T18:00:00.002+02:002009-06-30T18:00:23.172+02:00EPiServer Composer - How to load content functions defined on a different pageThis in another example of interesting EPiServer Composer (version 3.2.5) use case - on homepage there is a content area called "Bottom Area" which contains number of content functions. I want to display this whole content area on other arbitrary pages.<br /><br />Here is a piece of code which can do this for us:<!-- code formatted by http://manoli.net/csharpformat/ --><br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span>var structure = PageDataManager.LoadPageStruct(homepage.PageLink);</pre><pre><span class="lnum"> 2: </span>var contentArea = structure.GetContentAreaById(AreaName.BOTTOM_AREA_ID);</pre><pre class="alt"><span class="lnum"> 3: </span><span class="kwrd">for</span> (var i = 0; i < contentArea.ContentFunctions.Count; i++)</pre><pre><span class="lnum"> 4: </span>{</pre><pre class="alt"><span class="lnum"> 5: </span> var data = contentArea.ContentFunctions[i];</pre><pre><span class="lnum"> 6: </span> var contentFunction = PageDataManager.InitContentFunctionControl(ExtensionGeneric.ViewMode.ExtensionNormalMode,</pre><pre class="alt"><span class="lnum"> 7: </span> Page, data);</pre><pre><span class="lnum"> 8: </span> plhContainer.Controls.Add(contentFunction);</pre><pre class="alt"><span class="lnum"> 9: </span> </pre><pre><span class="lnum"> 10: </span> var element = contentFunction <span class="kwrd">as</span> IListItemPosition;</pre><pre class="alt"><span class="lnum"> 11: </span> <span class="kwrd">if</span> (element == <span class="kwrd">null</span>) <span class="kwrd">continue</span>;</pre><pre><span class="lnum"> 12: </span><br /></pre><pre class="alt"><span class="lnum"> 13: </span> element.IsLastElement = IsLastElement(i, contentArea);</pre><pre><span class="lnum"> 14: </span> element.IsFirstElement = IsFirstElement(i);</pre><pre class="alt"><span class="lnum"> 15: </span> </pre><pre><span class="lnum"> 16: </span>}</pre></div><br /><span style="font-weight: bold;">Explanation:</span><br /><ol><li>In first two lines I'm using PageDataManager to get instance of ExtensionPageData class for homepage and then I can easily access required content area. Please note that to load data from a different page you just need to provide reference to a different page.</li><li>Heaving an instance of ContentAreaData class (contentArea variable) I have access to the list of all content functions (contentArea.ContentFunctions)</li><li>In lines 5-8 I'm instantiating each content function and then it can be added to the PlaceHolder which is a container for all content functions.</li><li>In lines 10-14 I'm detecting first and last element as those elements may require some special CSS classes. You can read more about <a href="http://marekblotny.blogspot.com/2009/06/episerver-composer-how-to-figure-out.html">IListItemPosition interface and how to figure out which content function is first/last in my previous post</a>.</li></ol>To get everything working you still need those two simple methods:<br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd">private</span> <span class="kwrd">static</span> <span class="kwrd">bool</span> IsFirstElement(<span class="kwrd">int</span> i)</pre><pre><span class="lnum"> 2: </span>{</pre><pre class="alt"><span class="lnum"> 3: </span> <span class="kwrd">return</span> i == 0;</pre><pre><span class="lnum"> 4: </span>}</pre><pre class="alt"><span class="lnum"> 5: </span><br /></pre><pre><span class="lnum"> 6: </span><span class="kwrd">private</span> <span class="kwrd">static</span> <span class="kwrd">bool</span> IsLastElement(<span class="kwrd">int</span> i, ContentAreaData contentArea)</pre><pre class="alt"><span class="lnum"> 7: </span>{</pre><pre><span class="lnum"> 8: </span> <span class="kwrd">return</span> i == contentArea.ContentFunctions.Count - 1;</pre><pre class="alt"><span class="lnum"> 9: </span>}</pre></div><br />I think it's fairly simple but if you have any questions don't hesitate to leave a comment.Unknownnoreply@blogger.com3tag:blogger.com,1999:blog-6586401656416839531.post-88320000181672064812009-06-28T08:26:00.001+02:002009-06-28T10:00:44.703+02:00EPiServer Composer - How to figure out which content function is first (or last) within a content areaIn my previous post (<span style="text-decoration: underline;">EPiServer-based site in 4 weeks?</span>) I have presented number of quite high level thoughts regarding our last EPiServer project. In this and a few forthcoming post I would like to focus more on technical details and present several more interesting EPiServer Composer (version 3.2.5) use cases.<br /><br /><span style="font-weight: bold;">How to figure out which content function is first (or last) within a content area</span>.<br /><br />Imagine following piece of user interface:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEieBlYUpzwmU1An5Gy2dutCaYQzXUdZ5cf83mPOdVvM1WLLvN-gGc-u38t4WD2uK-1FSk7-JTbc6s3_BpRhnybotXBx5umWzCMaxIaKcwk3c4ydxXGfK_X4YYL3TW-1N-xCWi3MNFgFYYCf/s1600-h/composer.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 67px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEieBlYUpzwmU1An5Gy2dutCaYQzXUdZ5cf83mPOdVvM1WLLvN-gGc-u38t4WD2uK-1FSk7-JTbc6s3_BpRhnybotXBx5umWzCMaxIaKcwk3c4ydxXGfK_X4YYL3TW-1N-xCWi3MNFgFYYCf/s400/composer.png" alt="" id="BLOGGER_PHOTO_ID_5352100433818397970" border="0" /></a><br />It's an area where editors can define four arbitrary components. Components can be a text, image + text or just an image. Here is a typical html for this use case:<!-- code formatted by http://manoli.net/csharpformat/ --><br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd"><</span><span class="html">div</span> <span class="attr">class</span><span class="kwrd">="options"</span><span class="kwrd">></span><br /></pre><pre><span class="lnum"> 2: </span> <span class="kwrd"><</span><span class="html">div</span> <span class="attr">class</span><span class="kwrd">="option first"</span><span class="kwrd">></span> ... <span class="kwrd"></</span><span class="html">div</span><span class="kwrd">></span><br /></pre><pre class="alt"><span class="lnum"> 3: </span> <span class="kwrd"><</span><span class="html">div</span> <span class="attr">class</span><span class="kwrd">="option"</span><span class="kwrd">></span> ... <span class="kwrd"></</span><span class="html">div</span><span class="kwrd">></span><br /></pre><pre><span class="lnum"> 4: </span> <span class="kwrd"><</span><span class="html">div</span> <span class="attr">class</span><span class="kwrd">="option"</span><span class="kwrd">></span> ... <span class="kwrd"></</span><span class="html">div</span><span class="kwrd">></span><br /></pre><pre class="alt"><span class="lnum"> 5: </span> <span class="kwrd"><</span><span class="html">div</span> <span class="attr">class</span><span class="kwrd">="option last"</span><span class="kwrd">></span> .... <span class="kwrd"></</span><span class="html">div</span><span class="kwrd">></span><br /></pre><pre><span class="lnum"> 6: </span><span class="kwrd"></</span><span class="html">div</span><span class="kwrd">></span><br /></pre></div><br />Div with class <span style="font-style: italic;">options</span> is a wrapper for all components. Each component has a class <span style="font-style: italic;">option</span>. Quite typically in situation like this, first and last elements are a bit different. Depending on site design it's required to add some visual effects and that is why additional classes are added to the first and last element. This is very flexible approach as changes in html are minimal and final visual effect depends fully on CSS.<br /><br />The problem for developer is that EPiServer Composer allows to simply drag and drop any number of content functions and each function is fairly autonomous. Content function is responsible for displaying html, it has no knowledge about context in which it was used. It doesn't know anything about content area to which it belongs, also it doesn't know anything about other content functions within the same content area. That is why to get information about context you need to operate one level above the functions.<br /><br />But firstly, each content function need to implement following interface:<br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd">public</span> <span class="kwrd">interface</span> IListItemPosition</pre><pre><span class="lnum"> 2: </span>{</pre><pre class="alt"><span class="lnum"> 3: </span> <span class="kwrd">bool</span> IsLastElement { get; set; }</pre><pre><span class="lnum"> 4: </span> <span class="kwrd">bool</span> IsFirstElement { get; set; }</pre><pre class="alt"><span class="lnum"> 5: </span>}</pre></div><br />And now tricky part - changes on the higher level. A few things need to be added to the page/class which inherits from ExtensionBaseTemplate.<br /><br />You need to override OnInit method:<br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd">protected</span> <span class="kwrd">override</span> <span class="kwrd">void</span> OnInit(EventArgs e)</pre><pre><span class="lnum"> 2: </span>{</pre><pre class="alt"><span class="lnum"> 3: </span> CurrentExtensionPageHandler.LoadContentArea += CurrentExtensionPageHandler_LoadContentArea;</pre><pre><span class="lnum"> 4: </span> CurrentExtensionPageHandler.LoadContentFunction += CurrentExtensionPageHandler_LoadContentFunction;</pre><pre class="alt"><span class="lnum"> 5: </span><br /></pre><pre><span class="lnum"> 6: </span> <span class="kwrd">base</span>.OnInit(e);</pre><pre class="alt"><span class="lnum"> 7: </span>}</pre></div>Above code adds our handlers to the following events:<br /><ul><li>LoadContentArea - this way we will get information about all available content areas and what is more important - count of content functions for each area!</li><li>LoadContentFunction - Composer fires this event for each content function. Having a total number of content functions for each area we can easily detect first and last elements.</li></ul><br />Here are implementations for the handlers:<br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="preproc">#region</span> ContentAreaFunctionCounter</pre><pre><span class="lnum"> 2: </span><br /></pre><pre class="alt"><span class="lnum"> 3: </span><span class="kwrd">protected</span> ContentAreaFunctionCounter ContentAreaFunctionCounter = <span class="kwrd">new</span> ContentAreaFunctionCounter();</pre><pre><span class="lnum"> 4: </span><br /></pre><pre class="alt"><span class="lnum"> 5: </span><span class="kwrd">protected</span> <span class="kwrd">void</span> CurrentExtensionPageHandler_LoadContentFunction(<span class="kwrd">object</span> sender, ExtensionPageEventArgs e)</pre><pre><span class="lnum"> 6: </span>{</pre><pre class="alt"><span class="lnum"> 7: </span> var element = e.ContentFunction <span class="kwrd">as</span> IListItemPosition;</pre><pre><span class="lnum"> 8: </span> <span class="kwrd">if</span> (element == <span class="kwrd">null</span>) <span class="kwrd">return</span>;</pre><pre class="alt"><span class="lnum"> 9: </span> </pre><pre><span class="lnum"> 10: </span> element.IsFirstElement = IsFirstElement(e);</pre><pre class="alt"><span class="lnum"> 11: </span> element.IsLastElement = IsLastElement(e);</pre><pre><span class="lnum"> 12: </span>}</pre><pre class="alt"><span class="lnum"> 13: </span><br /></pre><pre><span class="lnum"> 14: </span><span class="kwrd">private</span> <span class="kwrd">static</span> <span class="kwrd">bool</span> IsFirstElement(ExtensionPageEventArgs e)</pre><pre class="alt"><span class="lnum"> 15: </span>{</pre><pre><span class="lnum"> 16: </span> <span class="kwrd">return</span> e.ContentArea.Controls.Count == 0;</pre><pre class="alt"><span class="lnum"> 17: </span>}</pre><pre><span class="lnum"> 18: </span><br /></pre><pre class="alt"><span class="lnum"> 19: </span><span class="kwrd">private</span> <span class="kwrd">bool</span> IsLastElement(ExtensionPageEventArgs e)</pre><pre><span class="lnum"> 20: </span>{</pre><pre class="alt"><span class="lnum"> 21: </span> var numberOfElements = ContentAreaFunctionCounter.GetContentAreaFunctionCount(e.ContentArea.ID);</pre><pre><span class="lnum"> 22: </span> <span class="kwrd">return</span> e.ContentArea.Controls.Count == (numberOfElements - 1);</pre><pre class="alt"><span class="lnum"> 23: </span>}</pre><pre><span class="lnum"> 24: </span><br /></pre><pre class="alt"><span class="lnum"> 25: </span><span class="kwrd">protected</span> <span class="kwrd">void</span> CurrentExtensionPageHandler_LoadContentArea(<span class="kwrd">object</span> sender, ExtensionPageEventArgs e)</pre><pre><span class="lnum"> 26: </span>{</pre><pre class="alt"><span class="lnum"> 27: </span> var functionsCount = e.ContentAreaData == <span class="kwrd">null</span> ? 0 : e.ContentAreaData.ContentFunctions.Count;</pre><pre><span class="lnum"> 28: </span> ContentAreaFunctionCounter.SetContentAreaFunctionCount(e.ContentArea.ID, functionsCount);</pre><pre class="alt"><span class="lnum"> 29: </span>}</pre><pre><span class="lnum"> 30: </span><br /></pre><pre class="alt"><span class="lnum"> 31: </span><span class="preproc">#endregion</span></pre></div><br />Missing part is class ContentAreaFunctionCounter which is responsible only for keeping information about available content areas and functions count:<br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd">public</span> <span class="kwrd">class</span> ContentAreaFunctionCounter</pre><pre><span class="lnum"> 2: </span>{</pre><pre class="alt"><span class="lnum"> 3: </span> <span class="kwrd">private</span> <span class="kwrd">readonly</span> IDictionary<<span class="kwrd">string</span>, <span class="kwrd">int</span>> contentAreaFunctionCount = <span class="kwrd">new</span> Dictionary<<span class="kwrd">string</span>, <span class="kwrd">int</span>>(); </pre><pre><span class="lnum"> 4: </span> </pre><pre class="alt"><span class="lnum"> 5: </span> <span class="kwrd">public</span> <span class="kwrd">void</span> SetContentAreaFunctionCount(<span class="kwrd">string</span> contentArea, <span class="kwrd">int</span> functionCount)</pre><pre><span class="lnum"> 6: </span> {</pre><pre class="alt"><span class="lnum"> 7: </span> contentAreaFunctionCount[contentArea] = functionCount;</pre><pre><span class="lnum"> 8: </span> }</pre><pre class="alt"><span class="lnum"> 9: </span><br /></pre><pre><span class="lnum"> 10: </span> <span class="kwrd">public</span> <span class="kwrd">int</span> GetContentAreaFunctionCount(<span class="kwrd">string</span> contentArea)</pre><pre class="alt"><span class="lnum"> 11: </span> {</pre><pre><span class="lnum"> 12: </span> <span class="kwrd">if</span> (contentAreaFunctionCount.ContainsKey(contentArea))</pre><pre class="alt"><span class="lnum"> 13: </span> {</pre><pre><span class="lnum"> 14: </span> <span class="kwrd">return</span> contentAreaFunctionsCount[contentArea];</pre><pre class="alt"><span class="lnum"> 15: </span> }</pre><pre><span class="lnum"> 16: </span><br /></pre><pre class="alt"><span class="lnum"> 17: </span> <span class="kwrd">return</span> 0;</pre><pre><span class="lnum"> 18: </span> }</pre><pre class="alt"><span class="lnum"> 19: </span>}</pre></div><br />I would recommend creating a base class encapsulating this functionality. Each content function which needs to be aware of it's position just need to implement the IListItemPosition interface. And that's it! :)<br /><br />If it's necessary you can easily extend IListItemPosition interface to contain <span style="font-style: italic;">int Position {get; set;}</span> property and this way give all functions idea about it's position (not just first and last one).Unknownnoreply@blogger.com27tag:blogger.com,1999:blog-6586401656416839531.post-55282997493516235592009-06-18T13:00:00.000+02:002009-06-18T13:00:03.545+02:00EPiServer-based site in 4 weeks?<div>I'm happy to share with you story of a EPiServer-based project that we have delivered a few weeks ago. I have been working on quite a few EPiServer projects but this one was special for number of reasons:</div> <ul><li>We had only four weeks to build the site.</li><li><a href="http://www.episerver.com/en/Products/EPiServer-Composer/">EPiServer Composer</a> was used to build almost whole functionality. </li><li>Extensive usage of Model-View-Presenter design pattern. </li><li>We have put into use idea which is getting more and more popular within <a href="http://cognifide.com/">Cognifide</a> - <a href="http://www.clevegibbon.com/contentmanagement/2009/05/26/content-first/">Content First</a>.</li></ul> <div> </div> <div>Let go though those main points in details ...<br /><br /><br /></div> <div> </div> <div> </div> <div><span style="font-size:130%;"><strong>Four weeks deadline<br /><br /></strong></span></div> <div> </div> <div>Well, that was the biggest challenge, from the very first workshop with the client to the launch day we got only four weeks. Client provided initial designs, but actual PSDs were not ready yet. So we had to understand what client needs, build it and also give client a chance to enter actual content before the deadline. Avoiding incorrect assumptions and misunderstandings is always important but with time frame like this we knew that a single bigger misunderstanding will slow us down enough to miss the deadline. Therefore we decided to use following subset of SCRUM practices:</div> <ul><li>daily scrum meetings - everyday in the morning we were meeting and briefly talking about 1) progress which was made the day before, 2) about plans for given day and 3) about blockers. We considered something as blocker if something was stopping us from finishing the task. It could have been unclear requirement, lack of html template or simply tech problem (with EPiServer Composer for instance).</li><li>retrospective meetings after each iteration - we decided to have one week sprints as it was giving us 3-4 iterations and also 3-4 chances to stop for a while and think about improving our process. After each iteration we were also delivering the latest version to the client. </li></ul> <div>I think I can safely say that without daily meetings and retrospective delivering the site before the deadline would not be possible.<br /><br /></div> <div> </div> <div> </div> <div><strong><br /><span style="font-size:130%;">EPiServer Composer<br /><br /></span></strong></div> <div> </div> <div>Before that project we were mostly only playing with <a href="http://www.episerver.com/en/Products/EPiServer-Composer/">EPiServer Composer</a>. Since the EPiServer Dev Summit 2008, when I had the chance to see X3 presentation, I always wanted to give it a try.<br />If you don't know anything about EPiServer Composer then check this video to get an idea how it can change the way you work with EPiServer:<br /><br /><object width="425" height="344"><param name="movie" value="http://www.youtube.com/v/47dG-WJgv9w&hl=pl&fs=1&color1=0x2b405b&color2=0x6b8ab6"><param name="allowFullScreen" value="true"><param name="allowscriptaccess" value="always"><embed src="http://www.youtube.com/v/47dG-WJgv9w&hl=pl&fs=1&color1=0x2b405b&color2=0x6b8ab6" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="344"></embed></object><br /><br />What is my impression after this project?</div> <ul><li>It's great for small and medium sites, very intuitive, editors were able to understand it, learn it and use it very fast. </li><li>From developer's perspective impression is also positive. It's really great that we can build a function/component that is completely "standalone" and can be reused many times. Although, don't think that you can build for example single generic flash component and reuse it absolutely everywhere. If usability is your main objective then probably you are going to need more the one version of flash component. But still, I like to think about it this way: we can reuse generic flash component every time when it's needed to get something working quickly and then refine it if necessary. </li></ul> <div>Composer was sometimes giving us a hard time but now, when we know how to use it I will be happy to see more projects with Composer on board.<br /><br /><br /></div> <div> </div> <div> </div> <div><span style="font-size:130%;"><strong>Model-View-Presenter</strong></span></div> <div> </div> <div><span style="font-size:130%;"><br /></span>Another fairly new thing for us. We felt that separation of concerns which code behind files offer is not sufficient. In order to get loosely coupled architecture which is later easy to maintain we decided to use MVP design pattern. We don't have any hard, unbiased metrics which can prove it, but my feeling is that MVP gives much better separation, which affects things like:</div> <ul><li>much higher code reusability rate</li><li>much easier refactoring</li><li>because presentation logic is separated from controls it's much easier to spot common parts of code that should be extracted</li><li>presentation logic has no dependency on ASP.NET runtime therefore it can be unit tested (more on that in one of my old posts - <a href="http://marekblotny.blogspot.com/2009/03/tdd-with-episerver.html">TDD with EPiServer</a>)</li><li>it's easier to catch what given module is doing, code readability is increased</li></ul> <div>Before that project MVP was an interesting design pattern for me, now I know that it works in practice. Try it once and you won't be able to live without it ;)<br /><br /></div> <div> </div> <div> </div> <div><strong><br /><span style="font-size:130%;">Content First</span></strong></div> <div> </div> <div><span style="font-size:130%;"><br /></span><a href="http://www.clevegibbon.com/contentmanagement/2009/05/26/content-first/">Content First</a> is almost a buzz word within the <a href="http://cognifide.com/">Cognifide</a>. But seriously, it really makes sense. In our case, it was a requirement to give editors a possibility to work with system before the final release. Editors needed about 2-3 weeks to check how EPiServer works, learn about provided by us functionality, think about content structure and finally for entering "production" content. We had to use Content First approach and work with editors in parallel. What are the implications?</div> <ul><li>Increased complexity - for a few reasons:</li><ul><li>we were not working with test data therefore we had to think about <a href="http://en.wikipedia.org/wiki/Backward_compatibility">backward compatibility</a> or about upgrading content to make sure that it works with the latest code. There was no option like "forget about that component which we delivered last week and start using this new one ... by the way .. you need to recreate all pages that use old component". </li><li>additional environment to deal with - editors need separate server, you need to updated this server frequently. Lots of things can be automated here but there are always some upgrades (new properties, required configuration etc.) that will require your time.</li><li>editors will always find a way to break something, again, you have to deal with it.</li></ul><li>Greater chance to deliver what is really needed - sometimes you can find important requirements only when working with real content, things like incorrect layout of some random components caused by too long title etc. Content itself might be an issue and while working on "Lorem ipsum" data you can't really discover this issues until the very last moment before the deadline.</li><li>Site is well tested - before the deadline site was checked by developers, testers and the client. Client has everything in place, level of confidence that system won't break in a last few moments before the deadline is high.</li><li>Dev team has more time to deliver whole functionality - because client can work with the system there is no need to deliver "final" release two or three weeks before going live and then praying for small number of critical bugs. Editors and developers can work in parallel which gives both sides additional time.</li></ul> <div> </div> <div> </div> <div>It was a cool project to work on, very dynamic, very agile and successful. It was also very busy time with a lot of pressure on us. But in the end we have learnt a lot and I think we all have a feeling that it was a decent achievement. </div>Unknownnoreply@blogger.com5tag:blogger.com,1999:blog-6586401656416839531.post-80234159066098980442009-05-19T08:00:00.007+02:002009-05-19T08:00:00.726+02:00How to define custom styles in EPiServer's Rich Text EditorEPiServer's Rich Text Editor (RTE) can be customized in a number different ways. Unfortunately not all available options are well documented. Defining your own set of styles available in RTE is one of those things.<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhW8L2OumBDSflg3X4KsHkbSIH_jeaNJC2ZzJ0vwiQ-yvQ46hydbrPZ0veaFnalLt85gTTGBkVuzuNxjJcLxsedn2uEsOqqisljoISEhIgGJiOjc-B4IJurS9A4ukv3Lg_Vhw-UupfCFPR3/s1600-h/blog-styles-RTE.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 156px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhW8L2OumBDSflg3X4KsHkbSIH_jeaNJC2ZzJ0vwiQ-yvQ46hydbrPZ0veaFnalLt85gTTGBkVuzuNxjJcLxsedn2uEsOqqisljoISEhIgGJiOjc-B4IJurS9A4ukv3Lg_Vhw-UupfCFPR3/s400/blog-styles-RTE.png" alt="" id="BLOGGER_PHOTO_ID_5337241572851971314" border="0" /></a><br />I always struggle to recall how to change this dropdown even though I have done it number of times. Therefore I have decided to simply write it down. Styles visible above are defined with this code:<br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span>/* EPiServer Editor Class names, required for WYSIWYG editor dropdown */</pre><pre><span class="lnum"> 2: </span>h1 { EditMenuName: Header 1; }</pre><pre class="alt"><span class="lnum"> 3: </span>h2 { EditMenuName: Header 2; }</pre><pre><span class="lnum"> 4: </span>h3 { EditMenuName: Header 3; }</pre><pre class="alt"><span class="lnum"> 5: </span>h4 { EditMenuName: Header 4; }</pre><pre><span class="lnum"> 6: </span>h5 { EditMenuName: Header 5; }<br /><br /></pre></div>Of course your are free to add as many styles as you like, important is to define styles with <span style="font-weight: bold;">EditMenuName</span> keyword.<br /><br />Having CSS ready you still have to make it visible to the editor. In old times, when Edit and Admin mode files were part of each solution it was a matter of editing this file: \Util\styles\editor.css. But nowdays, Edit and Admin mode files are located outside the solution, in Program Files folder, so they are shared among all applications. (since EPiServer 5 R2) Because usually different applications use different styles, and also I would say that it's a good practice to keep all "parts" of application in one place, we can't use old approach anymore. So what is a new approach?<br /><br />Answer is simple, create separate CSS file for your application which will be used by RTE. Creating such file is a good thing anyway as content editors expect similar experience when editing content in edit mode and browsing the site in view mode. If on your site text inside H1 tags is displayed in blue then you should get the same visual result in edit mode. This is the main point of creating special CSS file for RTE. Customization of style dropdown can be done in a meantime :)<br /><br />Let's say that your file is ready, final question is how you can configure EPiServer to load your file in edit mode. It can be done in two ways:<br /><ul><li>in web.config, update EPiServer's stite configuration by changing uiEditorCssPaths property in a following way uiEditorCssPaths="~/css/rte.css"</li><li>or alternatively you can set this property in Admin mode:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4DZVgffkSLZCzZAklzjwpqRgLdZPM4o3W71aCJtIQIaI-U_pu0EoG0jQhVC2TgckIOSJdmnZSxAhU-NXKm7eBY0f1LDDvPzLM5Ke29DHAQRnO2Gi5_AjccezQvWAEqgMhA7GoGlONCEDu/s1600-h/blog-styles-RTE2.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 154px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4DZVgffkSLZCzZAklzjwpqRgLdZPM4o3W71aCJtIQIaI-U_pu0EoG0jQhVC2TgckIOSJdmnZSxAhU-NXKm7eBY0f1LDDvPzLM5Ke29DHAQRnO2Gi5_AjccezQvWAEqgMhA7GoGlONCEDu/s400/blog-styles-RTE2.png" alt="" id="BLOGGER_PHOTO_ID_5337249129195181778" border="0" /></a></li></ul>Outcome of both options is exactly the same, in edit mode Rich Text Editors will "get" your CSS file, dropdown will use your styles and configuration will be saved in web.config.Unknownnoreply@blogger.com7tag:blogger.com,1999:blog-6586401656416839531.post-9938352035448645842009-04-15T07:25:00.000+02:002009-04-15T07:25:00.264+02:00Action<T> and Func<TResult> delegatesIn this post I would like to write a little bit about two types of <a href="http://msdn.microsoft.com/en-us/library/ms173171%28VS.80%29.aspx">delegates</a> which were added recently with ASP.NET 3.5 (<a href="http://msdn.microsoft.com/en-us/library/system.aspx">System Namespace</a>):<br /><br /><a href="http://msdn.microsoft.com/en-us/library/018hxwa8.aspx">Action<T></a> - "Encapsulates a method that takes a single parameter and does not return a value". There are additional versions for two, three and four parameters:<br /><ul><li><a href="http://msdn.microsoft.com/en-us/library/bb549311.aspx">Action<T1, T2></a></li><li><a href="http://msdn.microsoft.com/en-us/library/bb549392.aspx">Action<T1, T2, T3></a></li><li><a href="http://msdn.microsoft.com/en-us/library/bb548654.aspx">Action<T1, T2, T3, T4></a></li></ul>It can be used with method like this:<br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd">public</span> <span class="kwrd">static</span> <span class="kwrd">void</span> UpdatePage(PageData page, Action<PageData> action)</pre><pre><span class="lnum"> 2: </span>{</pre><pre class="alt"><span class="lnum"> 3: </span> var writableClone = page.CreateWritableClone();</pre><pre><span class="lnum"> 4: </span> action(writableClone);</pre><pre class="alt"><span class="lnum"> 5: </span> DataFactory.Instance.Save(writableClone, SaveAction.Publish);</pre><pre><span class="lnum"> 6: </span>}</pre></div><br />To updated page in EPiServer it's required to <a href="http://marekblotny.blogspot.com/2008/03/notsupportedexception-property-title-is.html">create writable clone first</a>, then we can apply our changes and finally instance of PageData has to be saved. This is a procedure which you can probably find in many places in your projects. With delegates you can easily "close" standard steps into a single method and pass changeable part as a parameter, like this:<!-- code formatted by http://manoli.net/csharpformat/ --><br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span>PresenterUtils.UpdatePage(CurrentPage,</pre><pre><span class="lnum"> 2: </span> p =></pre><pre class="alt"><span class="lnum"> 3: </span> {</pre><pre><span class="lnum"> 4: </span> p.Property[<span class="str">"MetaAuthor"</span>].Value = <span class="str">"Marek"</span>;</pre><pre class="alt"><span class="lnum"> 5: </span> p.Property[<span class="str">"MainBody"</span>].Value = <span class="str">"Hello World!"</span>;</pre><pre><span class="lnum"> 6: </span> });</pre><br /></div>I think it should be pretty obvious that the above piece of code changes MetaAuthor and MainBody properties on CurrentPage. For me, main benefit of this approach is increased readability, hence the code is easier to maintain. Note that there is nothing in this call about creating writable clone, calling save method on DataFactory, those are all important and required steps to get things working properly but in the same time they are irrelevant from businesses logic point of view. You don't need to see those "standard" actions to understand what your class/method is doing.<br /><br />You can also use delegates which return a value:<br /><ul><li><a href="http://msdn.microsoft.com/en-us/library/bb534960.aspx">Func<TResult></a></li><li><a href="http://msdn.microsoft.com/en-us/library/bb549151.aspx">Func<T, TResult></a></li><li><a href="http://msdn.microsoft.com/en-us/library/bb534647.aspx">Func<T1, T2, TResult></a></li><li><a href="http://msdn.microsoft.com/en-us/library/bb549430.aspx">Func<T1, T2, T3, TResult></a></li><li><a href="http://msdn.microsoft.com/en-us/library/bb534303.aspx">Func<T1, T2, T3, T4, TResult></a></li></ul>The simples version <a href="http://msdn.microsoft.com/en-us/library/bb534960.aspx">Func<TResult></a> - "encapsulates a method that has no parameters and returns a value of the type specified by the <span class="parameter">TResult</span> parameter."<br /><br />In this example, method returns default value if property doesn't exist or is not set. If property is set then its value can be formatted in some special way:<br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd">public</span> <span class="kwrd">static</span> <span class="kwrd">string</span> GetProperty<T>(PageData page, <span class="kwrd">string</span> propertyName, <span class="kwrd">string</span> defaultValue, Func<T, <span class="kwrd">string</span>> customAction)</pre><pre><span class="lnum"> 2: </span>{</pre><pre class="alt"><span class="lnum"> 3: </span> <span class="kwrd">if</span> (page == <span class="kwrd">null</span> || <span class="kwrd">string</span>.IsNullOrEmpty(propertyName) || page[propertyName] == <span class="kwrd">null</span>)</pre><pre><span class="lnum"> 4: </span> {</pre><pre class="alt"><span class="lnum"> 5: </span> <span class="kwrd">return</span> defaultValue;</pre><pre><span class="lnum"> 6: </span> }</pre><pre class="alt"><span class="lnum"> 7: </span><br /></pre><pre><span class="lnum"> 8: </span> <span class="kwrd">return</span> customAction((T) page[propertyName]);</pre><pre class="alt"><span class="lnum"> 9: </span>}</pre><br /></div>And this is how this method can be used:<br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd">public</span> <span class="kwrd">string</span> GetFormattedDate(PageData page, <span class="kwrd">string</span> format)</pre><pre><span class="lnum"> 2: </span>{</pre><pre class="alt"><span class="lnum"> 3: </span> <span class="kwrd">return</span> PresenterUtils.GetProperty<DateTime>(page, <span class="str">"EventDate"</span>, <span class="str">""</span>, x => x.ToString(format));</pre><pre><span class="lnum"> 4: </span>}</pre></div><br />In the above example we are interested in EventDate property, if this property is not set then empty string (default value) will be returned, otherwise DateTime will be formatted appropriately.<br /><br />Of course, in pre-ASP.NET 3.5 era things like this were also possible but it was required to define your own delegates etc. Now you can use those which are available for you in the framework. Personally I really like lambda expressions and this "way" of crating APIs, it's much more readable for me therefore I encourage you to give it a try! :)Unknownnoreply@blogger.com4tag:blogger.com,1999:blog-6586401656416839531.post-19125155225960210242009-04-10T09:15:00.000+02:002009-04-10T09:17:57.309+02:00Convention over Configuration<p>In this post I would like to introduce design pattern which is particularly close to my heart - <a href="http://en.wikipedia.org/wiki/Convention_over_Configuration">Convention over Configuration</a>. What I like the most about this pattern is that it eliminates lots of monkey code which we have to write from time to time. Firstly let me explain to you what I mean by monkey code.</p><p style="font-weight: bold;">Monkey code</p>Typical case of monkey code can be found in old-fasihion NHibernate mappings:<div class="csharpcode"><br /><pre><span class="lnum"> 1: </span><span class="kwrd"><</span><span class="html">hibernate-mapping</span><span class="kwrd">></span></pre><pre><span class="lnum"> 2: </span> <span class="kwrd"><</span><span class="html">class</span> <span class="attr">name</span><span class="kwrd">="Product"</span> <span class="attr">table</span><span class="kwrd">="tblProduct"</span><span class="kwrd">></span></pre><pre><span class="lnum"> 3: </span> <span class="kwrd"><</span><span class="html">id</span> <span class="attr">name</span><span class="kwrd">="Id"</span> <span class="attr">type</span><span class="kwrd">="Int32"</span> <span class="attr">column</span><span class="kwrd">="ProductID"</span><span class="kwrd">></span></pre><pre><span class="lnum"> 4: </span> <span class="kwrd"><</span><span class="html">generator</span> <span class="attr">class</span><span class="kwrd">="identity"</span> <span class="kwrd">/></span></pre><pre><span class="lnum"> 5: </span> <span class="kwrd"></</span><span class="html">id</span><span class="kwrd">></span></pre><pre><span class="lnum"> 6: </span> <span class="kwrd"><</span><span class="html">property</span> <span class="attr">name</span><span class="kwrd">="ProductLine"</span> <span class="attr">type</span><span class="kwrd">="String"</span><span class="kwrd">></span></pre><pre><span class="lnum"> 7: </span> <span class="kwrd"><</span><span class="html">column</span> <span class="attr">name</span><span class="kwrd">="ProductLine"</span> <span class="attr">length</span><span class="kwrd">="2"</span> <span class="kwrd">/></span></pre><pre><span class="lnum"> 8: </span> <span class="kwrd"></</span><span class="html">property</span><span class="kwrd">></span></pre><pre><span class="lnum"> 9: </span> <span class="kwrd"><</span><span class="html">property</span> <span class="attr">name</span><span class="kwrd">="Class"</span> <span class="attr">type</span><span class="kwrd">="String"</span><span class="kwrd">></span></pre><pre><span class="lnum"> 10: </span> <span class="kwrd"><</span><span class="html">column</span> <span class="attr">name</span><span class="kwrd">="Class"</span> <span class="attr">length</span><span class="kwrd">="2"</span> <span class="kwrd">/></span></pre><pre><span class="lnum"> 11: </span> <span class="kwrd"></</span><span class="html">property</span><span class="kwrd">></span></pre><pre><span class="lnum"> 12: </span> <span class="kwrd"><</span><span class="html">property</span> <span class="attr">name</span><span class="kwrd">="Size"</span> <span class="attr">type</span><span class="kwrd">="String"</span><span class="kwrd">></span></pre><pre><span class="lnum"> 13: </span> <span class="kwrd"><</span><span class="html">column</span> <span class="attr">name</span><span class="kwrd">="Size"</span> <span class="attr">length</span><span class="kwrd">="5"</span> <span class="kwrd">/></span></pre><pre><span class="lnum"> 14: </span> <span class="kwrd"></</span><span class="html">property</span><span class="kwrd">></span></pre><pre><span class="lnum"> 15: </span> <span class="kwrd"><</span><span class="html">property</span> <span class="attr">name</span><span class="kwrd">="DaysToManufacture"</span> <span class="attr">type</span><span class="kwrd">="Int32"</span><span class="kwrd">></span></pre><pre><span class="lnum"> 16: </span> <span class="kwrd"><</span><span class="html">column</span> <span class="attr">name</span><span class="kwrd">="DaysToManufacture"</span> <span class="attr">not-null</span><span class="kwrd">="true"</span> <span class="kwrd">/></span></pre><pre><span class="lnum"> 17: </span> <span class="kwrd"></</span><span class="html">property</span><span class="kwrd">></span></pre><pre><span class="lnum"> 18: </span> <span class="kwrd"><</span><span class="html">property</span> <span class="attr">name</span><span class="kwrd">="ModifiedDate"</span> <span class="attr">type</span><span class="kwrd">="DateTime"</span><span class="kwrd">></span></pre><pre><span class="lnum"> 19: </span> <span class="kwrd"><</span><span class="html">column</span> <span class="attr">name</span><span class="kwrd">="ModifiedDate"</span> <span class="attr">not-null</span><span class="kwrd">="true"</span> <span class="kwrd">/></span></pre><pre><span class="lnum"> 20: </span> <span class="kwrd"></</span><span class="html">property</span><span class="kwrd">></span></pre><pre><span class="lnum"> 21: </span> <span class="kwrd"><</span><span class="html">property</span> <span class="attr">name</span><span class="kwrd">="ListPrice"</span> <span class="attr">type</span><span class="kwrd">="Int32"</span><span class="kwrd">></span></pre><pre><span class="lnum"> 22: </span> <span class="kwrd"><</span><span class="html">column</span> <span class="attr">name</span><span class="kwrd">="ListPrice"</span> <span class="attr">not-null</span><span class="kwrd">="true"</span> <span class="attr">sql-type</span><span class="kwrd">="money"</span> <span class="kwrd">/></span></pre><pre><span class="lnum"> 23: </span> <span class="kwrd"></</span><span class="html">property</span><span class="kwrd">></span></pre><pre><span class="lnum"> 24: </span> <span class="kwrd"></</span><span class="html">class</span><span class="kwrd">></span></pre><pre><span class="lnum"> 25: </span><span class="kwrd"></</span><span class="html">hibernate-mapping</span><span class="kwrd">></span></pre></div><br /><p>Even though in most cases column name and property name are the same, we still have to specify them. There is nothing really creative in this code. I think it's general problem for all sorts of mappings, it doesn't really matter if they are in a form of XML or a plain-old-CLR-objects - someone has to write this pesky piece of code. If you multiply time needed to map single column by number of columns and then by number of tables then you will get huge waste of time.</p><p><span style="font-weight: bold;">Conventions</span></p><p>I like <a href="http://fluentnhibernate.org/">Fluent NHibernate</a> so much because it uses conventions, for instance I can say that all my tables have the same names as my classes and let fluent-nh map everything for me.</p><p> Within one application there might be number of aspects to which conventions can be applied. For instance, in fluent-nh it might be:<br /></p><ul><li>class name vs table name</li><li>property name vs column name</li><li>reference property name vs foreign key column name</li></ul><p></p><p>Another project using conventions is <a href="http://www.asp.net/mvc/">ASP.NET MVC</a>:<br /></p><ul><li>project structure - there are separate folders for Views, Controllers and Model</li><li>views placement - by default ASP.NET MVC framework will try to find view for given controller and action in folder like this: /Views/controllername/actionname.aspx.<br />Action name equals view name.</li></ul>As you can see based on ASP.NET MVC example, conventions are not just to eliminate monkey code, they can be also used to establish common standards for project structure, classes/properties naming etc.<p></p><p><span style="font-weight: bold;">Implementation</span><br /></p><p>Lets try to put this high level ideas into concrete ... how Convention over Configuration can be implemented? I will try to present an algorithm based on fluent-nh and real life scenario where we have to map database tables to classes but all tables in database have "tbl" prefix. Of course we don't want to name our classes with this prefix, also we don't want to specify manually for each class that corresponding table's name is "tbl" + class name.<br /></p>Normally with fluent-nh you would write your own <a href="http://marekblotny.blogspot.com/2009/04/conventions-after-rewrite.html">convention</a> implementing IClassConvention interface, then you would use <a href="http://marekblotny.blogspot.com/2009/02/fluentconfiguration-new-api-to.html">FluentConfiguration</a> class to "fluently" configure your database, add your mappings and also add your custom conventions. Having that you would call BuildSessionFactory() to get instance of properly configured NHibernate's SessionFactory. It's all straightforward but lets look under the hood to see what is going on behind the scene:<br /><ol><li>In the first step fluent-nh loads classes responsible for "convention discovery". Each class from this group is responsible for single convention, this way it's really simple to add new additional conventions later.<br />To find all "discovery" classes you can for instance use reflection to get all classes from a given namespace (flunet-nh's way) or all classes implementing some interface.<br /></li><li>Then it instantiates all mapping classes, custom conventions and default conventions. Default conventions will be used if there are no custom conventions. Be default class name will be equal to table name, we want to change it therefore we have to provide our own convention. It's important to remember that default convention shouldn't overwrite custom one.<br /></li><li>Next, it executes all "convention discovery" classes. Corresponding methods will get as a parameter all mappings and are responsible for finding and applying specific conventions.</li></ol>This is high level algorithm which allows you easily add additional conventions. You can have set of default conventions, but also you can provide your custom one. In general it is important to remember that<br /><ol><li>Any convention (default or custom) shouldn't overwrite explicit configuration!</li><li>Default convention shouldn't overwrite custom conventions!<br /></li></ol><p style="font-weight: bold;">Code samples</p><p>All conventions have to implement this simple interface:</p><div class="csharpcode"><br /><pre><span class="lnum"> 1: </span><span class="kwrd">public</span> <span class="kwrd">interface</span> IConvention<T> : IConvention</pre><pre><span class="lnum"> 2: </span>{</pre><pre><span class="lnum"> 3: </span> <span class="rem">/// <summary></span></pre><pre><span class="lnum"> 4: </span> <span class="rem">/// Whether this convention will be applied to the target.</span></pre><pre><span class="lnum"> 5: </span> <span class="rem">/// </summary></span></pre><pre><span class="lnum"> 6: </span> <span class="rem">/// <param name="target">Instace that could be supplied</param></span></pre><pre><span class="lnum"> 7: </span> <span class="rem">/// <returns>Apply on this target?</returns></span></pre><pre><span class="lnum"> 8: </span> <span class="kwrd">bool</span> Accept(T target);</pre><pre><span class="lnum"> 9: </span><br /></pre><pre><span class="lnum"> 10: </span> <span class="rem">/// <summary></span></pre><pre><span class="lnum"> 11: </span> <span class="rem">/// Apply changes to the target</span></pre><pre><span class="lnum"> 12: </span> <span class="rem">/// </summary></span></pre><pre><span class="lnum"> 13: </span> <span class="rem">/// <param name="target">Instance to apply changes to</param></span></pre><pre><span class="lnum"> 14: </span> <span class="kwrd">void</span> Apply(T target);</pre><pre><span class="lnum"> 15: </span>}</pre></div><br />Accept() method allows you to have number of conventions of given type and use them based on your custom logic.<br /><br />This is how "discovery" is implemented:<br /><div class="csharpcode"><br /><pre><span class="lnum"> 1: </span><span class="kwrd">public</span> <span class="kwrd">void</span> Apply(IEnumerable<IClassMap> classes)</pre><pre><span class="lnum"> 2: </span>{</pre><pre><span class="lnum"> 3: </span> var conventions = conventionFinder.Find<IClassConvention>();</pre><pre><span class="lnum"> 4: </span><br /></pre><pre><span class="lnum"> 5: </span> <span class="kwrd">foreach</span> (var classMap <span class="kwrd">in</span> classes)</pre><pre><span class="lnum"> 6: </span> {</pre><pre><span class="lnum"> 7: </span> <span class="kwrd">foreach</span> (var classConvention <span class="kwrd">in</span> conventions)</pre><pre><span class="lnum"> 8: </span> {</pre><pre><span class="lnum"> 9: </span> <span class="kwrd">if</span> (classConvention.Accept(classMap))</pre><pre><span class="lnum"> 10: </span> classConvention.Apply(classMap);</pre><pre><span class="lnum"> 11: </span> }</pre><pre><span class="lnum"> 12: </span> }</pre><pre><span class="lnum"> 13: </span>}</pre><br /></div>ConventionFinder returns all objects implementing IClassConvention interface, then it applies this all conventions to all mappings.<br /><br />Finally, our convention which will add "tbl" prefix for table name:<br /><!-- code formatted by http://manoli.net/csharpformat/ --><br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd">public</span> <span class="kwrd">class</span> TableNameConvention : IClassConvention</pre><pre><span class="lnum"> 2: </span>{</pre><pre class="alt"><span class="lnum"> 3: </span> <span class="kwrd">public</span> <span class="kwrd">bool</span> Accept(IClassMap target)</pre><pre><span class="lnum"> 4: </span> {</pre><pre class="alt"><span class="lnum"> 5: </span> <span class="kwrd">return</span> <span class="kwrd">string</span>.IsNullOrEmpty(target.TableName);</pre><pre><span class="lnum"> 6: </span> }</pre><pre class="alt"><span class="lnum"> 7: </span><br /></pre><pre><span class="lnum"> 8: </span> <span class="kwrd">public</span> <span class="kwrd">void</span> Apply(IClassMap target)</pre><pre class="alt"><span class="lnum"> 9: </span> {</pre><pre><span class="lnum"> 10: </span> target.WithTable(<span class="str">"tbl"</span> + target.EntityType.Name);</pre><pre class="alt"><span class="lnum"> 11: </span> }</pre><pre><span class="lnum"> 12: </span>}</pre></div><br />And a few interesting pieces of code which I found in fluent-nh sources:<br /><!-- code formatted by http://manoli.net/csharpformat/ --><br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd">private</span> <span class="kwrd">void</span> AddDefaultConventions()</pre><pre><span class="lnum"> 2: </span>{</pre><pre class="alt"><span class="lnum"> 3: </span> <span class="kwrd">foreach</span> (var foundType <span class="kwrd">in</span> from type <span class="kwrd">in</span> <span class="kwrd">typeof</span>(PersistenceModel).Assembly.GetTypes()</pre><pre><span class="lnum"> 4: </span> <span class="kwrd">where</span> type.Namespace == <span class="kwrd">typeof</span>(TableNameConvention).Namespace && !type.IsAbstract</pre><pre class="alt"><span class="lnum"> 5: </span> select type)</pre><pre><span class="lnum"> 6: </span> {</pre><pre class="alt"><span class="lnum"> 7: </span> ConventionFinder.Add(foundType);</pre><pre><span class="lnum"> 8: </span> }</pre><pre class="alt"><span class="lnum"> 9: </span>}</pre></div><br />The above method finds all default conventions. Next method instantiates type using default constructor or the one which takes (in this case) ConventionFinder as a parameter:<br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd">private</span> <span class="kwrd">object</span> Instantiate(Type type)</pre><pre><span class="lnum"> 2: </span>{</pre><pre class="alt"><span class="lnum"> 3: </span> <span class="kwrd">object</span> instance = <span class="kwrd">null</span>;</pre><pre><span class="lnum"> 4: </span><br /></pre><pre class="alt"><span class="lnum"> 5: </span> <span class="kwrd">foreach</span> (var constructor <span class="kwrd">in</span> type.GetConstructors())</pre><pre><span class="lnum"> 6: </span> {</pre><pre class="alt"><span class="lnum"> 7: </span> <span class="kwrd">if</span> (IsFinderConstructor(constructor))</pre><pre><span class="lnum"> 8: </span> instance = constructor.Invoke(<span class="kwrd">new</span>[] { <span class="kwrd">this</span> });</pre><pre class="alt"><span class="lnum"> 9: </span> <span class="kwrd">else</span> <span class="kwrd">if</span> (IsParameterlessConstructor(constructor))</pre><pre><span class="lnum"> 10: </span> instance = constructor.Invoke(<span class="kwrd">new</span> <span class="kwrd">object</span>[] { });</pre><pre class="alt"><span class="lnum"> 11: </span> }</pre><pre><span class="lnum"> 12: </span><br /></pre><pre class="alt"><span class="lnum"> 13: </span> <span class="kwrd">return</span> instance;</pre><pre><span class="lnum"> 14: </span>} </pre></div><br />I hope that this post gives you much better idea about Convention over Configuration in general but also explains how to implement this pattern in practice. I encourage you to give it a try instead of writing monkey code.<br /><br />If you encountered different cases where CoC fits perfectly I would be very interested to hear about it, leave a comment or send me an email. Or maybe you in general disagree ... leave a comment ... it will be interesting to know your view on this.Unknownnoreply@blogger.com5tag:blogger.com,1999:blog-6586401656416839531.post-64267304448518790612009-04-01T11:45:00.000+02:002009-04-01T11:45:43.788+02:00Conventions - After RewriteIn December I have written a post about <a href="http://marekblotny.blogspot.com/2008/12/fluent-nhibernate-conventions-and.html">Conventions and AutoPersistenceModel</a> in Fluent NHibernate. Since then lots of things have changed, especially with conventions, in this post I would like to show how to accomplish the same, old goals with new API.<br /><br />To recap quickly, we have two following tables:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGAtTUuHzlqnGD0WjcZ0qFuQzr860vEYshZnA1XPVDSHYZGV4RVwlHc4N_1bmyRZT6kmuCSAKU_VmCgwic7Av8tQwrddyjW-eYODL8b9-25MwbSL__swSTePhIhd1Sc0jJq8SLq4Zo7tEj/s1600-h/adventureWorks-1.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 205px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGAtTUuHzlqnGD0WjcZ0qFuQzr860vEYshZnA1XPVDSHYZGV4RVwlHc4N_1bmyRZT6kmuCSAKU_VmCgwic7Av8tQwrddyjW-eYODL8b9-25MwbSL__swSTePhIhd1Sc0jJq8SLq4Zo7tEj/s400/adventureWorks-1.png" alt="" id="BLOGGER_PHOTO_ID_5279768782809524578" border="0" /></a>The issues which we are going to solve with conventions include:<br /><blockquote>AdventureWorks database doesn't follow the default convention. Differences:<br /><ul><li>table name is different then class name (Product vs Production.Product)</li><li>id property has different name then primary key column (Id vs ProductID)</li><li>properties representing links between tables have different names then foreign key column names (Product vs ProductID)</li></ul></blockquote><br />Here is how we can create conventions which are specific for AdventureWorks database:<br /><br /><span class="Apple-style-span" style="font-weight: bold;">IClassConvention</span><br /><div><br /></div><div>By default class name equals table name. If for some reason it's not true in your case then basically you have two options:</div><div><ul><li>in each mapping class you can call WithTable(...) method and pass as a parameter your custom table name </li><li>create your own convention by implementing IClassConvention<br /></li></ul><div>Of course second option is preferable in most cases, IClassConvention interface has to methods which need to be implemented:</div><div><br /></div></div><div class="csharpcode"><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd">public</span> <span class="kwrd">bool</span> Accept(IClassMap target)</pre><pre><span class="lnum"> 2: </span>{</pre><pre class="alt"><span class="lnum"> 3: </span> <span class="rem">// apply this convention if table wasn't specified with WithTable(..) method</span></pre><pre><span class="lnum"> 4: </span> <span class="kwrd">return</span> <span class="kwrd">string</span>.IsNullOrEmpty(target.TableName);</pre><pre class="alt"><span class="lnum"> 5: </span>}</pre><pre><span class="lnum"> 6: </span> </pre><pre class="alt"><span class="lnum"> 7: </span><span class="kwrd">public</span> <span class="kwrd">void</span> Apply(IClassMap classMap)</pre><pre><span class="lnum"> 8: </span>{</pre><pre class="alt"><span class="lnum"> 9: </span> <span class="rem">// table name can be like: </span></pre><pre><span class="lnum"> 10: </span> <span class="rem">// "Production." + class name or "Sales." + class name</span></pre><pre class="alt"><span class="lnum"> 11: </span> <span class="rem">// "Production" and "Sales" are the last parts of the namespace </span></pre><pre><span class="lnum"> 12: </span> </pre><pre class="alt"><span class="lnum"> 13: </span> var lastElementOfNamespace = classMap.EntityType.Namespace</pre><pre><span class="lnum"> 14: </span> .Substring(classMap.EntityType.Namespace.LastIndexOf(<span class="str">'.'</span>) + 1);</pre><pre class="alt"><span class="lnum"> 15: </span> </pre><pre><span class="lnum"> 16: </span> classMap.WithTable(String.Format(<span class="str">"{0}.{1}"</span>, lastElementOfNamespace, classMap.EntityType.Name));</pre><pre class="alt"><span class="lnum"> 17: </span>}</pre></div><br />I think comments explain everything but I would like to stress the way Accept() method was implemented in. It's important to remeber that we don't always want to apply our conventions! If, for some reason, in mapping class WithTable(...) method was called to set specific table name then we shouldn't overwrite it. <div><br /></div><div><span class="Apple-style-span" style="font-weight: bold;">IIdConvention</span></div><div><span class="Apple-style-span" style="font-weight: bold;"><br /></span>Default convention for identity properties is also fairly straightforward - property name equals column name. If you need to change it then again there are two options:</div><div><ul><li>in mapping class you can specify column name like this: <span class="Apple-style-span" style="font-style: italic;">Id(x => x.Id, "ProductID");</span> <br /></li><li>or implement IIdConvention and provide your own convention</li></ul><div><br /></div></div><!-- code formatted by http://manoli.net/csharpformat/ --><div class="csharpcode"><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd">public</span> <span class="kwrd">bool</span> Accept(IIdentityPart target)</pre><pre><span class="lnum"> 2: </span>{</pre><pre class="alt"><span class="lnum"> 3: </span> <span class="rem">// make sure that column name wasn't set by calling Id(x => x.Id, "...")</span></pre><pre><span class="lnum"> 4: </span> <span class="kwrd">return</span> <span class="kwrd">string</span>.IsNullOrEmpty(target.GetColumnName());</pre><pre class="alt"><span class="lnum"> 5: </span>}</pre><pre><span class="lnum"> 6: </span> </pre><pre class="alt"><span class="lnum"> 7: </span><span class="kwrd">public</span> <span class="kwrd">void</span> Apply(IIdentityPart target)</pre><pre><span class="lnum"> 8: </span>{</pre><pre class="alt"><span class="lnum"> 9: </span> <span class="rem">// primary key = class name + "ID"</span></pre><pre><span class="lnum"> 10: </span> target.ColumnName(target.EntityType.Name + <span class="str">"ID"</span>);</pre><pre class="alt"><span class="lnum"> 11: </span>}</pre></div>A few words of explanation, in database, primary keys are named like ProductID, ProductReviewID whereas in our class I want to stay with simple Id property for each class. Hence, to build column name it's required to use class name (for instance <span class="Apple-style-span" style="font-style: italic;">Product</span>) and add "ID".<br /><br /><span class="Apple-style-span" style="font-weight: bold;">IReferenceConvention and IHasManyConvention</span><br /><!-- code formatted by http://manoli.net/csharpformat/ --><br /><div>Last issue to deal with are foreign keys. ProductReview table has foreign key to Product table, as usually, property name (Product) is different then column name (ProductID) and therefore instead of specifing that (<span class="Apple-style-span" style="font-style: italic;">References(x => x.Product, "ProductID");</span>) we can implement IReferenceConvention interface:<br /><!-- code formatted by http://manoli.net/csharpformat/ --><br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd">public</span> <span class="kwrd">bool</span> Accept(IManyToOnePart target)</pre><pre><span class="lnum"> 2: </span>{</pre><pre class="alt"><span class="lnum"> 3: </span> <span class="rem">// make sure that column name wasn't set with References(x => x.Product, "...");</span></pre><pre><span class="lnum"> 4: </span> <span class="kwrd">return</span> <span class="kwrd">string</span>.IsNullOrEmpty(target.GetColumnName());</pre><pre class="alt"><span class="lnum"> 5: </span>}</pre><pre><span class="lnum"> 6: </span> </pre><pre class="alt"><span class="lnum"> 7: </span><span class="kwrd">public</span> <span class="kwrd">void</span> Apply(IManyToOnePart target)</pre><pre><span class="lnum"> 8: </span>{</pre><pre class="alt"><span class="lnum"> 9: </span> <span class="rem">// foreign key column name = property name + "ID"</span></pre><pre><span class="lnum"> 10: </span> <span class="rem">//</span></pre><pre class="alt"><span class="lnum"> 11: </span> <span class="rem">// it will be used in case like this:</span></pre><pre><span class="lnum"> 12: </span> <span class="rem">// <many-to-one name="Product" column="ProductID" /></span></pre><pre class="alt"><span class="lnum"> 13: </span> </pre><pre><span class="lnum"> 14: </span> target.ColumnName(target.Property.Name + <span class="str">"ID"</span>);</pre><pre class="alt"><span class="lnum"> 15: </span>}</pre><br /></div>And to map easily other side of this association, without spcifying key column names we have to implement IHasManyConvention:<br /><div class="csharpcode"><br /></div><div class="csharpcode"><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd">public</span> <span class="kwrd">bool</span> Accept(IOneToManyPart target)</pre><pre><span class="lnum"> 2: </span>{</pre><pre class="alt"><span class="lnum"> 3: </span> <span class="kwrd">return</span> target.KeyColumnNames.List().Count == 0;</pre><pre><span class="lnum"> 4: </span>}</pre><pre class="alt"><span class="lnum"> 5: </span> </pre><pre><span class="lnum"> 6: </span><span class="kwrd">public</span> <span class="kwrd">void</span> Apply(IOneToManyPart target)</pre><pre class="alt"><span class="lnum"> 7: </span>{</pre><pre><span class="lnum"> 8: </span> <span class="rem">// foreign key column name = class name + "ID" </span></pre><pre class="alt"><span class="lnum"> 9: </span> <span class="rem">//</span></pre><pre><span class="lnum"> 10: </span> <span class="rem">// it will be used to set key column in example like this:</span></pre><pre class="alt"><span class="lnum"> 11: </span> <span class="rem">//</span></pre><br /><pre><span class="lnum"> 12: </span> <span class="rem">// <bag name="ProductReview" inverse="true"></span></pre><pre class="alt"><span class="lnum"> 13: </span> <span class="rem">// <key column="ProductID" /></span></pre><pre><span class="lnum"> 14: </span> <span class="rem">// <one-to-many class="AdventureWorksPlayground...ProductReview, AdventureWorksPlayground, ..." /></span></pre><pre class="alt"><span class="lnum"> 15: </span> <span class="rem">// </bag></span></pre><pre><span class="lnum"> 16: </span> </pre><pre class="alt"><span class="lnum"> 17: </span> target.KeyColumnNames.Add(target.EntityType.Name + <span class="str">"ID"</span>);</pre><pre><span class="lnum"> 18: </span> target.LazyLoad();</pre><pre class="alt"><span class="lnum"> 19: </span> target.Inverse();</pre><pre><span class="lnum"> 20: </span>}</pre><br /></div>Please note that in last example we are not only setting key column name but also some other parameters like lazy load and inverse. </div><div><br /></div><div><span class="Apple-style-span" style="font-weight: bold;">Last words</span></div><div><br /></div><div>As you can see with new API you need to write slightly more lines of code to achieve the same effect but I think it's a fair trade off for much greater control and flexibility. </div><div><br /></div><div>This post is just "touching" the subject, you can find much more about <a href="http://wiki.fluentnhibernate.org/show/Conventions">conventions</a> and different <a href="http://wiki.fluentnhibernate.org/show/ConventionsInterfaces">interfaces</a> on wiki. </div><div><br /></div><div>Related posts:</div><div><ul><li><a href="http://marekblotny.blogspot.com/2009/02/fluentconfiguration-new-api-to.html">Configure NHibernate with FluentConfiguration</a></li><li><a href="http://marekblotny.blogspot.com/2009/03/fluent-nhibernate-and-inheritance.html">Inheritance Mapping with Fluent NHibernate</a></li><li><a href="http://marekblotny.blogspot.com/2009/02/fluent-nhbernate-and-collections.html">Collections Mapping with Fluent NHibernate</a></li></ul></div>Unknownnoreply@blogger.com4tag:blogger.com,1999:blog-6586401656416839531.post-15632295012662436032009-03-26T13:25:00.005+01:002009-03-27T14:06:29.169+01:00TDD with EPiServerDuring the last Demo Day (conference organized by <a href="http://www.cognifide.com/">Cognifide</a> every 3-4 months) I had a chance to talk about <a href="http://en.wikipedia.org/wiki/Test-driven_development">Test Driven Development</a> in EPiServer projects. In this post I would like to recap main points of my presentation.<br /><br /><span style="font-weight: bold;">Obstacles on the way to TDD</span><br /><br />Unfortunately it's very hard to apply TDD to ASP.NET WebForms as it is. WebForms was designed as a platform for <a href="http://en.wikipedia.org/wiki/Rapid_application_development">Rapid Application Development</a>. Features like wide set of rich controls and state management (view state) can significantly speed up development but in the same time WebForms does not provide decent separation of concerns. I think code-behind files were suppose to give us some separation between the HTML and application logic. And to be fair it's "some" separation, but we still need to deal in one code-behind file with ASP.NET components, which are tightly bound to the underlying HttpContext, presentation logic and usually a few other things. It's clearly too much. There are way too many concerns in one place, too many dependencies on ASP.NET runtime. It makes code in applications like this hard to maintain and also makes writing unit test impossible.<br /><br />Another thing with which you have to cope with is EPiServer. To get an idea of problems which you may encounter check this unit tests:<br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span>[Test]</pre><pre><span class="lnum"> 2: </span><span class="kwrd">public</span> <span class="kwrd">void</span> MyEPiServerUnitTest()</pre><pre class="alt"><span class="lnum"> 3: </span>{</pre><pre><span class="lnum"> 4: </span> var page = <span class="kwrd">new</span> PageData();</pre><pre class="alt"><span class="lnum"> 5: </span> page.Property.Add(<span class="str">"MetaAuthor"</span>, <span class="kwrd">new</span> PropertyString { Value = <span class="str">"MetaAuthor123"</span> });</pre><pre><span class="lnum"> 6: </span> page.Property.Add(<span class="str">"Heading"</span>, <span class="kwrd">new</span> PropertyString { Value = <span class="str">"Heading123"</span> });</pre><pre class="alt"><span class="lnum"> 7: </span> page.Property.Add(<span class="str">"MainBody"</span>, <span class="kwrd">new</span> PropertyXhtmlString { Value = <span class="str">"MainBody123"</span> });</pre><pre><span class="lnum"> 8: </span><br /></pre><pre class="alt"><span class="lnum"> 9: </span> Assert.IsTrue(page[<span class="str">"MainBody"</span>].Equals(<span class="str">"MainBody123"</span>));</pre><pre><span class="lnum"> 10: </span>}</pre><br /></div>Basically I'm creating PageData instance with a few properties and then I'm checking if correct values will be returned. This is probably the simplest unit test ever. So if you run it then you don't expect anything else then a green bar right? Surprisingly, instead of green bar you will get exception saying:<br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> </span>System.ApplicationException: First time you call Settings.Instance you must have a valid HttpContext.</pre></div><br />If you want to know who you should blame for calling Settings.Instance then check PropertyXhtmlString :)<br /><br />As you may know, HttpContext can be mocked, it's not a problem. But in our case it won't help much, we won't get this exception of course, but we will get different one instead.<br /><br />As you can see the way to TDD is not easy, EPiServer is also closely bound to HttpContext.<br /><br /><span style="font-weight: bold;">Deal with problems in two steps</span><br /><br />Step 1 - Isolate as much functionality as you can away from ASP.NET runtime. How? Introduce design pattern called Model-View-Presenter (MVP). This pattern divides the responsibilities into separate classes:<br /><ul><li>View - those are *.aspx and *.ascx files including code-behind files. This layer should be as thin as possible, code-behind files should delegate as much functionality as possible to the Presenter.</li><li>Presenter is responsible for interface logic which includes initialization of view and dealing with all events. It's important that presenter shouldn't have any reference to System.Web namespace and should be separated from the concrete view with an interface. Additionally presenter has access to business layer and/or backend services.</li><li>Model - the data on which application is working on, for EPiServer those might be pages, users, files, data stored in the HttpSession etc.<br /></li></ul>It's worth mentioning that in general there are two variations of MVP design pattern:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOaVZ4_3CiMaBC2RYf6_lLdMK06ARnzI8b2NeFV_mOJ2ZBoifdNYqf0tBRvYqnNofpr7ii7dmq3YoafOViRf2ifMKEadN95Oxjyg_JRG6tQ4pBnkyiG9IdtVwlBa3HurOZ0xSDfB9juD0k/s1600-h/model-view-presenter.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 233px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOaVZ4_3CiMaBC2RYf6_lLdMK06ARnzI8b2NeFV_mOJ2ZBoifdNYqf0tBRvYqnNofpr7ii7dmq3YoafOViRf2ifMKEadN95Oxjyg_JRG6tQ4pBnkyiG9IdtVwlBa3HurOZ0xSDfB9juD0k/s400/model-view-presenter.png" alt="" id="BLOGGER_PHOTO_ID_5317256588103694722" border="0" /></a>There are minor differences between both versions, you can learn more about both versions on Martin Fowler's site: <a href="http://martinfowler.com/eaaDev/PassiveScreen.html">Passive View</a> and <a href="http://martinfowler.com/eaaDev/SupervisingPresenter.html">Supervising Controller</a>.<br /><br />Let me show you how it works in practice. I'm going to implement well known news page from EPiServer's public templates, the page for visitors looks like this:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtKLKn5V6jrJ8bCSwBRbMaqMnGRBtPsRwLYxFJMLdZRDZES2m6FcNpsGOA64go5RPa02I2hBnWm1JKylCbZOCk9s_OMnB_YabFdDzRXFa2JCw1u5vGKgYF4kLfsqJY-cbPLwkg6yeaGE8C/s1600-h/blog1.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 188px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtKLKn5V6jrJ8bCSwBRbMaqMnGRBtPsRwLYxFJMLdZRDZES2m6FcNpsGOA64go5RPa02I2hBnWm1JKylCbZOCk9s_OMnB_YabFdDzRXFa2JCw1u5vGKgYF4kLfsqJY-cbPLwkg6yeaGE8C/s400/blog1.png" alt="" id="BLOGGER_PHOTO_ID_5317424904245323810" border="0" /></a><br />Areas which are set by news page are marked with red rectangles. Other data comes from MasterPage. Below you can check how the aspx file:<br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd"><</span><span class="html">asp:Content</span> <span class="attr">ID</span><span class="kwrd">="Content4"</span> <span class="attr">ContentPlaceHolderID</span><span class="kwrd">="MainBodyRegion"</span> <span class="attr">runat</span><span class="kwrd">="server"</span><span class="kwrd">></span></pre><pre><span class="lnum"> 2: </span> <span class="kwrd"><</span><span class="html">div</span> <span class="attr">id</span><span class="kwrd">="MainBody"</span><span class="kwrd">></span></pre><pre class="alt"><span class="lnum"> 3: </span> <span class="kwrd"><</span><span class="html">h1</span><span class="kwrd">><</span><span class="html">asp:Literal</span> <span class="attr">ID</span><span class="kwrd">="litHeading"</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="kwrd">/></</span><span class="html">h1</span><span class="kwrd">></span></pre><pre><span class="lnum"> 4: </span> <span class="kwrd"><</span><span class="html">asp:Literal</span> <span class="attr">ID</span><span class="kwrd">="litMainBody"</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="kwrd">/></span></pre><pre class="alt"><span class="lnum"> 5: </span> <span class="kwrd"></</span><span class="html">div</span><span class="kwrd">></span> </pre><pre><span class="lnum"> 6: </span><span class="kwrd"></</span><span class="html">asp:Content</span><span class="kwrd">></span></pre><pre class="alt"><span class="lnum"> 7: </span><br /></pre><pre><span class="lnum"> 8: </span><br /></pre><pre class="alt"><span class="lnum"> 9: </span><span class="kwrd"><</span><span class="html">asp:Content</span> <span class="attr">ID</span><span class="kwrd">="Content5"</span> <span class="attr">ContentPlaceHolderID</span><span class="kwrd">="SecondaryBodyRegion"</span> <span class="attr">runat</span><span class="kwrd">="server"</span><span class="kwrd">></span></pre><pre><span class="lnum"> 10: </span> <span class="kwrd"><</span><span class="html">div</span> <span class="attr">id</span><span class="kwrd">="SecondaryBody"</span><span class="kwrd">></span></pre><pre class="alt"><span class="lnum"> 11: </span> <span class="kwrd"><</span><span class="html">dl</span><span class="kwrd">></span></pre><pre><span class="lnum"> 12: </span> <span class="kwrd"><</span><span class="html">dt</span><span class="kwrd">><</span><span class="html">EPiServer:Translate</span> <span class="attr">ID</span><span class="kwrd">="author"</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="attr">Text</span><span class="kwrd">="/news/writername"</span> <span class="kwrd">/></</span><span class="html">dt</span><span class="kwrd">></span></pre><pre class="alt"><span class="lnum"> 13: </span> <span class="kwrd"><</span><span class="html">dd</span><span class="kwrd">><</span><span class="html">asp:Literal</span> <span class="attr">ID</span><span class="kwrd">="litMetaAuthor"</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="kwrd">/></</span><span class="html">dd</span><span class="kwrd">></span></pre><pre><span class="lnum"> 14: </span> <span class="kwrd"><</span><span class="html">dt</span><span class="kwrd">><</span><span class="html">EPiServer:Translate</span> <span class="attr">ID</span><span class="kwrd">="Translate1"</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="attr">Text</span><span class="kwrd">="/news/publishdate"</span> <span class="kwrd">/></</span><span class="html">dt</span><span class="kwrd">></span></pre><pre class="alt"><span class="lnum"> 15: </span> <span class="kwrd"><</span><span class="html">dd</span><span class="kwrd">><</span><span class="html">asp:Literal</span> <span class="attr">ID</span><span class="kwrd">="litPageStartPublish"</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="kwrd">/></</span><span class="html">dd</span><span class="kwrd">></span></pre><pre><span class="lnum"> 16: </span> <span class="kwrd"></</span><span class="html">dl</span><span class="kwrd">></span></pre><pre class="alt"><span class="lnum"> 17: </span><span class="kwrd"></</span><span class="html">div</span><span class="kwrd">></span></pre><pre><span class="lnum"> 18: </span><span class="kwrd"></</span><span class="html">asp:Content</span><span class="kwrd">></span></pre></div><br />As you can see, so far nothing surprising. But now, instead of jumping to the code-behind file, you should create an interface for the view. I'm going to call my interface INewsView and this is how it looks like:<br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd">public</span> <span class="kwrd">interface</span> INewsView</pre><pre><span class="lnum"> 2: </span>{</pre><pre class="alt"><span class="lnum"> 3: </span> String Heading { get; set; }</pre><pre><span class="lnum"> 4: </span> String MainBody { get; set; }</pre><pre class="alt"><span class="lnum"> 5: </span> String MetaAuthor { get; set; }</pre><pre><span class="lnum"> 6: </span> String PageStartPublish { get; set; }</pre><pre class="alt"><span class="lnum"> 7: </span>}<br /><br /></pre></div>INewsView interface defines a contract between presenter and view. View needs to get above data from presenter and presenter is obligated to deliver them.<br /><br />Having the interface ready we can start working on the presenter, the simplest implementation looks like this:<br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd">public</span> <span class="kwrd">class</span> NewsPresenter : AbstractPresenterBase<INewsView></pre><pre><span class="lnum"> 2: </span>{</pre><pre class="alt"><span class="lnum"> 3: </span> <span class="kwrd">public</span> <span class="kwrd">override</span> <span class="kwrd">void</span> Initialize()</pre><pre><span class="lnum"> 4: </span> {</pre><pre class="alt"><span class="lnum"> 5: </span> View.MetaAuthor = PresenterUtils.GetPropertyValue(CurrentPage, <span class="str">"MetaAuthor"</span>, <span class="str">""</span>);</pre><pre><span class="lnum"> 6: </span> View.PageStartPublish = PresenterUtils.GetProperty<DateTime>(CurrentPage, <span class="str">"PageStartPublish"</span>, <span class="str">""</span>,</pre><pre class="alt"><span class="lnum"> 7: </span> x => x.ToShortDateString());</pre><pre><span class="lnum"> 8: </span><br /></pre><pre class="alt"><span class="lnum"> 9: </span> View.Heading = PresenterUtils.GetPropertyValue(CurrentPage, <span class="str">"Heading"</span>, <span class="str">""</span>);</pre><pre><span class="lnum"> 10: </span> View.MainBody = PresenterUtils.GetPropertyValue(CurrentPage, <span class="str">"MainBody"</span>, <span class="str">""</span>);</pre><pre class="alt"><span class="lnum"> 11: </span> }</pre><pre><span class="lnum"> 12: </span>}</pre><br /></div>PresenterUtils class is just my helper which is returning values of properties if they are set, or default values otherwise. There is no magic here but as you may notice, logic dealing with CurrentPage is located in one place which makes it much more reusable.<br /><br />Now we have NewsPresenter ready we can piece it all together. The only missing part is implementation of INewsView interface:<br /><!-- code formatted by http://manoli.net/csharpformat/ --><br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd">public</span> <span class="kwrd">partial</span> <span class="kwrd">class</span> NewsItem : SimpleMVPPageBase<NewsPresenter, INewsView>, INewsView</pre><pre><span class="lnum"> 2: </span>{</pre><pre class="alt"><span class="lnum"> 3: </span> <span class="kwrd">protected</span> <span class="kwrd">override</span> INewsView ViewImplementation</pre><pre><span class="lnum"> 4: </span> {</pre><pre class="alt"><span class="lnum"> 5: </span> get { <span class="kwrd">return</span> <span class="kwrd">this</span>; }</pre><pre><span class="lnum"> 6: </span> }</pre><pre class="alt"><span class="lnum"> 7: </span><br /></pre><pre><span class="lnum"> 8: </span> <span class="kwrd">public</span> String MetaAuthor</pre><pre class="alt"><span class="lnum"> 9: </span> {</pre><pre><span class="lnum"> 10: </span> get { <span class="kwrd">return</span> litMetaAuthor.Text; }</pre><pre class="alt"><span class="lnum"> 11: </span> set { litMetaAuthor.Text = <span class="kwrd">value</span>; }</pre><pre><span class="lnum"> 12: </span> }</pre><pre class="alt"><span class="lnum"> 13: </span><br /></pre><pre><span class="lnum"> 14: </span> <span class="kwrd">public</span> String PageStartPublish</pre><pre class="alt"><span class="lnum"> 15: </span> {</pre><pre><span class="lnum"> 16: </span> get { <span class="kwrd">return</span> litPageStartPublish.Text; }</pre><pre class="alt"><span class="lnum"> 17: </span> set { litPageStartPublish.Text = <span class="kwrd">value</span>; }</pre><pre><span class="lnum"> 18: </span> }</pre><pre class="alt"><span class="lnum"> 19: </span><br /></pre><pre><span class="lnum"> 20: </span> <span class="kwrd">public</span> <span class="kwrd">string</span> Heading</pre><pre class="alt"><span class="lnum"> 21: </span> {</pre><pre><span class="lnum"> 22: </span> get { <span class="kwrd">return</span> litHeading.Text; }</pre><pre class="alt"><span class="lnum"> 23: </span> set { litHeading.Text = <span class="kwrd">value</span>; }</pre><pre><span class="lnum"> 24: </span> }</pre><pre class="alt"><span class="lnum"> 25: </span><br /></pre><pre><span class="lnum"> 26: </span> <span class="kwrd">public</span> <span class="kwrd">string</span> MainBody</pre><pre class="alt"><span class="lnum"> 27: </span> {</pre><pre><span class="lnum"> 28: </span> get { <span class="kwrd">return</span> litMainBody.Text; }</pre><pre class="alt"><span class="lnum"> 29: </span> set { litMainBody.Text = <span class="kwrd">value</span>; }</pre><pre><span class="lnum"> 30: </span> }</pre><pre class="alt"><span class="lnum"> 31: </span>}</pre><br /></div>This class is located in code-behind file, it implements INewsView interface and represents a link between NewsPresenter and the view and actual ASP.NET controls.<br /><br />What you can't see in this class is how the presenter is instantiated, when the view implementation is passed to the presenter, this is wrapped in SimpleMVPPageBase class:<!-- code formatted by http://manoli.net/csharpformat/ --><br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd">public</span> <span class="kwrd">abstract</span> <span class="kwrd">class</span> SimpleMVPPageBase<PresenterClass, ViewInterface> : SimplePage</pre><pre><span class="lnum"> 2: </span> <span class="kwrd">where</span> PresenterClass : AbstractPresenterBase<ViewInterface>, <span class="kwrd">new</span>()</pre><pre class="alt"><span class="lnum"> 3: </span>{</pre><pre><span class="lnum"> 4: </span> <span class="kwrd">protected</span> PresenterClass Presenter;</pre><pre class="alt"><span class="lnum"> 5: </span> <span class="kwrd">protected</span> <span class="kwrd">abstract</span> ViewInterface ViewImplementation { get; }</pre><pre><span class="lnum"> 6: </span><br /></pre><pre class="alt"><span class="lnum"> 7: </span> <span class="kwrd">protected</span> <span class="kwrd">override</span> <span class="kwrd">sealed</span> <span class="kwrd">void</span> OnInit(EventArgs e)</pre><pre><span class="lnum"> 8: </span> {</pre><pre class="alt"><span class="lnum"> 9: </span> <span class="kwrd">base</span>.OnInit(e);</pre><pre><span class="lnum"> 10: </span> Presenter = <span class="kwrd">new</span> PresenterClass</pre><pre class="alt"><span class="lnum"> 11: </span> {</pre><pre><span class="lnum"> 12: </span> View = ViewImplementation,</pre><pre class="alt"><span class="lnum"> 13: </span> CurrentPage = CurrentPage</pre><pre><span class="lnum"> 14: </span> };</pre><pre class="alt"><span class="lnum"> 15: </span> }</pre><pre><span class="lnum"> 16: </span><br /></pre><pre class="alt"><span class="lnum"> 17: </span> <span class="kwrd">protected</span> <span class="kwrd">override</span> <span class="kwrd">sealed</span> <span class="kwrd">void</span> OnLoad(EventArgs e)</pre><pre><span class="lnum"> 18: </span> {</pre><pre class="alt"><span class="lnum"> 19: </span> <span class="kwrd">base</span>.OnLoad(e);</pre><pre><span class="lnum"> 20: </span> <span class="kwrd">if</span> (!IsPostBack)</pre><pre class="alt"><span class="lnum"> 21: </span> {</pre><pre><span class="lnum"> 22: </span> Presenter.Initialize();</pre><pre class="alt"><span class="lnum"> 23: </span> DataBind();</pre><pre><span class="lnum"> 24: </span> }</pre><pre class="alt"><span class="lnum"> 25: </span> } </pre><pre><span class="lnum"> 26: </span>}</pre></div><br />SimpleMVPPageBase class is responsible for two things:<br /><ul><li>It instantiates presenter class, and sets for it CurrentPage and view implementation.</li><li>Also, within OnLoad, it calls Presenter.Initialize() method which should initialize the view.<br /></li></ul>With all those pieces in place, you should get a working page. So, in fact we are in the starting position but with this version we have significantly better separation of concerns and our logic is testable:<br /><!-- code formatted by http://manoli.net/csharpformat/ --><br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd">public</span> <span class="kwrd">class</span> Checking_news_view_initialization : AbstractPresenterTest<NewsPresenter></pre><pre><span class="lnum"> 2: </span>{</pre><pre class="alt"><span class="lnum"> 3: </span> <span class="kwrd">protected</span> <span class="kwrd">override</span> <span class="kwrd">void</span> Before_each_test()</pre><pre><span class="lnum"> 4: </span> {</pre><pre class="alt"><span class="lnum"> 5: </span> Presenter.CurrentPage = EPiServerPage.CreateBlankPage()</pre><pre><span class="lnum"> 6: </span> .AddProperty<PropertyString>(<span class="str">"MetaAuthor"</span>, <span class="str">"author123"</span>)</pre><pre class="alt"><span class="lnum"> 7: </span> .AddProperty<PropertyString>(<span class="str">"Heading"</span>, <span class="str">"heading123"</span>)</pre><pre><span class="lnum"> 8: </span> .AddProperty<PropertyXhtmlString>(<span class="str">"MainBody"</span>, <span class="str">"mainBody123"</span>)</pre><pre class="alt"><span class="lnum"> 9: </span> .AddProperty<PropertyDate>(EPiProperties.PageStartPublish, DateTime.Today)</pre><pre><span class="lnum"> 10: </span> .Instance();</pre><pre class="alt"><span class="lnum"> 11: </span><br /></pre><pre><span class="lnum"> 12: </span> Presenter.View = mocks.StrictMock<INewsView>(<span class="kwrd">null</span>);</pre><pre class="alt"><span class="lnum"> 13: </span> }</pre><pre><span class="lnum"> 14: </span><br /></pre><pre class="alt"><span class="lnum"> 15: </span> [Test]</pre><pre><span class="lnum"> 16: </span> <span class="kwrd">public</span> <span class="kwrd">void</span> It_should_set_all_properties()</pre><pre class="alt"><span class="lnum"> 17: </span> {</pre><pre><span class="lnum"> 18: </span> <span class="kwrd">using</span> (mocks.Record())</pre><pre class="alt"><span class="lnum"> 19: </span> {</pre><pre><span class="lnum"> 20: </span> Expect.Call(Presenter.View.Heading).SetPropertyWithArgument(<span class="str">"heading123"</span>);</pre><pre class="alt"><span class="lnum"> 21: </span> Expect.Call(Presenter.View.MainBody).SetPropertyWithArgument(<span class="str">"mainBody123"</span>);</pre><pre><span class="lnum"> 22: </span> Expect.Call(Presenter.View.MetaAuthor).SetPropertyWithArgument(<span class="str">"author123"</span>);</pre><pre class="alt"><span class="lnum"> 23: </span> Expect.Call(Presenter.View.PageStartPublish).SetPropertyWithArgument(DateTime.Today.ToShortDateString());</pre><pre><span class="lnum"> 24: </span> }</pre><pre class="alt"><span class="lnum"> 25: </span><br /></pre><pre><span class="lnum"> 26: </span> <span class="kwrd">using</span> (mocks.Playback())</pre><pre class="alt"><span class="lnum"> 27: </span> {</pre><pre><span class="lnum"> 28: </span> Presenter.Initialize();</pre><pre class="alt"><span class="lnum"> 29: </span> }</pre><pre><span class="lnum"> 30: </span> }</pre><pre class="alt"><span class="lnum"> 31: </span>}</pre><br /></div><br />Within Before_each_test() method I'm basically creating an instance of PageData with a few properties. I'm also mocking the view using <a href="http://ayende.com/projects/rhino-mocks.aspx">Rhino Mocks</a>. Thanks to that in It_should_set_all_properties() method I can record my expectations and later verify them.<br /><br />But wait a second ... as I was writing earlier, EPiServer requires valid HttpContext right?<br /><br />Workaround for this issue is a step 2 on our way to TDD:<br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span>[TestFixture]</pre><pre><span class="lnum"> 2: </span><span class="kwrd">public</span> <span class="kwrd">abstract</span> <span class="kwrd">class</span> AbstractPresenterTest<PresenterClass> <span class="kwrd">where</span> PresenterClass : <span class="kwrd">new</span>()</pre><pre class="alt"><span class="lnum"> 3: </span>{</pre><pre><span class="lnum"> 4: </span> <span class="kwrd">protected</span> PresenterClass Presenter { get; <span class="kwrd">private</span> set; }</pre><pre class="alt"><span class="lnum"> 5: </span> <span class="kwrd">protected</span> MockRepository mocks { get; <span class="kwrd">private</span> set; }</pre><pre><span class="lnum"> 6: </span><br /></pre><pre class="alt"><span class="lnum"> 7: </span><br /></pre><pre><span class="lnum"> 8: </span> <span class="kwrd">protected</span> AbstractPresenterTest()</pre><pre class="alt"><span class="lnum"> 9: </span> {</pre><pre><span class="lnum"> 10: </span> Presenter = <span class="kwrd">new</span> PresenterClass();</pre><pre class="alt"><span class="lnum"> 11: </span> }</pre><pre><span class="lnum"> 12: </span><br /></pre><pre class="alt"><span class="lnum"> 13: </span> [TestFixtureSetUp]</pre><pre><span class="lnum"> 14: </span> <span class="kwrd">protected</span> <span class="kwrd">void</span> SetUpContextForAWholeFixture()</pre><pre class="alt"><span class="lnum"> 15: </span> {</pre><pre><span class="lnum"> 16: </span> var settings = <span class="kwrd">new</span> EPiServer.Configuration.Settings</pre><pre class="alt"><span class="lnum"> 17: </span> {</pre><pre><span class="lnum"> 18: </span> StringCompressionThreshold = 0</pre><pre class="alt"><span class="lnum"> 19: </span> };</pre><pre><span class="lnum"> 20: </span><br /></pre><pre class="alt"><span class="lnum"> 21: </span> Type settingsType = settings.GetType();</pre><pre><span class="lnum"> 22: </span> settingsType.GetField(<span class="str">"_instance"</span>, BindingFlags.Static | BindingFlags.NonPublic)</pre><pre class="alt"><span class="lnum"> 23: </span> .SetValue(settings, settings);</pre><pre><span class="lnum"> 24: </span> }</pre><pre class="alt"><span class="lnum"> 25: </span><br /></pre><pre><span class="lnum"> 26: </span> [SetUp]</pre><pre class="alt"><span class="lnum"> 27: </span> <span class="kwrd">protected</span> <span class="kwrd">void</span> SetUpContextForEachTest()</pre><pre><span class="lnum"> 28: </span> {</pre><pre class="alt"><span class="lnum"> 29: </span> mocks = <span class="kwrd">new</span> MockRepository();</pre><pre><span class="lnum"> 30: </span> Before_each_test();</pre><pre class="alt"><span class="lnum"> 31: </span> }</pre><pre><span class="lnum"> 32: </span><br /></pre><pre class="alt"><span class="lnum"> 33: </span> <span class="kwrd">protected</span> <span class="kwrd">abstract</span> <span class="kwrd">void</span> Before_each_test();</pre><pre><span class="lnum"> 34: </span>}</pre><br /></div><br />HttpContext related exception won't be triggered if you create instance of Settings and using reflection assign it to the static and private field _instance on Settings class. This way you can also provide your configuration for tests.<br /><br /><span style="font-weight: bold;">Conclusions</span><br /><ul><li>the most important one is that Test Driven Development with EPiServer is possible!</li><li>you need to isolate as much functionality from ASP.NET runtime and MVP design patter is doing exactly that for you</li><li>with a little bit of hacking of EPiServer you can instantiate PageData and test your logic</li><li>MVP requires additional effort to create interfaces, presenters etc. Downside is that you won't be able to build web applications so rapidly but it should pay off in longer and more complex projects where having a set of automated tests is a big advantage.<br /></li></ul><span style="font-weight: bold;">Credits</span><br /><ul><li>My research and presentation were inspired by <a href="http://codebetter.com/blogs/raymond.lewallen/">Raymond Lewallen</a> who was a guest of Poznan .Net Group and was presenting Behavioral Driven Development approach. </li><li>I have used image from Microsoft <a href="http://msdn.microsoft.com/en-us/library/cc304760.aspx">site</a> talking about MVP.</li></ul>Unknownnoreply@blogger.com13tag:blogger.com,1999:blog-6586401656416839531.post-24893388846951808502009-03-11T22:20:00.002+01:002009-04-01T11:47:43.882+02:00Validation of NHibernate EntitiesRecently I was reading Billy McCafferty's post "<a href="http://devlicio.us/blogs/billy_mccafferty/archive/2009/03/07/a-few-nhibernate-tips.aspx">A Few NHibernate Tips</a>" and one point there, related to mapping files, was particularly interesting for me:<br /><blockquote>Don’t bother including database meta data (e.g., column length) in mapping files unless intending to auto generate the SQL using the <a href="http://www.hibernate.org/hib_docs/nhibernate/html/toolsetguide.html">hbm2dll</a> tool.</blockquote>I was really surprised to see that things like column length, information if column accept null values are completely ignored.<br /><br /><span style="font-weight: bold;">Why does it matter? </span><br /><br />There are actually two reasons:<br /><ul><li>if details regarding database meta data are irrelevant then why should we write mappings at all? Fluent Nhibernate and <a href="http://marekblotny.blogspot.com/2008/12/fluent-nhibernate-conventions-and.html">Auto Mapping</a> is the way to go.</li><li>performance impact - it's a huge waste of time to make a call to database just to get an exception saying that some columns don't accept nulls. Luckily, there is a project called <a href="http://nhforge.org/wikis/validator10/default.aspx">NHibernate Validator</a> which can solve this issue.<br /></li></ul><span style="font-weight: bold;">Fluent NHibernate and NHibernate Validator in one project</span><br /><br />It's fairly simple to configure those two to work together:<br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd">public</span> <span class="kwrd">static</span> ISessionFactory GetFluentlyConfiguredSessionFactory()</pre><pre><span class="lnum"> 2: </span>{</pre><pre class="alt"><span class="lnum"> 3: </span> <span class="kwrd">return</span> Fluently.Configure()</pre><pre><span class="lnum"> 4: </span> .Database(MsSqlConfiguration</pre><pre class="alt"><span class="lnum"> 5: </span> .MsSql2005</pre><pre><span class="lnum"> 6: </span> .ConnectionString(c => c.Is(ConnectionString)))</pre><pre class="alt"><span class="lnum"> 7: </span> .Mappings(m =></pre><pre><span class="lnum"> 8: </span> m.FluentMappings.AddFromAssemblyOf<Product>()</pre><pre class="alt"><span class="lnum"> 9: </span> .ConventionDiscovery.Add(new AdventureWorksConvention())</pre><pre><span class="lnum"> 10: </span> .ExportTo(ExternalMappingsOutputDirectory))</pre><pre class="alt"><span class="lnum"> 11: </span> .ExposeConfiguration(ConfigureNHibernateValidator)</pre><pre><span class="lnum"> 12: </span> .BuildSessionFactory();</pre><pre class="alt"><span class="lnum"> 13: </span>}</pre><pre><span class="lnum"> 14: </span><br /></pre><pre class="alt"><span class="lnum"> 15: </span><span class="kwrd">public</span> <span class="kwrd">static</span> <span class="kwrd">void</span> ConfigureNHibernateValidator(Configuration configuration)</pre><pre><span class="lnum"> 16: </span>{</pre><pre class="alt"><span class="lnum"> 17: </span> var nhvc = <span class="kwrd">new</span> NHVConfiguration();</pre><pre><span class="lnum"> 18: </span> nhvc.Properties[NHibernate.Validator.Cfg.Environment.ValidatorMode] = <span class="str">"UseAttribute"</span>;</pre><pre class="alt"><span class="lnum"> 19: </span> nhvc.Mappings.Add(<span class="kwrd">new</span> NHibernate.Validator.Cfg.MappingConfiguration(<span class="str">"AdventureWorksPlayground"</span>, <span class="kwrd">null</span>));</pre><pre><span class="lnum"> 20: </span> </pre><pre class="alt"><span class="lnum"> 21: </span> var validator = <span class="kwrd">new</span> ValidatorEngine();</pre><pre><span class="lnum"> 22: </span> validator.Configure(nhvc);</pre><pre class="alt"><span class="lnum"> 23: </span><br /></pre><pre><span class="lnum"> 24: </span> <span class="rem">//Registering of Listeners and DDL-applying here</span></pre><pre class="alt"><span class="lnum"> 25: </span> ValidatorInitializer.Initialize(configuration, validator);</pre><pre><span class="lnum"> 26: </span>}</pre></div><br />First method - GetFluentlyConfiguredSessionFactory() is responsible for fluent-nh <a href="http://marekblotny.blogspot.com/2009/02/fluentconfiguration-new-api-to.html">configuration</a>. Second method, ConfigureNHibernateValidator(), instantiates Validator and registers listeners.<br /><blockquote>NHibernate Validator has two built-in NHibernate event listeners. Whenever a PreInsertEvent or PreUpdateEvent occurs, the listeners will verify all constraints of the entity instance and throw an exception if any of them are violated. Basically, objects will be checked before any insert and before any update triggered by NHibernate. This includes cascading changes! This is the most convenient and easiest way to activate the validation process. If a constraint is violated, the event will raise a runtime InvalidStateException which contains an array of InvalidValues describing each failure.</blockquote>In your project you would like to change string "AdventureWorksPlayground", that is a name of your assembly. Probably you don't want to change validation mode which is set to "UseAttribute". Alternative is that you can use XML file for it.<br /><br />Here is an example of domain object with a few validation rules:<br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd">public</span> <span class="kwrd">class</span> SalesTerritory</pre><pre><span class="lnum"> 2: </span>{</pre><pre class="alt"><span class="lnum"> 3: </span> <span class="kwrd">public</span> <span class="kwrd">virtual</span> <span class="kwrd">int</span> Id { get; set; }</pre><pre><span class="lnum"> 4: </span> </pre><pre class="alt"><span class="lnum"> 5: </span> [NotNull]</pre><pre><span class="lnum"> 6: </span> [Pattern(Regex = <span class="str">"[A-Za-z0-9]+"</span>)]</pre><pre class="alt"><span class="lnum"> 7: </span> <span class="kwrd">public</span> <span class="kwrd">virtual</span> <span class="kwrd">string</span> Name { get; set; }</pre><pre><span class="lnum"> 8: </span><br /></pre><pre class="alt"><span class="lnum"> 9: </span> [NotNull]</pre><pre><span class="lnum"> 10: </span> <span class="kwrd">public</span> <span class="kwrd">virtual</span> <span class="kwrd">string</span> Group { get; set; }</pre><pre class="alt"><span class="lnum"> 11: </span> </pre><pre><span class="lnum"> 12: </span> [Length(Max = 3), NotNull]</pre><pre class="alt"><span class="lnum"> 13: </span> <span class="kwrd">public</span> <span class="kwrd">virtual</span> <span class="kwrd">string</span> CountryRegionCode { get; set; }</pre><pre><span class="lnum"> 14: </span>}</pre></div><br />Out of the box you can use number different attributes to describe your validation rules, you can also create your own one if necessary. I was running a few tests and the results show that Validator will throw an exception four times faster then the NHibernate itself (and that was with database on the same machine).<br /><br />You can learn much more about NHibernate Validator on <a href="http://nhforge.org/wikis/validator10/nhibernate-validator-1-0-0-documentation.aspx">NHibernate Forge wiki</a>.<br /><br /><div style="text-align: right;"><a rev="vote-for" href="http://dotnetshoutout.com/Validation-of-NHibernate-Entities"><img alt="Shout it" src="http://dotnetshoutout.com/image.axd?url=http%3A%2F%2Fmarekblotny.blogspot.com%2F2009%2F03%2Fvalidation-of-nhibernate-entities.html" style="border: 0px none ;" /></a></div>Unknownnoreply@blogger.com3tag:blogger.com,1999:blog-6586401656416839531.post-70491242371735717042009-03-09T23:25:00.003+01:002009-04-01T12:00:40.594+02:00Fluent NHibernate and Inheritance MappingWhile exploring the <a href="http://www.codeplex.com/MSFTDBProdSamples/Release/ProjectReleases.aspx?ReleaseId=4004">AdventureWorks</a> database I have found an interesting case where inheritance mapping has to be used. In this post I would like to show how it can be neatly mapped with <a href="http://fluentnhibernate.org/">Fluent NHibernate</a>. Lets start with database schema:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4K2dkLJu9lgVsp_xLCgqoNQ_fZeMEfeujJG5_z-SuDOYt2Npxtjo1r_I2X2I91C0unSbHjTmYNMXFNS3-sSmO2-h4kz2k1d7W_Luk1ryufjtfh7HTW1gnM81Z21vD0uoVCtMSSx0EncSM/s1600-h/InheritanceDBSchema.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 171px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4K2dkLJu9lgVsp_xLCgqoNQ_fZeMEfeujJG5_z-SuDOYt2Npxtjo1r_I2X2I91C0unSbHjTmYNMXFNS3-sSmO2-h4kz2k1d7W_Luk1ryufjtfh7HTW1gnM81Z21vD0uoVCtMSSx0EncSM/s400/InheritanceDBSchema.png" alt="" id="BLOGGER_PHOTO_ID_5311283748317987442" border="0" /></a>There are a few interesting things about this few tables:<br /><ul><li>There are two types of customers: Store and Individual</li><li>Each customer is represented as a single record in Customer table and additionally single record in Individual or Store table. Customer has a CustomerType column based on which we can determine the type.<br /></li><li>Individual and Store tables have a primary key which is also a foreign key to the main Customer table. It means that Customer with ID 1231 will have corresponding record in Store (or Individual) table with the same ID.</li></ul><span style="font-weight: bold;">Why inheritance is needed here?</span><br /><br />The above tables can be also mapped without any fancy inheritance so question is why should we bother? The real reason for this hassle are business rules which are different for each type of customer (and potentially sales territory). To be more specific, there might be different rules for queuing deliveries, calculating discounts etc.<br /><br />It makes sense to encapsulate those rules in a separate class for each type of consumer. This line of thinking leads us to the class diagram like this:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4bj9Pq1e4calA9th1oVxFFCedVVPE4BNmSFRX4Hsz-b_iuK2HaHdJoumV1ssj5LlpufbWNJSBGRXVBR5a9aP7KCsFRjx7vQLI7m_1ruWfrimXF1cBGon8AZTzTLcZKQO7rL3kEMg-aZy4/s1600-h/CustomerClassDiagram.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 373px; height: 400px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4bj9Pq1e4calA9th1oVxFFCedVVPE4BNmSFRX4Hsz-b_iuK2HaHdJoumV1ssj5LlpufbWNJSBGRXVBR5a9aP7KCsFRjx7vQLI7m_1ruWfrimXF1cBGon8AZTzTLcZKQO7rL3kEMg-aZy4/s400/CustomerClassDiagram.png" alt="" id="BLOGGER_PHOTO_ID_5311289873579799650" border="0" /></a><br />Customer is marked as a abstract class so it can't be instantiated. For simplicity I have added here only one method GetCurrentDiscount() which represents business logic specific for each type. Additionally StoreConsumer and IndividualConsumer classes have a Details property which "links" to two other tables - Store or Individual.<br /><br /><span style="font-weight: bold;">Inheritance Mapping</span><br /><br />In general NHibernate supports three different inheritance mapping <a href="http://www.hibernate.org/hib_docs/nhibernate/html/inheritance.html">strategies</a>. In this case I will use table per class hierarchy strategy. With Fluent NHibernate Customer class can be mapped like this:<br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd">public</span> <span class="kwrd">class</span> CustomerMap : ClassMap<Customer><br /></pre><pre><span class="lnum"> 2: </span>{<br /></pre><pre class="alt"><span class="lnum"> 3: </span> <span class="kwrd">public</span> CustomerMap()</pre><pre><span class="lnum"> 4: </span> {</pre><pre class="alt"><span class="lnum"> 5: </span> Id(x => x.Id).GeneratedBy.Native();</pre><pre><span class="lnum"> 6: </span><br /></pre><pre class="alt"><span class="lnum"> 7: </span> Map(x => x.ModifiedDate).Not.Nullable();</pre><pre><span class="lnum"> 8: </span> Map(x => x.AccountNumber).ReadOnly().Unique();</pre><pre class="alt"><span class="lnum"> 9: </span><br /></pre><pre><span class="lnum"> 10: </span> References(x => x.SalesTerritory, <span class="str">"TerritoryID"</span>);</pre><pre class="alt"><span class="lnum"> 11: </span> </pre><pre><span class="lnum"> 12: </span> DiscriminateSubClassesOnColumn<<span class="kwrd">string</span>>(<span class="str">"CustomerType"</span>)</pre><pre class="alt"><span class="lnum"> 13: </span> .SubClass<IndividualCustomer>(<span class="str">"I"</span>,</pre><pre><span class="lnum"> 14: </span> m => m.HasOne(x => x.Details)</pre><pre class="alt"><span class="lnum"> 15: </span> .Cascade.SaveUpdate()</pre><pre><span class="lnum"> 16: </span> .FetchType.Join())</pre><pre class="alt"><span class="lnum"> 17: </span> .SubClass<StoreCustomer>(<span class="str">"S"</span>,</pre><pre><span class="lnum"> 18: </span> m => m.HasOne(x => x.Details)</pre><pre class="alt"><span class="lnum"> 19: </span> .Cascade.SaveUpdate()</pre><pre><span class="lnum"> 20: </span> .FetchType.Join());</pre><pre class="alt"><span class="lnum"> 21: </span> }</pre><pre><span class="lnum"> 22: </span>}</pre></div>Let me explain what is going on up there:<br /><ul><li>Thanks to Id(...) and Map(...) methods we can tell Fluent NHibernate about identity column and other simple "data" columns. You can find more about basics <a href="http://marekblotny.blogspot.com/2008/12/fluent-nhibernate-introduction-and.html">here</a>.</li><li>References(...) method is used to map <a href="http://marekblotny.blogspot.com/2009/02/fluent-nhbernate-and-collections.html">many-to-one association</a> with SalesTerritory table. In this case column name doesn't follow the <a href="http://marekblotny.blogspot.com/2009/04/conventions-after-rewrite.html">convention</a> therefore it has to be explicitly specified. </li><li>CustomerType column is used as a discriminator. "The discriminator column contains marker values that tell the persistence layer what subclass to instantiate for a particular row." CustomerType can have two values:</li><ul><li>I for individual customer, in which case IndividualCustomer class will be instantiated or</li><li>S for store and for StoreCustomer class.</li></ul><li>Both subclasses map one additional one-to-one association. Based on above mapping you can't see that actually each subclass "links" to the different table, it will be clear though when you check both classes:</li></ul><div class="csharpcode"><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd">public</span> <span class="kwrd">class</span> IndividualCustomer : Customer</pre><pre><span class="lnum"> 2: </span>{</pre><pre class="alt"><span class="lnum"> 3: </span> <span class="kwrd">public</span> <span class="kwrd">virtual</span> Individual Details { get; set; }</pre><pre><span class="lnum"> 4: </span> </pre><pre class="alt"><span class="lnum"> 5: </span> <span class="kwrd">public</span> <span class="kwrd">override</span> <span class="kwrd">double</span> GetCurrentDiscount()</pre><pre><span class="lnum"> 6: </span> {</pre><pre class="alt"><span class="lnum"> 7: </span> <span class="rem">// some logic here</span></pre><pre><span class="lnum"> 8: </span> }</pre><pre class="alt"><span class="lnum"> 9: </span>}</pre><br /></div>And StoreCustomer:<br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd">public</span> <span class="kwrd">class</span> StoreCustomer : Customer</pre><pre><span class="lnum"> 2: </span>{</pre><pre class="alt"><span class="lnum"> 3: </span> <span class="kwrd">public</span> <span class="kwrd">virtual</span> Store Details { get; set; }</pre><pre><span class="lnum"> 4: </span> </pre><pre class="alt"><span class="lnum"> 5: </span> <span class="kwrd">public</span> <span class="kwrd">override</span> <span class="kwrd">double</span> GetCurrentDiscount()</pre><pre><span class="lnum"> 6: </span> {</pre><pre class="alt"><span class="lnum"> 7: </span> <span class="rem">// some logic here</span></pre><pre><span class="lnum"> 8: </span> }</pre><pre class="alt"><span class="lnum"> 9: </span>}</pre><br /></div>Please note that Details property has different type in each case, in each case NHibernate will be able to figure out which class should be created and what data should be populated ... awesome!<br /><br /><span style="font-weight: bold;">One-to-one association</span><br /><br />There is one additional feature worth mentioning - as it was stated earlier, ID from Customer table equals ID from Store (or Individual) table. How it can be mapped to make sure that inserts will work? I will explain it based on Individual class:<br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd">public</span> <span class="kwrd">class</span> Individual</pre><pre><span class="lnum"> 2: </span>{</pre><pre class="alt"><span class="lnum"> 3: </span> <span class="kwrd">public</span> <span class="kwrd">virtual</span> <span class="kwrd">int</span> Id { get; set; }</pre><pre><span class="lnum"> 4: </span> <span class="kwrd">public</span> <span class="kwrd">virtual</span> <span class="kwrd">int</span> Contact { get; set; }</pre><pre class="alt"><span class="lnum"> 5: </span> <span class="kwrd">public</span> <span class="kwrd">virtual</span> <span class="kwrd">string</span> Demographics { get; set; }</pre><pre><span class="lnum"> 6: </span> <span class="kwrd">public</span> <span class="kwrd">virtual</span> DateTime ModifiedDate { get; set; }</pre><pre class="alt"><span class="lnum"> 7: </span> <span class="kwrd">public</span> <span class="kwrd">virtual</span> IndividualCustomer Customer { get; set; }</pre><pre><span class="lnum"> 8: </span>}</pre></div>Individual class is always associated with individual customer therefore we can explicitly say that property Customer is of type IndividualCustomer. Other properties are fairly straightforward.<br /><br />Here is the mapping class:<br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd">public</span> <span class="kwrd">class</span> InidividualMap : ClassMap<Individual></pre><pre><span class="lnum"> 2: </span>{</pre><pre class="alt"><span class="lnum"> 3: </span> <span class="kwrd">public</span> InidividualMap()</pre><pre><span class="lnum"> 4: </span> {</pre><pre class="alt"><span class="lnum"> 5: </span> Id(x => x.Id, <span class="str">"CustomerID"</span>).GeneratedBy.Foreign(<span class="str">"Customer"</span>);</pre><pre><span class="lnum"> 6: </span><br /></pre><pre class="alt"><span class="lnum"> 7: </span> Map(x => x.Demographics);</pre><pre><span class="lnum"> 8: </span> Map(x => x.ModifiedDate);</pre><pre class="alt"><span class="lnum"> 9: </span> Map(x => x.Contact, <span class="str">"ContactID"</span>);</pre><pre><span class="lnum"> 10: </span><br /></pre><pre class="alt"><span class="lnum"> 11: </span> HasOne(x => x.Customer).Constrained();</pre><pre><span class="lnum"> 12: </span> }</pre><pre class="alt"><span class="lnum"> 13: </span>}</pre><br /></div>Appropriate generator for identity column is the key here:<br /><blockquote>foreign - uses the identifier of another associated object. Usually used in conjunction with a one-to-one primary key association.<br /></blockquote>With above mapping foreign generator knows about the Customer and will use exactly the same ID. More about foreign generator and different types of one-to-one association you can find <a href="http://www.hibernate.org/hib_docs/nhibernate/html/mapping.html">here</a>.<br /><br /><span style="font-weight: bold;">Related articles:</span><br /><ul><li><a href="http://marekblotny.blogspot.com/2009/02/fluent-nhbernate-and-collections.html">Fluent NHibernate and Collections mapping</a></li><li>James Gregory's post about <a href="http://blog.jagregory.com/2009/01/05/fluent-nhibernate-subclass-syntax-changes/">Fluent NHibernate SubClass syntax changes</a></li><li><a href="http://marekblotny.blogspot.com/2008/12/fluent-nhibernate-introduction-and.html">Introduction to Fluent NHibernate</a></li></ul><br /><div style="text-align: right;"><a rev="vote-for" href="http://dotnetshoutout.com/Fluent-NHibernate-and-Inheritance-Mapping"><img alt="Shout it" src="http://dotnetshoutout.com/image.axd?url=http%3A%2F%2Fmarekblotny.blogspot.com%2F2009%2F03%2Ffluent-nhibernate-and-inheritance.html" style="border: 0px none ;" /></a></div>Unknownnoreply@blogger.com6tag:blogger.com,1999:blog-6586401656416839531.post-59748901309808946342009-03-05T12:54:00.025+01:002009-03-05T18:22:49.934+01:00EPiServer Search with VirtualPathVersioningProviderEPiServer out of the box provides versioning file system handled by VirtualPathVersioningProvider. In this post I would like to show you step by step how to enable search for this provider.<br /><br /><span style="font-weight: bold;">Step 1 - Make sure that you are using VirtualPathVersioningProvider. </span><br /><br />It sounds funny but it's worth double checking if your website is configured to use versioning file system or native file system (VirtualPathNativeProvider).<br /><br />The best way is to check web.config section in which virtual providers are defined. You should find there lines like this:<br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd"><</span><span class="html">add</span> <span class="attr">showInFileManager</span><span class="kwrd">="true"</span> </pre><pre><span class="lnum"> 2: </span> <span class="attr">virtualName</span><span class="kwrd">="Documents"</span> </pre><pre class="alt"><span class="lnum"> 3: </span> <span class="attr">virtualPath</span><span class="kwrd">="~/Documents/"</span> </pre><pre><span class="lnum"> 4: </span> <span class="attr">bypassAccessCheck</span><span class="kwrd">="false"</span> </pre><pre class="alt"><span class="lnum"> 5: </span> <span class="attr">maxVersions</span><span class="kwrd">="5"</span> </pre><pre><span class="lnum"> 6: </span> <span class="attr">name</span><span class="kwrd">="SiteDocuments"</span></pre><pre class="alt"><span class="lnum"> 7: </span> <span class="attr">type</span><span class="kwrd">="EPiServer.Web.Hosting.VirtualPathVersioningProvider,EPiServer"</span></pre><pre><span class="lnum"> 8: </span> <span class="attr">physicalPath</span><span class="kwrd">="C:\EPiServer\VPP\ExampleEPiServerSite\Documents"</span><span class="kwrd">><span style="font-family:Georgia,serif;"><br /></span></span></pre></div><br />physicalPath property specifies where actually files will be stored. Interesting is that the paths and the filenames are stored in a database, only content is located in the file system. Internally, VirtualPathVersioningProvider keeps content in a fairly characteristic way:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgJxj7saWLYxHKLIIIIIKqCiwLfL6VLpYbDxldxs3y1l2b2JIMLQ-xf-RzkHCYtrcIi5813wur4HuH3QYFv7RCe3hSjWk-B28JDJ6AX4IgYqyOeTZL9q07kiwXLGmKzBCysmqPImisvmf5/s1600-h/fs.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 230px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgJxj7saWLYxHKLIIIIIKqCiwLfL6VLpYbDxldxs3y1l2b2JIMLQ-xf-RzkHCYtrcIi5813wur4HuH3QYFv7RCe3hSjWk-B28JDJ6AX4IgYqyOeTZL9q07kiwXLGmKzBCysmqPImisvmf5/s400/fs.png" alt="" id="BLOGGER_PHOTO_ID_5309672098082852290" border="0" /></a><br /><span style="font-weight: bold;">Step 2 - EPiServer Indexing Service configuration</span><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvsN-O3t5ewvi3MRO8hCaKLhc8ijtqNqLiEDLUBxqF-RTy55uH54kFNnjj9X1liCxMNRDERY6IwIQTN0UJRrxeYL60hkbr4rqUZV2Q33bHLsfSg4XNK6xoMzupZUr0ysGL0bkW00Z_RJ3c/s1600-h/service.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 388px; height: 137px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvsN-O3t5ewvi3MRO8hCaKLhc8ijtqNqLiEDLUBxqF-RTy55uH54kFNnjj9X1liCxMNRDERY6IwIQTN0UJRrxeYL60hkbr4rqUZV2Q33bHLsfSg4XNK6xoMzupZUr0ysGL0bkW00Z_RJ3c/s400/service.png" alt="" id="BLOGGER_PHOTO_ID_5309677248045638610" border="0" /></a><br />Indexing Service configuration is saved in a EPiServer.IndexingService.exe.config file which can be found in the same folder where the EPiServer Indexing Service was installed.<br /><br />To let know indexing service about your folders you need to alter following part of configuration:<br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd"><</span><span class="html">episerver.indexingService</span><span class="kwrd">></span></pre><pre><span class="lnum"> 2: </span> <span class="kwrd"><</span><span class="html">indexes</span><span class="kwrd">></span></pre><pre class="alt"><span class="lnum"> 3: </span> <span class="kwrd"><</span><span class="html">add</span> <span class="attr">connectionString</span><span class="kwrd">="Data Source=server_name;Database=db_name;User Id=username;Password=password;Network Library=DBMSSOCN;"</span> </pre><pre><span class="lnum"> 4: </span> <span class="attr">databaseClient</span><span class="kwrd">=""</span> </pre><pre class="alt"><span class="lnum"> 5: </span> <span class="attr">filePath</span><span class="kwrd">="C:\EPiServer\VPP\ExampleEPiServerSite\Documents"</span> </pre><pre><span class="lnum"> 6: </span> <span class="attr">itemRoot</span><span class="kwrd">="/Documents"</span> <span class="kwrd">/></span></pre><pre class="alt"><span class="lnum"> 7: </span> <span class="kwrd"></</span><span class="html">indexes</span><span class="kwrd">></span></pre><pre><span class="lnum"> 8: </span><span class="kwrd"></</span><span class="html">episerver.indexingService</span><span class="kwrd">></span></pre><br /></div>Two things are important here:<br /><ul><li>you need to use the same database here (connectionString) which your application is using<br /></li><li>and the same path (filePath)</li></ul><br /><span style="font-weight: bold;">Step 3 - Make sure that Indexing Service works</span><br /><br />The best way to makes sure that your folder was indexed is to check if folder called <span style="font-weight: bold;">index</span> was created. (Check the first image) This folder contains indexing service's data.<br /><br />If your service is started and <span style="font-weight: bold;">index</span> folder is not present then probably good idea is to check indexing service logs, usually you can find there helpful information.<br /><br />Lack of index folder can also cause error like this:<br /><br /><span id="ctl00_FullRegion_ctl01_virtualPathFileManager_searchmode1_SearchMessage"><blockquote>VirtualPathProvider 'SiteDocuments', Search Error: C:\EPiServer\VPP\ExampleEPiServerSite\Documents\index not a directory<br /></blockquote><br /></span><span style="font-weight: bold;">Step 4 - Check if it works</span> Probably the fastest way to check if search works it to use EPiServer's build-in file management tool:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6BLi_c7BJeJQcGuOUA9-EaETP6mMR_S0g7FH0geJfw8R6P2cMGV-isWlX8Rcnl5kau3Lc_uJP8yuhvviUoI0VH1DHaEpsn6H0QTgH75buyudpf9f0zlH_vGg9YNnjTvEhQh-vWBP3Ng8F/s1600-h/st1.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 288px; height: 124px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6BLi_c7BJeJQcGuOUA9-EaETP6mMR_S0g7FH0geJfw8R6P2cMGV-isWlX8Rcnl5kau3Lc_uJP8yuhvviUoI0VH1DHaEpsn6H0QTgH75buyudpf9f0zlH_vGg9YNnjTvEhQh-vWBP3Ng8F/s400/st1.png" alt="" id="BLOGGER_PHOTO_ID_5309684722326188370" border="0" /></a><br />Then you can access search feature:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiEFJPTvyBXcztSEbQl7QKk7FFZhIZ5fZaRVvwdWaJ_5v5ObvRna8lpEJanel1pVZDuHvV6CwrPD9ySLhKwwmgRr2Vp7A3iwdwv17G5Iq67dmZszVDCw7Wl6OIBm3OG-VhcV6xr4Yez9cTU/s1600-h/st2.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 342px; height: 144px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiEFJPTvyBXcztSEbQl7QKk7FFZhIZ5fZaRVvwdWaJ_5v5ObvRna8lpEJanel1pVZDuHvV6CwrPD9ySLhKwwmgRr2Vp7A3iwdwv17G5Iq67dmZszVDCw7Wl6OIBm3OG-VhcV6xr4Yez9cTU/s400/st2.png" alt="" id="BLOGGER_PHOTO_ID_5309684725026434130" border="0" /></a><br />I hope that this post will save a few people a bit of time. If you have been working with EPiServer search before and have interesting experiences then leave a comment, your input is valuable!Unknownnoreply@blogger.com4tag:blogger.com,1999:blog-6586401656416839531.post-2216237242647510702009-03-03T23:45:00.000+01:002009-03-03T23:45:00.594+01:00DefaultButton - Deal with users hitting ENTER on your formsDevelopers tend to assume that users will be always clicking on the buttons to submit forms. Is it a valid assumption? Unfortunately not always ... the simplest example can be quick search, a common component on many sites.<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjwfLk6-JlS41dlIu2vA8PBL5bk0Q4uBO47lDR7s3BRbNYsbJwWP90jp-SIZX7FmRRoDA_njEJjXduHijtU_5ckBrYWkyZ0k7xMqTesBbe85j3_RtXIhgld8K24awvEuJpdH1SpBIVU_pf1/s1600-h/quick-search.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 256px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjwfLk6-JlS41dlIu2vA8PBL5bk0Q4uBO47lDR7s3BRbNYsbJwWP90jp-SIZX7FmRRoDA_njEJjXduHijtU_5ckBrYWkyZ0k7xMqTesBbe85j3_RtXIhgld8K24awvEuJpdH1SpBIVU_pf1/s400/quick-search.png" alt="" id="BLOGGER_PHOTO_ID_5309080660816612482" border="0" /></a><br />What if user simply hit ENTER instead of clicking on the button next to the text box? Because our button wasn't clicked then of course even handler for Click event won't be invoked. <span style="font-weight: bold;">The problem is that users expect that hitting Enter will correctly submit the form.</span> So what can we do?<br /><br /><a href="http://msdn.microsoft.com/en-us/library/system.web.ui.htmlcontrols.htmlform.defaultbutton%28VS.80%29.aspx">HtmlForm</a> and <a href="http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.panel.defaultbutton.aspx">Panel</a> classes have a property called <span style="font-weight: bold;">DefaultButton</span>. Thanks to this property we can address the issue. First lets check the HtmlForm.DefaultButton property, here is how it can be used:<br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd">protected</span> <span class="kwrd">void</span> Page_Load(<span class="kwrd">object</span> sender, EventArgs e)<br /></pre><pre><span class="lnum"> 2: </span>{<br /></pre><pre class="alt"><span class="lnum"> 3: </span> Page.Form.DefaultButton = btnSearch.UniqueID;<br /></pre><pre><span class="lnum"> 4: </span>}</pre><br /></div>That's all what is needed to make sure that post back generated by pressing the ENTER key will be "associated" with the button<span style="font-style: italic;"> btnSearch</span>. That is an improvement, but there is another problem to solve, check contact us page:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtequfEX3M2qaDQHYMH9JyQHwCeqVE0K1D3Tk9CpXiC7GgwRXXJilwl4aRLuFunfk3zHmHBZZ25aRsXAk4oqlzS7RGtmLCUZYy_AfBD_nygMS5C7S0kuiViCKJDZoGf8XLzWzPDyDtU8aM/s1600-h/quick-search-2.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 216px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtequfEX3M2qaDQHYMH9JyQHwCeqVE0K1D3Tk9CpXiC7GgwRXXJilwl4aRLuFunfk3zHmHBZZ25aRsXAk4oqlzS7RGtmLCUZYy_AfBD_nygMS5C7S0kuiViCKJDZoGf8XLzWzPDyDtU8aM/s400/quick-search-2.png" alt="" id="BLOGGER_PHOTO_ID_5309087049254870930" border="0" /></a>This is slightly more complex situation as here we would like to have Send button to be the default button for HtmlForm. So what about quick search? The following piece of code will solve it:<br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd"><</span><span class="html">asp:panel</span> <span class="attr">id</span><span class="kwrd">="pnlSearch"</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="attr">defaultbutton</span><span class="kwrd">="btnSearchButton"</span><span class="kwrd">></span></pre><pre><span class="lnum"> 2: </span> <span class="kwrd"><</span><span class="html">asp:textbox</span> <span class="attr">id</span><span class="kwrd">="txbSearchQuery"</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="attr">validationgroup</span><span class="kwrd">="search"</span> <span class="attr">maxlength</span><span class="kwrd">="50"</span> <span class="kwrd">/></span></pre><pre class="alt"><span class="lnum"> 3: </span> <span class="kwrd"><</span><span class="html">asp:Button</span> <span class="attr">id</span><span class="kwrd">="btnSearchButton"</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="attr">text</span><span class="kwrd">="Search"</span> <span class="attr">validationgroup</span><span class="kwrd">="search"</span> <span class="kwrd">/></span></pre><pre><span class="lnum"> 4: </span><span class="kwrd"></</span><span class="html">asp:panel</span><span class="kwrd">></span></pre><br /></div>By surrounding the quick search controls with ASP.NET panel we can define for that group default button, different default button then the one on HtmlForm. In this simple way it's possible to define arbitrary number of small areas on the page with custom default button.<br /><br /><span style="font-weight: bold;">Another thing worth noticing is that defining unique validation group for all functionally related groups of controls is considered as a good practice</span>. This way you can be sure that submitting one group won't trigger validation somewhere else.<br /><br />I know that those are not a life-changing features but still I find them very useful, it's good to dust off basics like this from time to time.Unknownnoreply@blogger.com10tag:blogger.com,1999:blog-6586401656416839531.post-10623641767618655672009-03-01T17:00:00.002+01:002009-03-01T17:02:22.987+01:00Coding buddy - Interesting approach to Code ReviewHow often do you ask your peers to review your code? If not very often then here is an idea for you - find a coding buddy! The buddy system can be implemented in 2 simple steps. But firstly, here is the idea behind the buddy system:<br /><blockquote>Individuals can do great things, but two highly motivated peers can accomplish even more when they work together. Surely there's at least one programmer you work with who you admire or at least respect enough to adopt the buddy system with.</blockquote><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYEi6qILLyfRGl_GvacDmOIzEd9HZDT2mtycwWp_N8tXRb8mN1mhsXwwASSfnAb8GYUh162Ev2RNzrJLwMM6A_ig5Gyd_NArSVMNAs_OHh5exgz_Lkpc_ablfsz9JZiFrvoxCNt8rfBSbM/s1600-h/batman-and-robin.png"><img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer; width: 200px; height: 200px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYEi6qILLyfRGl_GvacDmOIzEd9HZDT2mtycwWp_N8tXRb8mN1mhsXwwASSfnAb8GYUh162Ev2RNzrJLwMM6A_ig5Gyd_NArSVMNAs_OHh5exgz_Lkpc_ablfsz9JZiFrvoxCNt8rfBSbM/s200/batman-and-robin.png" alt="" id="BLOGGER_PHOTO_ID_5308250140338030594" border="0" /></a>So <span style="font-weight: bold;">step 1</span> is to find a person meeting the above criteria, he will be a second "half of an awesome part-time coding dynamic duo". It has been proven, that this approach really works - there are lots of well-known dynamic duos like:<br /><ul><li>Batman and Robin</li><li><a href="http://www.imdb.com/title/tt0098439/">Tango and Cash</a></li><li>Mario and Luigi</li><li>Starsky and Hutch</li><li><a href="http://en.wikipedia.org/wiki/Miami_Vice">Crockett and Tubbs</a></li><li>Bert and Ernie</li><li>Cheap and Dale</li><li><a href="http://en.wikipedia.org/wiki/Laurel_and_Hardy">Laurel and Hardy</a></li></ul>Your duo can join this small hall of fame! ;)<br /><br />Now it's time to move to <span style="font-weight: bold;">step 2</span> - peer review. Let me start with clarifying what kind of peer review I'm thinking about. Peer review can have multiple variations. For sure, with you coding buddy you don't want to do very formal, systematic and rigorous reviews (inspection) you should be much more flexible and more into quick and informal meetings (peer deskcheck).<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXqvqdgz_H-dLKTgq2z-Xi1aEX_cSRZC3B6k7ncPLC4zyxbnHEiZWnR2SZRB6ir541KSwsiuO3nWdyXtsHHKMge4uS6bQX7uF_17uyhFCLsb4y3FbTGMc7boy-pm7fl4rUVz-5fCG7tgRy/s1600-h/peer_review.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 131px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXqvqdgz_H-dLKTgq2z-Xi1aEX_cSRZC3B6k7ncPLC4zyxbnHEiZWnR2SZRB6ir541KSwsiuO3nWdyXtsHHKMge4uS6bQX7uF_17uyhFCLsb4y3FbTGMc7boy-pm7fl4rUVz-5fCG7tgRy/s400/peer_review.png" alt="" id="BLOGGER_PHOTO_ID_5308231445784167122" border="0" /></a><br />I think informal nature of peer deskcheck is the factor that makes is so effective. You can comfortably chat with your buddy and explain to him what is the business objective for given piece of code and what was your approach to the problem. It's a great way to make sure that your idea makes sense and you are on the right course.<br /><br />Those are only direct benefits, indirect benefits include:<br /><ul><li>improved team work - reviews help people to "accustom" to helping each other</li><li>increased familiarity of code base - sharing knowledge is always important, reviews give developers a chance to teach others about their piece of code</li><li><a href="http://marekblotny.blogspot.com/2008/06/dont-go-dark.html">prevent individuals from going dark</a></li><li>increased quality - "reviews motivate us to practice superior craftsmanship because we know our co-workers will closely examine our work. In this indirect way, peer reviews lead to higher quality."<br /></li></ul>And that would be it, try to implement those two relatively simple steps and check on your own if it improves your code, check if other people understand your design and learn from it.<br /><br /><span style="font-weight: bold;">What are the next steps?</span> (assuming that it buddy system works)<br /><ul><li>find more coding buddies as it means more ideas, more views which afterwards should lead to even better code and even better knowledge sharing</li><li>learn about different types of peer reviews, other types can be also useful. (<a href="http://www.processimpact.com/reviews_book/chapter_3.pdf">chapter 3 of Peer Reviews in Software: A Practical Guide</a>)<br /></li><li>try to change your company's software engineering culture to include peer reviews as a part of the process which you follow, every one can benefit from it (<a href="http://www.processimpact.com/reviews_book/chapter_2.pdf">chapter 2 of Peer Reviews in Software: A Practical Guide</a>)</li></ul>Finally, I couldn't stop myself from posting this <a href="http://www.osnews.com/story/19266/WTFs_m">cartoon</a>:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiUM61IlQy4lUemn5HPI3poB4Zr4u8hldcFfVp2UWrL4SpgF84j7PlXtRh0aUKbc7Ds0YeyaMuXciXeMwrYSrWfDUbGUmiz_jP6YIgtzWgbxKPe7I2bhHkw_BbbxUrBP466_71Pt_bsjMmt/s1600-h/wtfm.jpg"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 377px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiUM61IlQy4lUemn5HPI3poB4Zr4u8hldcFfVp2UWrL4SpgF84j7PlXtRh0aUKbc7Ds0YeyaMuXciXeMwrYSrWfDUbGUmiz_jP6YIgtzWgbxKPe7I2bhHkw_BbbxUrBP466_71Pt_bsjMmt/s400/wtfm.jpg" alt="" id="BLOGGER_PHOTO_ID_5308239417242651602" border="0" /><br /></a>Last words ...<br /><ul><li>This post was inspired by Jeff Atwood's post "<a href="http://www.codinghorror.com/blog/archives/001229.html">Who's your coding buddy</a>". I know Jeff that you don't read such a obscure blogs like this one but thank you for your post!</li><li>Quotations and images used in this post are from Jeff Atwood's post and from Karl Wiegers' <a href="http://www.amazon.com/dp/0201734850/">Peer Reviews in Software: A Practical Guide</a><br /></li></ul>Unknownnoreply@blogger.com1tag:blogger.com,1999:blog-6586401656416839531.post-23583325228265927672009-02-24T07:50:00.002+01:002009-02-24T09:54:05.294+01:00EPiServer -- Deleted Pages<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiC0TsYepjULDvx7AN1PVHD8hhyxLZ3d1rmV-UvhaQTQVdBTqGUWslGW1xHP06G5Bi6p9kng25PH2TkKSZicp440Rnr0TPRxbRLigZUJEdYQdh_AHwHyPan-APnBYK81S6rssQXmukx-uA7/s1600-h/RecycleBin.png"><img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer; width: 138px; height: 94px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiC0TsYepjULDvx7AN1PVHD8hhyxLZ3d1rmV-UvhaQTQVdBTqGUWslGW1xHP06G5Bi6p9kng25PH2TkKSZicp440Rnr0TPRxbRLigZUJEdYQdh_AHwHyPan-APnBYK81S6rssQXmukx-uA7/s400/RecycleBin.png" alt="" id="BLOGGER_PHOTO_ID_5306251547914594626" border="0" /></a><br />I was asked recently a few times how to detect deleted pages. Deleted from editors perspective, which means -- moved to the Recycle Bin. Answer to this question is really simple, PageData class has a property called IsDeleted, here is an example:<br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd">public</span> <span class="kwrd">static</span> <span class="kwrd">bool</span> IsPageDeleted(PageReference pageRef)</pre><pre><span class="lnum"> 2: </span>{</pre><pre class="alt"><span class="lnum"> 3: </span> PageData page = DataFactory.Instance.GetPage(pageRef);</pre><pre><span class="lnum"> 4: </span> <span class="kwrd">return</span> page.IsDeleted;</pre><pre class="alt"><span class="lnum"> 5: </span>}</pre></div><br />In fact, that is all what is needed to check if page was deleted. Below I would like to list additionally a few related tips which might be useful:<br /><ul><li>You can get reference to the Recycle Bin thanks to this static property:<br /><br /><div class="csharpcode"><pre class="alt"><span class="lnum"> </span>PageReference.WasteBasket</pre></div></li><li>Another way to check if instance of PageReference class points to the Recycle Bin is to call this method:<br /><br /><div class="csharpcode"><pre class="alt"><span class="lnum"> </span>DataFactory.Instance.IsWastebasket(<span class="kwrd">new</span> PageReference(12))</pre></div></li><li>If you would like to move a page to the Recycle Bin programmatically then you don't really want to delete the page, you should use this method instead:<br /><br /><div class="csharpcode"><pre class="alt"><span class="lnum"> </span>DataFactory.Instance.MoveToWastebasket(<span class="kwrd">new</span> PageReference(121));</pre></div></li><li>EPiServer has by default scheduled job called "Automatic Emptying of Recycle Bin" which is responsible for:<br /><blockquote>With Automatic Emptying of Recycle Bin, you can set how often your Recycle Bin should be emptied. The aim of this function is to stop old information from being left in the Recycle Bin for a long period of time. With automatic emptying, all information that is older than 30 days will be deleted from the Recycle Bin.</blockquote><br />So what can be wrong when pages from your Recycle Bin don't get removed? The most likely option is that this scheduled job is not activated, make sure that checkbox <span style="font-style: italic;">Active</span> is checked ;)</li></ul>If you need further details or some relevant information is missing then feel free to leave a comment.<br /><br /><span style="font-weight: bold;">Other interesting posts</span>:<br /><ul><li><a href="http://marekblotny.blogspot.com/2009/02/episerver-outgoing-links.html">EPiServer -- Outgoing Links</a><br /></li><li><a href="http://marekblotny.blogspot.com/2009/01/ultimate-diagnostic-tool-for-episerver.html">Ultimate diagnostic tool for EPiServer</a></li><li><a href="http://marekblotny.blogspot.com/2009/02/episerver-5-r2-and-link-collection.html">EPiServer 5 R2 and Link Collection property</a></li></ul>Unknownnoreply@blogger.com3tag:blogger.com,1999:blog-6586401656416839531.post-56117535618203620572009-02-18T18:00:00.001+01:002009-02-18T21:37:18.336+01:00Google Trends<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgg1e6ydk6odiKkPUcZIMNKCku33QNTO00U0qRZIpjj2Nw_AosK3Gn3MiKaw8-7RPn38liCTKE4wQUf0hvPLuakoPiw8K3emi-QitpI9WjWqaZBeqBHwRSNBXGdTokMrpy3hayeaUb5eDNy/s1600-h/logo_sm.gif"><img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer; width: 150px; height: 55px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgg1e6ydk6odiKkPUcZIMNKCku33QNTO00U0qRZIpjj2Nw_AosK3Gn3MiKaw8-7RPn38liCTKE4wQUf0hvPLuakoPiw8K3emi-QitpI9WjWqaZBeqBHwRSNBXGdTokMrpy3hayeaUb5eDNy/s320/logo_sm.gif" alt="" id="BLOGGER_PHOTO_ID_5304137207998135154" border="0" /></a>Have you ever wanted to check how popular are certain key words in search engines? Or maybe how busy are popular websites? Now it's all possible with <a href="http://trends.google.com/trends">Google Trends</a>.<br /><br />For instance you can check number of daily unique visitors for popular websites like <a href="http://twitter.com/">Twitter</a> and <a href="http://digg.com/">Digg</a> and additionally, you can compare them on a single chart:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWRKEyScytX4cCv51GU_aQx4BAio0EZSf-4MgyukKlEp-96qPdDuCONPRduPQkKwp-10nnkVIdF3iz5BLEC592XHbQ8nPbb2wu_BTDQ0CfXMHwUzfriCAZozpflv8CKMQ-lOMZ5oEgxibF/s1600-h/chart.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 150px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWRKEyScytX4cCv51GU_aQx4BAio0EZSf-4MgyukKlEp-96qPdDuCONPRduPQkKwp-10nnkVIdF3iz5BLEC592XHbQ8nPbb2wu_BTDQ0CfXMHwUzfriCAZozpflv8CKMQ-lOMZ5oEgxibF/s400/chart.png" alt="" id="BLOGGER_PHOTO_ID_5304130823414864610" border="0" /></a>Clearly you can see that Twitter is growing whereas Digg has some problems with keeping the levels.<br /><br />If you check Search Volume Index then you will get the same conclusion:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCakL8mHN-yILeaLkK1Nk0nUGP_IU7KWRdCz_hdMc10W6xK9JSb2-gj51uBp2i_xPSpqHO0Dzm3jzZUEWos7v7BQ8LUoitLR5nxLKKcds2o2deL8ClxNP4w50HovzBLNx8cljMta_jdrAN/s1600-h/chart2.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 151px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCakL8mHN-yILeaLkK1Nk0nUGP_IU7KWRdCz_hdMc10W6xK9JSb2-gj51uBp2i_xPSpqHO0Dzm3jzZUEWos7v7BQ8LUoitLR5nxLKKcds2o2deL8ClxNP4w50HovzBLNx8cljMta_jdrAN/s400/chart2.png" alt="" id="BLOGGER_PHOTO_ID_5304132325286743090" border="0" /></a>On this graph you don't see <a href="http://www.google.com/intl/en/trends/about.html#10">actual traffic numbers</a> ...<br /><blockquote>The numbers you see on the y-axis of the Search Volume Index aren't absolute search traffic numbers. Instead, Trends scales the first term you've entered so that its average search traffic in the chosen time period is 1.0; subsequent terms are then scaled relative to the first term. Note that all numbers are relative to total traffic. </blockquote>It's not my intention here to show you that Twitter is trendy, I want to give you an idea how interesting information you can find with Google Trends. You can now easily check how popular different ideas/technologies/products/politicians are. Try to see for instance how rapidly is growing number of searches for ASP.NET MVC - it's growing really fast. (Actually, thanks to an article <a href="http://codeclimber.net.nz/archive/2009/02/17/interest-in-asp.net-mvc-is-raising.aspx">Interest in ASP.NET MVC is raising</a> I have learned about Google Trends)<br /><br />Remember though, Google Trends will give you only estimated values, those are not accurate data:<br /><blockquote>It's important to keep in mind that all results from Trends for Websites are estimated. Moreover, the data is updated periodically, so recent changes in traffic data may not be reflected. Finally, keep in mind that Trends for Websites is a Google Labs product, so it's still in its early stages of development and may therefore contain some inaccuracies.</blockquote>In opinion ... even though the data are estimated and not all websites are included ... it's still an awesome tool!Unknownnoreply@blogger.com5tag:blogger.com,1999:blog-6586401656416839531.post-79085618414451730732009-02-17T22:10:00.005+01:002009-04-01T12:01:34.652+02:00FluentConfiguration -- New API to configure NHibernateFluent NHibernate from the very beginning provides really clean API to configure NHibernate. I didn't expect to see any changes in this area ... but yet new <span style="font-weight: bold;">"fluent"</span> way to configure NHibernate has been introduced.<br /><br />This is the way I was using so far (it still works well):<br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd">private</span> <span class="kwrd">static</span> Configuration GetNHibernateConfig()<br /></pre><pre><span class="lnum"> 2: </span>{</pre><pre class="alt"><span class="lnum"> 3: </span> <span class="kwrd">return</span> MsSqlConfiguration.MsSql2005</pre><pre><span class="lnum"> 4: </span> .ConnectionString(c => c.Is(<span class="str">@"Data Source=db_server;Database=db_name;...."</span>))</pre><pre class="alt"><span class="lnum"> 5: </span> .UseReflectionOptimizer()</pre><pre><span class="lnum"> 6: </span> .ShowSql()</pre><pre class="alt"><span class="lnum"> 7: </span> .ConfigureProperties(<span class="kwrd">new</span> Configuration());</pre><pre><span class="lnum"> 8: </span>}</pre><pre class="alt"><span class="lnum"> 9: </span><br /></pre><pre><span class="lnum"> 10: </span><span class="kwrd">public</span> <span class="kwrd">static</span> ISessionFactory GetSessionFactory()</pre><pre class="alt"><span class="lnum"> 11: </span>{</pre><pre><span class="lnum"> 12: </span> <span class="rem">// configure nhibernate</span></pre><pre class="alt"><span class="lnum"> 13: </span> Configuration config = GetNHibernateConfig();</pre><pre><span class="lnum"> 14: </span><br /></pre><pre class="alt"><span class="lnum"> 15: </span> var models = <span class="kwrd">new</span> PersistenceModel();</pre><pre><span class="lnum"> 16: </span> </pre><pre class="alt"><span class="lnum"> 17: </span> <span class="rem">// alter default conventions if necessary</span></pre><pre><span class="lnum"> 18: </span> SetUpConvention(models.Conventions);</pre><pre class="alt"><span class="lnum"> 19: </span><br /></pre><pre><span class="lnum"> 20: </span> models.addMappingsFromAssembly(<span class="kwrd">typeof</span> (Product).Assembly);</pre><pre class="alt"><span class="lnum"> 21: </span> models.Configure(config);</pre><pre><span class="lnum"> 22: </span><br /></pre><pre class="alt"><span class="lnum"> 23: </span> <span class="rem">// save xml files with mappings to some random location</span></pre><pre><span class="lnum"> 24: </span> models.WriteMappingsTo(<span class="str">@"c:\dev\mappings"</span>);</pre><pre class="alt"><span class="lnum"> 25: </span><br /></pre><pre><span class="lnum"> 26: </span> <span class="rem">// build factory</span></pre><pre class="alt"><span class="lnum"> 27: </span> <span class="kwrd">return</span> config.BuildSessionFactory();</pre><pre><span class="lnum"> 28: </span>}</pre><pre class="alt"><span class="lnum"> 29: </span><br /></pre></div><br />Honestly I didn't expect that readability can be improved much ... but check this:<br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd">public</span> <span class="kwrd">static</span> ISessionFactory GetFluentlyConfiguredSessionFactory()</pre><pre><span class="lnum"> 2: </span>{</pre><pre class="alt"><span class="lnum"> 3: </span> <span class="kwrd">return</span> Fluently.Configure()</pre><pre><span class="lnum"> 4: </span> .Database(MsSqlConfiguration</pre><pre class="alt"><span class="lnum"> 5: </span> .MsSql2005</pre><pre><span class="lnum"> 6: </span> .ConnectionString(c => c.Is(<span class="str">@"Data Source=db_server;Database=db_name;...."</span>)))</pre><pre class="alt"><span class="lnum"> 7: </span><br /></pre><pre><span class="lnum"> 8: </span> .Mappings(m =></pre><pre class="alt"><span class="lnum"> 9: </span> m.FluentMappings.AddFromAssemblyOf<Product>()</pre><pre><span class="lnum"> 10: </span> .ConventionDiscovery.Add(new AdventureWorksConvention())<span class="rem"></span></pre><pre class="alt"><span class="lnum"> 11: </span> .ExportTo(<span class="str">@"c:\dev\mappings"</span>))</pre><pre><span class="lnum"> 12: </span><br /></pre><pre class="alt"><span class="lnum"> 13: </span> .BuildSessionFactory();</pre><pre><span class="lnum"> 14: </span>}</pre></div><br />What I really like about Fluent NHibernate is that it doesn't force user to take all or nothing. If you want you can easily use combine different types of mappings. For instance you can add fluent-nh to your existing project, reuse old mappings (XML files) and add new mappings configured with fluent API. Here is an example:<br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd">public</span> <span class="kwrd">static</span> ISessionFactory GetFluentlyConfiguredSessionFactoryWithHbmFiles()</pre><pre><span class="lnum"> 2: </span>{</pre><pre class="alt"><span class="lnum"> 3: </span> <span class="kwrd">return</span> Fluently.Configure()</pre><pre><span class="lnum"> 4: </span> .Database(MsSqlConfiguration</pre><pre class="alt"><span class="lnum"> 5: </span> .MsSql2005</pre><pre><span class="lnum"> 6: </span> .ConnectionString(c =></pre><pre class="alt"><span class="lnum"> 7: </span> c.Is(<span class="str">@"Data Source=db_server;Database=db_name;...."</span>)))</pre><pre><span class="lnum"> 8: </span><br /></pre><pre class="alt"><span class="lnum"> 9: </span> .Mappings(m =></pre><pre><span class="lnum"> 10: </span> {</pre><pre class="alt"><span class="lnum"> 11: </span> m.FluentMappings.AddFromAssemblyOf<Product>()</pre><pre><span class="lnum"> 12: </span> .ConventionDiscovery.Add(new AdventureWorksConvention())</pre><pre class="alt"><span class="lnum"> 13: </span> .ExportTo(<span class="str">@"c:\dev\mappings"</span>);</pre><pre><span class="lnum"> 14: </span> m.HbmMappings.AddFromAssemblyOf<Product>();</pre><pre class="alt"><span class="lnum"> 15: </span> })</pre><pre><span class="lnum"> 16: </span><br /></pre><pre class="alt"><span class="lnum"> 17: </span> .BuildSessionFactory();</pre><pre><span class="lnum"> 18: </span>}</pre></div>In a similar way it is possible to combine standard fluent-nh mappings with XML files and auto mapping.<br /><br />I didn't include in this post anything about conventions to keep things short and concise but you can find details in this <a href="http://marekblotny.blogspot.com/2009/04/conventions-after-rewrite.html">post</a>.<br /><br /><span style="font-weight: bold;">Related posts:</span><br /><ul><li><a href="http://wiki.fluentnhibernate.org/show/DatabaseConfiguration">Database Configuration - Fluent NHibernate official wiki</a></li><li><a href="http://http//marekblotny.blogspot.com/2009/04/conventions-after-rewrite.html">Fluent NHibernate - Conventions After Rewrite</a><br /></li><li><a href="http://marekblotny.blogspot.com/2009/02/fluent-nhbernate-and-collections.html">Fluent NHibernate and Collections mapping</a></li></ul>Unknownnoreply@blogger.com2tag:blogger.com,1999:blog-6586401656416839531.post-42708196184193919092009-02-16T18:00:00.000+01:002009-02-16T18:00:00.982+01:00EPiServer - Outgoing LinksIn this post I will show how to get list of all referenced pages (and files) for any EPiServer page. Although it sounds like a trivial task, in fact, it's not so obvious. First of all it's necessary to realize that there are two major groups of "linking" properties:<br /><ul><li>Properties that derive from PropertyPageReference, internally they store link as a page id. Out of the box there in only one property type in EPiServer which uses this class -- PageReference.<br /></li><li>And the bunch of properties which use <a href="http://sdk.episerver.com/library/cms5/Developers%20Guide/Permanent%20Links.htm">permanent links</a> internally like:<ul><li>PropertyImageUrl - Url to image</li><li>PropertyDocumentUrl - Url to document</li><li>PropertyUrl - URL to page/external address</li><li>PropertyXhtmlString - Xhtml Long String</li><li>PropertyLinkCollection - <a href="http://marekblotny.blogspot.com/2009/02/episerver-5-r2-and-link-collection.html">Link Collection</a><br /></li></ul></li></ul>It's fairly simple to get referenced page from PropertyPageReference:<br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span>var pageReference = CurrentPage.Property[<span class="str">"propert_name"</span>] <span class="kwrd">as</span> PropertyPageReference;</pre><pre><span class="lnum"> 2: </span>var page = DataFactory.Instance.GetPage(pageReference.PageLink);</pre><br /></div>What about other property types? There is a one common thing for them -- they all implement <a href="http://sdk.episerver.com/library/cms5/html/T_EPiServer_Core_Transfer_IReferenceMap.htm">IReferenceMap</a> interface:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjEezmpw2FWwpphiLI0AMvieZju0aEKF2H7wFQdxo9NkenicJDwdpSXXrRGeXP5ZCFF39ARJNKG5FYfu5j1Buokd1TBM6PsrmeX_d3vUoJdb1OyfFzaa3e23hJMgtapJpT3I0xD-xMZMy3Y/s1600-h/IRefrence.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 370px; height: 214px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjEezmpw2FWwpphiLI0AMvieZju0aEKF2H7wFQdxo9NkenicJDwdpSXXrRGeXP5ZCFF39ARJNKG5FYfu5j1Buokd1TBM6PsrmeX_d3vUoJdb1OyfFzaa3e23hJMgtapJpT3I0xD-xMZMy3Y/s400/IRefrence.png" alt="" id="BLOGGER_PHOTO_ID_5303344466399929042" border="0" /></a><br />We can use following code to get outgoing links:<br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span>var referenceMap = property <span class="kwrd">as</span> IReferenceMap;</pre><pre><span class="lnum"> 2: </span><span class="kwrd">if</span> (referenceMap != <span class="kwrd">null</span>)</pre><pre class="alt"><span class="lnum"> 3: </span>{</pre><pre><span class="lnum"> 4: </span> IList<Guid> linkIds = referenceMap.ReferencedPermanentLinkIds;</pre><pre class="alt"><span class="lnum"> 5: </span> <span class="kwrd">foreach</span> (Guid guid <span class="kwrd">in</span> linkIds)</pre><pre><span class="lnum"> 6: </span> {</pre><pre class="alt"><span class="lnum"> 7: </span> PermanentLinkMap map = PermanentLinkMapStore.Find(guid);</pre><pre><span class="lnum"> 8: </span> </pre><pre class="alt"><span class="lnum"> 9: </span> <span class="rem">// mappedUrl example: /Templates/Public/Pages/NewsItem.aspx?id=30</span></pre><pre><span class="lnum"> 10: </span> <span class="kwrd">string</span> mappedUrl = map.MappedUrl.ToString();</pre><pre class="alt"><span class="lnum"> 11: </span><br /></pre><pre><span class="lnum"> 12: </span> <span class="rem">// and get friendly URL version using UrlRewriteProvider</span></pre><pre class="alt"><span class="lnum"> 13: </span> var url = <span class="kwrd">new</span> UrlBuilder(mappedUrl);</pre><pre><span class="lnum"> 14: </span> EPiServer.Global.UrlRewriteProvider.ConvertToExternal(url, <span class="kwrd">null</span>, System.Text.Encoding.UTF8);</pre><pre class="alt"><span class="lnum"> 15: </span> <span class="kwrd">string</span> friendlyUrl = UriSupport.AbsoluteUrlBySettings(url.ToString());</pre><pre><span class="lnum"> 16: </span> }</pre><pre class="alt"><span class="lnum"> 17: </span>}</pre></div><span style="font-weight: bold;"><br />What are permanent links?</span><br /><blockquote>Internal URL's in EPiServer are stored in the database using a format called Permanent Links. Property types are responsible to transform a URL from a permanent link to a standard template link upon access from user code, and of course the other way around before content is stored to the database. <br /></blockquote>It's a very useful feature of EPiServer because it enables you to manipulate files and pages without risk that some links will get broken.<br /><blockquote>You can rename files and templates without affecting the links; you can even move an EPiServer site from a virtual directory to a root site without breaking a single link.</blockquote><br /><span style="font-weight: bold;">Permanent Links and EPiserver's API</span><br /><br />IReferenceMap Interface exposes ReferencedPermanentLinkIds property thanks to which we have access to all links stored internally. That is very convenient especially for properties like PropertyXhtmlString which usually also store lots of other data. It is worth noticing that PropertyLongString doesn't implement this interface, hence it doesn't use permanent links. That is a reason why PropertyXhtmlString is recommended over PropertyLongString.<br /><br />PermanentLinkMapStore class is a part of EPiServer's API for permanent links. I used this class to get mapped URL based on link's Guid. In next step mapped URL can be converted to friendly URL (code based on <a href="http://labs.episerver.com/en/Blogs/Ted-Nyberg/Dates/112276/2/How-to-get-the-friendly-URL-of-a-page-in-EPiServer-CMS/">Ted Nyberg's post</a>). Permanent link can be broken (referenced page was deleted) in which case PermanentLinkStore.Find() method will return null.<br /><br />Based on above code I have created an edit mode plugin which lists all outgoing links, it looks like this:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRAHnraflrQ1HLFcOfaKJH4ydm-02dRjhLLzNELSkGeObl67LYnc_-6Ldt5m1-bG4nEmX2YU-wxYPc4m7fVZDg18mY_BuL-WYy_rcwPBp75-bIe4oUtt31hjzEWXuiz1lzgHIux08TOM-N/s1600-h/links-tab.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 210px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRAHnraflrQ1HLFcOfaKJH4ydm-02dRjhLLzNELSkGeObl67LYnc_-6Ldt5m1-bG4nEmX2YU-wxYPc4m7fVZDg18mY_BuL-WYy_rcwPBp75-bIe4oUtt31hjzEWXuiz1lzgHIux08TOM-N/s400/links-tab.png" alt="" id="BLOGGER_PHOTO_ID_5303366678678936338" border="0" /></a>Source code can be downloaded from <a href="http://marekblotny.googlepages.com/OutgoingLinksPlugin.zip">here</a>.<br /><br /><span style="font-weight: bold;">Other interesting posts</span>:<br /><ul><li><a href="http://world.episerver.com/Articles/Items/Link-Management-in-EPiServer-CMS-5-R1-Under-the-Hood/">Link Management in EPiServer CMS 5 R1 Under the Hood</a></li><li><a href="http://marekblotny.blogspot.com/2009/01/ultimate-diagnostic-tool-for-episerver.html">Ultimate diagnostic tool for EPiServer</a></li><li><a href="http://marekblotny.blogspot.com/2009/02/episerver-5-r2-and-link-collection.html">EPiServer 5 R2 and Link Collection property</a><br /></li></ul>Unknownnoreply@blogger.com9tag:blogger.com,1999:blog-6586401656416839531.post-31601885468646273422009-02-14T10:30:00.000+01:002009-02-14T10:30:00.871+01:00Basic Software Estimation ConceptsIn one of my recent posts I was writing that <a href="http://marekblotny.blogspot.com/2009/01/single-point-estimates-are-meaningless.html">single point estimates are meaningless</a>. In this post I would like to carry on with this topic and talk about a few other fundamental concepts for software estimation based on Steve McConnell's "<a href="http://www.microsoft.com/learning/en/us/books/2425.aspx" border="0"><span class="b24-booktitle">Software Estimation: Demystifying the Black Art</span></a>".<br /><br /><span>One of the most important things is to know the difference between estimates, targets and commitments.</span><br /><blockquote>While a <span style="font-style: italic;">target</span> is a description of a desirable business objective, a <i class="emphasis">commitment</i> is a promise to deliver defined functionality at a specific level of quality by a certain date. A commitment can be the same as the estimate, or it can be more aggressive or more conservative than the estimate. In other words, do not assume that the commitment has to be the same as the estimate; it doesn't.</blockquote>It's quite typical situation when developers are asked to estimate new project or piece of functionality for which deadline is already set. You have to know if you are really asked to provide estimates or to figure our how to meet a deadline. Those are two totally different things. <span style="font-weight: bold;">Estimation should be unbiased</span> therefore deadline doesn't matter. If deadline matters then you are, in fact, asked to provide a plan in which goal is to deliver before the deadline.<br /><a href="http://marekblotny.blogspot.com/2009/01/single-point-estimates-are-meaningless.html"><br />Single point estimates are meaningless</a>, <span style="font-weight: bold;">estimations should always be represented as a range</span> -- the best and the worst scenario. Don't estimate only at the beginning of a project. At every stage estimates can be useful and they can show that project goal is in danger.<br /><blockquote>Once we make an estimate and, on the basis of that estimate, make a <a name="69"></a><a name="IDX-12"></a>commitment to deliver functionality and quality by a particular date, then we <i class="emphasis">control</i> the project to meet the target. Typical project control activities include removing non-critical requirements, redefining requirements, replacing less-experienced staff with more-experienced staff, and so on</blockquote> Controlling the project include dealing with changing requirements. But with new requirements estimations also change and after a few iterations your target is to deliver something radically different then it was estimated at the very beginning. How can you say then if initial estimates were accurate?<br /><blockquote><p class="para">In practice, if we deliver a project with about the level of functionality intended, using about the level of resources planned, in about the time frame targeted, then we typically say that the project "met its estimates," despite all the analytical impurities implicit in that statement.</p></blockquote><p class="para"></p><p class="para">If it's well known that assumptions will change, functionality will change then what is the real purpose of estimates?<br /></p><p class="para"></p><blockquote>The primary purpose of software estimation is not to predict a project's outcome; it is to determine whether a project's targets are realistic enough to allow the project to be controlled to meet them.</blockquote><p></p><p class="para">Important implication is that gap between estimates and actual times has to be small enough to be manageable. According to book, 20% is the limit which can be controlled.<br /></p><p class="para"></p><blockquote>Estimates don't need to be perfectly accurate as much as they need to be <i class="emphasis">useful</i>. When we have the combination of accurate estimates, good target setting, and good planning and control, we can end up with project results that are close to the <a name="73"></a><a name="IDX-14"></a>"estimates." </blockquote><p></p>That takes us to a definition of <span style="font-weight: bold;">"good estimate"</span><br /><span class="emphasis"></span><blockquote><span class="emphasis">A good estimate is an estimate that provides a clear enough view of the project reality to allow the project leadership to make good decisions about how to control the project to hit its targets</span><span class="unicode">.</span> </blockquote>All of that and much more can be found in the book, it's worth reading.Unknownnoreply@blogger.com85tag:blogger.com,1999:blog-6586401656416839531.post-61176235796995616512009-02-12T23:50:00.005+01:002009-02-13T00:12:08.737+01:00EPiServer 5 R2 and Link Collection propertyWith EPiServer 5 R2 new property type was released -- Link Collection. It looks like a EPiServer's version of very popular <a href="https://www.coderesort.com/p/epicode/wiki/MultiPageProperty">Mulitipage property</a>. In this post I would like to show you exactly how it can be used and also what are the pros and cons.<br /><br />After adding a property of this type to a page you will see in edit mode this:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjSohUWmtfJbN4Ks-x88fFDeN0ImFjt_UjAkO7bC4ROdqm2h2oe0RRcBNWjR_5nYsvoTygTjcrrwPT98XRuKBjFz0inWNvwGdjdLW-1zWwcrNPlC5NpnZ-GbpzsgFcVQWlbf2zfS9ky9Hyf/s1600-h/linksCollection1.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 123px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjSohUWmtfJbN4Ks-x88fFDeN0ImFjt_UjAkO7bC4ROdqm2h2oe0RRcBNWjR_5nYsvoTygTjcrrwPT98XRuKBjFz0inWNvwGdjdLW-1zWwcrNPlC5NpnZ-GbpzsgFcVQWlbf2zfS9ky9Hyf/s400/linksCollection1.png" alt="" id="BLOGGER_PHOTO_ID_5302032782625221490" border="0" /></a><br />And with a few links added property looks like this:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVCRZG7eGjQAMakOfQY16PbXHPWfZvxrNxB0LBIKUVcKnmWkSr26KL6KIdDeIX9lTmvqi-J-2F3eB-3wXHO4kqq-r-va349PSU1rrxWvUPIQioGbeEba4ZgU2Pt5TtcYl7Xt87rEz3KiEZ/s1600-h/linksCollection3.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 229px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVCRZG7eGjQAMakOfQY16PbXHPWfZvxrNxB0LBIKUVcKnmWkSr26KL6KIdDeIX9lTmvqi-J-2F3eB-3wXHO4kqq-r-va349PSU1rrxWvUPIQioGbeEba4ZgU2Pt5TtcYl7Xt87rEz3KiEZ/s400/linksCollection3.png" alt="" id="BLOGGER_PHOTO_ID_5302032781487651106" border="0" /></a><br />This is first significant change comparing to old Multipage property (MP) -- list of all links is visible on the page. With old MP it was necessary to click on the button to get a popup with a list of links. That is a good change!<br /><br />What is missing here for me is a ability to test links. Text which you can see for the first item on the list is not necessary a page name (it might be a clickable text) so it's impossible to figure out from this view what page is referenced.<br /><br />Funny thing is that title for this link has a following form:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMf_rVDU_QHgvH3_mJbgChawV6X4EhPo17fUxehK_JE2BzM6-oSt-JXysO6jQHrSpSnd7Qq9ZEQDpOjo-mW4HpLxbQtk4cx9Zv6QhXOYH-v6djB2KOAT2IJ6o1CTn8UVT9quyN0YqoNDjD/s1600-h/linksCollection5.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 325px; height: 85px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMf_rVDU_QHgvH3_mJbgChawV6X4EhPo17fUxehK_JE2BzM6-oSt-JXysO6jQHrSpSnd7Qq9ZEQDpOjo-mW4HpLxbQtk4cx9Zv6QhXOYH-v6djB2KOAT2IJ6o1CTn8UVT9quyN0YqoNDjD/s400/linksCollection5.png" alt="" id="BLOGGER_PHOTO_ID_5302036086790175650" border="0" /></a>It is very useful isn't it? ;) I think the simplest solution would be to make link text clickable.<br /><br />After clicking on 'Add Link' or 'Edit' button you will get old popup:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIByy8ZY0kZtKJRNRVyEAXG1nyIg-3cr_TgZlRrKMIwgn8CQladmcnPYMhBIHP0dWukP4E_mcc3FGXFQY5lPotUSls2IEH8WVf2a2o_d8xHs_hG4qhuLnetWSLfW3oAUQvZioint451VVQ/s1600-h/linksCollection4.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 388px; height: 400px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIByy8ZY0kZtKJRNRVyEAXG1nyIg-3cr_TgZlRrKMIwgn8CQladmcnPYMhBIHP0dWukP4E_mcc3FGXFQY5lPotUSls2IEH8WVf2a2o_d8xHs_hG4qhuLnetWSLfW3oAUQvZioint451VVQ/s400/linksCollection4.png" alt="" id="BLOGGER_PHOTO_ID_5302034373209312050" border="0" /></a><br />There are no surprises here, it's an old well-know dialog.<br /><br />Lets check now how to deal with Link collection in a code. It's quite common to use Repeater to display links:<br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd"><</span><span class="html">asp:Repeater</span> <span class="attr">ID</span><span class="kwrd">="rptRelatedLinks"</span> <span class="attr">runat</span><span class="kwrd">="server"</span><span class="kwrd">></span></pre><pre><span class="lnum"> 2: </span> <span class="kwrd"><</span><span class="html">HeaderTemplate</span><span class="kwrd">><</span><span class="html">dl</span><span class="kwrd">></</span><span class="html">HeaderTemplate</span><span class="kwrd">></span></pre><pre class="alt"><span class="lnum"> 3: </span> <span class="kwrd"><</span><span class="html">ItemTemplate</span><span class="kwrd">><</span><span class="html">dt</span><span class="kwrd">><</span><span class="html">asp:HyperLink</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="attr">ID</span><span class="kwrd">="hplMainLink"</span> <span class="kwrd">/></</span><span class="html">dt</span><span class="kwrd">></</span><span class="html">ItemTemplate</span><span class="kwrd">></span></pre><pre><span class="lnum"> 4: </span> <span class="kwrd"><</span><span class="html">FooterTemplate</span><span class="kwrd">></</span><span class="html">dl</span><span class="kwrd">></</span><span class="html">FooterTemplate</span><span class="kwrd">></span></pre><pre class="alt"><span class="lnum"> 5: </span><span class="kwrd"></</span><span class="html">asp:Repeater</span><span class="kwrd">></span></pre><br /></div>And here is a code to get links from the CurrentPage:<br /><div class="csharpcode"><br /><pre class="alt"><span class="lnum"> 1: </span>PropertyLinkCollection links = (PropertyLinkCollection) CurrentPage.Property[<span class="str">"RelatedLinks"</span>];</pre><pre><span class="lnum"> 2: </span>rptRelatedLinks.DataSource = links;</pre><pre class="alt"><span class="lnum"> 3: </span>rptRelatedLinks.DataBind();</pre></div><br />And a method populating the Repeater:<br /><br /><div class="csharpcode"><pre class="alt"><span class="lnum"> 1: </span><span class="kwrd">void</span> rptRelatedLinks_ItemDataBound(<span class="kwrd">object</span> sender, RepeaterItemEventArgs e)</pre><pre><span class="lnum"> 2: </span>{</pre><pre class="alt"><span class="lnum"> 3: </span> <span class="kwrd">if</span> (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)</pre><pre><span class="lnum"> 4: </span> {</pre><pre class="alt"><span class="lnum"> 5: </span> LinkItem linkItem = (LinkItem) e.Item.DataItem;</pre><pre><span class="lnum"> 6: </span> HyperLink link = (HyperLink) e.Item.FindControl(<span class="str">"hplMainLink"</span>);</pre><pre class="alt"><span class="lnum"> 7: </span><br /></pre><pre><span class="lnum"> 8: </span> <span class="rem">// mapped link has a form like:</span></pre><pre class="alt"><span class="lnum"> 9: </span> <span class="rem">// <a href="/Templates/Public/Pages/Page.aspx?id=16&epslanguage=en" target="_blank" </span></pre><pre><span class="lnum"> 10: </span> <span class="rem">// title="this is link title">Information about the meeting</a></span></pre><pre class="alt"><span class="lnum"> 11: </span> <span class="kwrd">string</span> mappedLink = linkItem.ToMappedLink();</pre><pre><span class="lnum"> 12: </span> </pre><pre class="alt"><span class="lnum"> 13: </span> <span class="rem">// permanent link form:</span></pre><pre><span class="lnum"> 14: </span> <span class="rem">// <a href="~/link/bb6aa3227f8f467bbe1a42154cb56ba5.aspx" target="_blank" </span></pre><pre class="alt"><span class="lnum"> 15: </span> <span class="rem">// title="this is link title">Information about the meeting</a></span></pre><pre><span class="lnum"> 16: </span> <span class="kwrd">string</span> permanentLink = linkItem.ToPermanentLink();</pre><pre class="alt"><span class="lnum"> 17: </span><br /></pre><pre><span class="lnum"> 18: </span> <span class="rem">// because Href property will return permanent link like </span></pre><pre class="alt"><span class="lnum"> 19: </span> <span class="rem">// ~/link/bb6aa3227f8f467bbe1a42154cb56ba5.aspx</span></pre><pre><span class="lnum"> 20: </span> <span class="rem">//</span></pre><pre class="alt"><span class="lnum"> 21: </span> <span class="rem">// it's necessary to use PermanentLinkMapStore.ToMapped(url) to covert it to normal form</span></pre><pre><span class="lnum"> 22: </span> <span class="rem">// result is required to determine if conversion was successful</span></pre><pre class="alt"><span class="lnum"> 23: </span> <span class="rem">// it will fail for mails (mailto:test@test.com), documents and external links</span></pre><pre><span class="lnum"> 24: </span> UrlBuilder url = <span class="kwrd">new</span> UrlBuilder(linkItem.Href);</pre><pre class="alt"><span class="lnum"> 25: </span> <span class="kwrd">bool</span> result = PermanentLinkMapStore.ToMapped(url);</pre><pre><span class="lnum"> 26: </span><br /></pre><pre class="alt"><span class="lnum"> 27: </span> link.NavigateUrl = result ? url.ToString() : linkItem.Href;</pre><pre><span class="lnum"> 28: </span> link.Text = linkItem.Text;</pre><pre class="alt"><span class="lnum"> 29: </span> link.ToolTip = linkItem.Title;</pre><pre><span class="lnum"> 30: </span> link.Target = linkItem.Target;</pre><pre class="alt"><span class="lnum"> 31: </span> }</pre><pre><span class="lnum"> 32: </span>}</pre><br /></div>The basic problem is that Href property returns permanent link, therefore it's necessary to use PermanentLinkMapStore class to convert the links. ToMappedLink() method returns a full "a" tag, which might be convenient in some cases. Take a look on all properties again:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0iU1sObxbxcCjiPCyM4tkw5iFoLMShyphenhyphenXsik2MceOy4-Pqw5pIamWsAuv6OHouk4ufGW_BCf0vWuC6VJasUY306RtqH7rB3kQLRsSx9KOwfO2Xn12SWKR35HYyR4iQfDQJvMy-urMF_F6D/s1600-h/linksCollection6.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 86px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0iU1sObxbxcCjiPCyM4tkw5iFoLMShyphenhyphenXsik2MceOy4-Pqw5pIamWsAuv6OHouk4ufGW_BCf0vWuC6VJasUY306RtqH7rB3kQLRsSx9KOwfO2Xn12SWKR35HYyR4iQfDQJvMy-urMF_F6D/s400/linksCollection6.png" alt="" id="BLOGGER_PHOTO_ID_5302040688776365634" border="0" /></a>My overall impression is positive, new property Link collection is easy to use but for sure there are things which could be improved like ability to test a link or ability to define page root to look for pages to include. It's a hassle to always start from the very top!<br /><br />I wonder now if there is still a reason to use old Multipage property, what do you think?<br /><br />Related posts:<br /><ul><li><a href="http://marekblotny.blogspot.com/2008/08/episerver-multipageproperty-dont-use.html">MultipageProperty -- don't use SelectedPages property!</a></li><li><a href="http://marekblotny.blogspot.com/2008/12/problems-with-episerver-5-r2-and-ms.html">Problems with EPiServer 5 R2 and MS Vista</a></li><li><a href="http://marekblotny.blogspot.com/2009/01/ultimate-diagnostic-tool-for-episerver.html">Ultimate diagnostic tool for EPiServer</a></li><li><a href="http://marekblotny.blogspot.com/2008/09/edit-page-shortcutexternal-link-tab.html">Edit Page - Shortcut/External link tab</a><br /></li></ul>Unknownnoreply@blogger.com9tag:blogger.com,1999:blog-6586401656416839531.post-50612294623225328712009-02-08T10:00:00.001+01:002009-02-08T22:21:35.310+01:00That's what I call a comfortable office<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjU1YJVBjjTTr89rLhpmXD2yZIRYH5a1eg8z0GVvgMFu9N6ndAJPyz1NrpWczBJ7Tc6ovNQlys6ZXobuzTXSaW84CYRNtm4NYZjGXRSSw9ODleNeEOZzS3cWea3I2E_Bd4E_U41kmCzrv_J/s1600-h/DSC_1740.NEF.jpg"><img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer; width: 200px; height: 134px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjU1YJVBjjTTr89rLhpmXD2yZIRYH5a1eg8z0GVvgMFu9N6ndAJPyz1NrpWczBJ7Tc6ovNQlys6ZXobuzTXSaW84CYRNtm4NYZjGXRSSw9ODleNeEOZzS3cWea3I2E_Bd4E_U41kmCzrv_J/s200/DSC_1740.NEF.jpg" alt="" id="BLOGGER_PHOTO_ID_5300348560613814514" border="0" /></a>You guys know Joel Spolsky right? At the moment he is a CEO at <a href="http://www.fogcreek.com/">Fog Creek Software</a>, small company in Manhattan. I'm really impressed with their new office, take a look on a <a href="http://picasaweb.google.com/spolsky/FogCreekSNewOffice#">slideshow</a>.<br /><br />Joel was writing a few times that his main goal is to provide the best possible environment for software developers and this way gain the highest productivity:<br /><br /><blockquote>Building great office space for software developers serves two purposes: increased productivity, and increased recruiting pull. Private offices with doors that close prevent programmers from interruptions allowing them to concentrate on code without being forced to stop and listen to every interesting conversation in the room. And the nice offices wow our job candidates, making it easier for us to attract, hire, and retain the great developers we need to make software profitably. It’s worth it, especially in a world where so many software jobs provide only the most rudimentary and depressing cubicle farms.</blockquote> I know exactly how important that is as I'm lucky to work for company having the same goal. But still, there are things I feel jealous about after checking the slideshow. Beside the awesome design I especially like <span style="" class="gphoto-photocaption-caption">30“ monitors (I had a chance to work a bit on 22</span><span style="" class="gphoto-photocaption-caption">“ monitors and I'm 100% convinced that it makes a difference) or </span><span style="" class="gphoto-photocaption-caption">long desks (huge monitors require huge desks ;)</span> ). I'm sure that one day I will talk my boss into buying such a nice monitors for us ;)<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjix5d1mC2hu2XrfBkHlHKseMaFXBptkltw3d5-lskwZp12DJ0dB7yIMo9TnukkHzEccLZTZhhkMklHG4XI8FWrZyNad1Mg-rRyxpOy-eJLL4cG8p66Ni7mVWbthMijaTr5jWNqFE5BBSYP/s1600-h/DSC_1456.NEF.jpg"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 268px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjix5d1mC2hu2XrfBkHlHKseMaFXBptkltw3d5-lskwZp12DJ0dB7yIMo9TnukkHzEccLZTZhhkMklHG4XI8FWrZyNad1Mg-rRyxpOy-eJLL4cG8p66Ni7mVWbthMijaTr5jWNqFE5BBSYP/s400/DSC_1456.NEF.jpg" alt="" id="BLOGGER_PHOTO_ID_5300345715764282930" border="0" /></a><br />Here you can find Joel's <a href="http://www.joelonsoftware.com/items/2008/12/29.html">post about new office</a>, and an article about the office in <a href="http://www.nytimes.com/2009/02/08/realestate/commercial/08sqft.html?_r=1&partner=permalink&exprod=permalink">The New York Times</a>.Unknownnoreply@blogger.com7