tag:blogger.com,1999:blog-68178087812109194912024-02-20T23:35:47.571+01:00Mirosław Jedynak - .NET blogNews, interesting features, resolved problems in .Net FrameworkMirosław Jedynakhttp://www.blogger.com/profile/03407217246259905745noreply@blogger.comBlogger17125tag:blogger.com,1999:blog-6817808781210919491.post-35682318281223908282013-02-22T08:00:00.000+01:002013-02-22T08:00:05.143+01:00Geo Point Picker property for EPiServer 7 from Making Waves<b>Piotr Dela</b> from <a href="http://makingwaves.com">Making Waves</a> created <b><a href="http://openwaves.codeplex.com/wikipage?title=OpenWaves.EPiServer.GeoPickerProperty">Geo Point Picker</a></b> property for EPiServer7
<br />
<br />
GeoPicker property is based on Google Maps API and provides a rich interface for selecting a geo-coordinates – perfect for editing location of points of interests we want to show on a map.
<br />
<ul>
<li>Source code is available at: <a href="https://openwaves.codeplex.com/SourceControl/changeset/view/85301#1685614">https://openwaves.codeplex.com/SourceControl/changeset/view/85301#1685614</a></li>
<li> Documentation is at: <a href="http://openwaves.codeplex.com/wikipage?title=OpenWaves.EPiServer.GeoPickerProperty">http://openwaves.codeplex.com/wikipage?title=OpenWaves.EPiServer.GeoPickerProperty</a></li>
<li>NuGet package: <a href="http://nuget.org/packages/OpenWaves.EPiServer.GeoProperties/">http://nuget.org/packages/OpenWaves.EPiServer.GeoProperties/</a> </li>
</ul>
<br/>
<b><span style="font-size: large;">Installation</span></b>
<br/>
<br/>
Install <b>OpenWaves.EPiServer.GeoProperties</b> NuGet package from <a href="http://nuget.org/packages/OpenWaves.EPiServer.GeoProperties/">http://nuget.org/packages/OpenWaves.EPiServer.GeoProperties/</a>
<br/>
<br/>
<b><span style="font-size: large;">Getting started</span></b>
<br/>
To use the property set the type of the property to GeoPoint.
<pre class="brush: csharp">using OpenWaves.EPiServer.GeoProperties;
...
[UIHint(PropertyGeoPoint.UiHint)]
public virtual GeoPoint CustomLocation { get; set; }</pre>
<br/>
<b><span style="font-size: large;">From edit mode</span></b>
<br/>
<br/>
Property editor lets editors type the geo-coordinates in Lat/Long number inputs or click on the location thumbnail to display a richer UI.
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHAo6PVYHE-UANw3yz96jhYrxvDgCYhMLnBZgM2zEWTGhX6ebbH96FhjRUxn1Dxfpo_8T5i2izOmTm6ECb1YbbaNqdbk2HLSgt2q5SjvYlLhMg2EPWVb_01XIaDgSywdAcHUImuRNo1mE/s1600/geo_point_picker_1.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHAo6PVYHE-UANw3yz96jhYrxvDgCYhMLnBZgM2zEWTGhX6ebbH96FhjRUxn1Dxfpo_8T5i2izOmTm6ECb1YbbaNqdbk2HLSgt2q5SjvYlLhMg2EPWVb_01XIaDgSywdAcHUImuRNo1mE/s320/geo_point_picker_1.png" /></a>
<br />
<br />
The popup lets editors search for locations and/or select a point by dragging a marker over the map.
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhd9AMwF7mKn-JTzcvj0meQP5Ns85ZKtN7x5L-s3SngalqySKj84-p4yPXZnubgLzQdKOgylVZl1EIXrjvrJ-WwCxnsWxv7bUqD56y_LZ7n7kLbCTycaMgDH889zguDOHFUKyfLr3MJI4E/s1600/geo_point_picker_2.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhd9AMwF7mKn-JTzcvj0meQP5Ns85ZKtN7x5L-s3SngalqySKj84-p4yPXZnubgLzQdKOgylVZl1EIXrjvrJ-WwCxnsWxv7bUqD56y_LZ7n7kLbCTycaMgDH889zguDOHFUKyfLr3MJI4E/s320/geo_point_picker_2.png" /></a>
<br />
<br />
After the location is selected, property editor shows a thumbnail of the location and its geo-coordinates.
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvzfEGCFn4SdNGCpyy1G4enCmpbkVWfyYKhzOY9gq1WTyCK4Bei0w0myX6k2SKqke6oDMAy-kCymcBKzRxfuu4qjhDGJFdEp45gvQ8eM8y-Mun6k-JhpihIpfJxLoWhrT1j6TiFwcqkp8/s1600/geo_point_picker_3.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvzfEGCFn4SdNGCpyy1G4enCmpbkVWfyYKhzOY9gq1WTyCK4Bei0w0myX6k2SKqke6oDMAy-kCymcBKzRxfuu4qjhDGJFdEp45gvQ8eM8y-Mun6k-JhpihIpfJxLoWhrT1j6TiFwcqkp8/s320/geo_point_picker_3.png" /></a>Mirosław Jedynakhttp://www.blogger.com/profile/03407217246259905745noreply@blogger.com40tag:blogger.com,1999:blog-6817808781210919491.post-53322985747778843942013-02-19T11:49:00.001+01:002013-02-21T10:09:56.877+01:00OpenWaves.EPiServer.Localization: Strongly typed access to EPiServer language filesRecently Making Waves released OpenWaves.EPiServer.Localization NuGet package. It provides strongly typed access to EPiServer lang.
Now you can use:
<pre class="brush: csharp">
string text = TranslationKeys.MyPage.MyCategory.Intro.GetString();
// instead of
// string text = LocalizationService.Current.GetString("/myPage/myCategory/intro");
</pre>
<br />
<b><span style="font-size: large;">UPDATE: EPiServer 6 & 7 supported</span></b>
<p>Localization package support both EPiServer 6 & 7. To change version change <b>Resources\TranslationKeys.tt</b></p>
<pre class="brush: csharp">
...
<#@ import namespace="OpenWaves.EPiServer.Localization.Transformations" #>
<#
var epiServerVersion = 7;
// for EPiServer 6.x version
// var epiServerVersion = 6;
...
</pre>
<br />
<b><span style="font-size: large;">Benefits</span></b>
<ul>
<li><b>Intelli Sense support</b></li>
<li>Compile time check for correct language keys</li>
<li>When refactoring all occurrences are renamed</li>
<li>Supports "Find all usages" of language keys</li>
</ul>
<br />
<b><span style="font-size: large;">Usage</span></b>
<br />
<ol>
<li>Add NuGet package OpenWaves.EPiServer.Localization
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhge2s-hsE7kNNnU08YK2xsJVSwHynW51f43lGusRAcKMtesIkqV7QdEn0KsKo7BpxDJsAA7Tn5p30FX_fsQYLy5l_a28LmzTS-mXe05l_u70ATuu1qOCXLiSffqvkyyUuZzCDZOVm-FQI/s1600/manage_packages.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhge2s-hsE7kNNnU08YK2xsJVSwHynW51f43lGusRAcKMtesIkqV7QdEn0KsKo7BpxDJsAA7Tn5p30FX_fsQYLy5l_a28LmzTS-mXe05l_u70ATuu1qOCXLiSffqvkyyUuZzCDZOVm-FQI/s320/manage_packages.png" /></a>
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgRoo2UKh3brGTgrKwuRKx7sPNYIPk3obr7ux96ld94BaaxrlmGcOLfXuzpbnO5BfTU5x2FcSIaSg-oRApiluyvhnns4H4ZDn1kclhdvInwPmWz0YABpftzApsQiE4RTAolGc-f-QG3f4Q/s1600/install.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgRoo2UKh3brGTgrKwuRKx7sPNYIPk3obr7ux96ld94BaaxrlmGcOLfXuzpbnO5BfTU5x2FcSIaSg-oRApiluyvhnns4H4ZDn1kclhdvInwPmWz0YABpftzApsQiE4RTAolGc-f-QG3f4Q/s320/install.png" /></a>
</li>
<li>It's ready to use!</li>
</ol>
<br />
<br />
<b><span style="font-size: large;">NOTE: After changing language file (*.xml) you have to"Run Custom Tool" manually to regenerate translation key classes</span></b>
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtitZP5lrBYtCYPdsqpMt_UBorT7JljrWtA7_41r0SSEqAs05o2EzO9tm3tSoRfv9lU8zDKIpKgQDJKuDpWgKAv0CadETur6DdBSWuXVW7fVv3lLeyJgSyd1KBkZgdaX9HnvqXAUrmbOc/s1600/run_custom_tool.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtitZP5lrBYtCYPdsqpMt_UBorT7JljrWtA7_41r0SSEqAs05o2EzO9tm3tSoRfv9lU8zDKIpKgQDJKuDpWgKAv0CadETur6DdBSWuXVW7fVv3lLeyJgSyd1KBkZgdaX9HnvqXAUrmbOc/s320/run_custom_tool.png" /></a>Mirosław Jedynakhttp://www.blogger.com/profile/03407217246259905745noreply@blogger.com4tag:blogger.com,1999:blog-6817808781210919491.post-91098476998728800892012-12-02T13:35:00.000+01:002013-10-23T19:21:07.029+02:00TechCamp #4: Quality Assurance vs Quality ControlPanel dyskusyjny (prowadzi Krzysztof Helak, Profeo) Quality assurance – czy tworzenie bezbłędnego oprogramowania jest możliwe?
<br/>
<br/>
<iframe width="640" height="480" src="http://www.youtube.com/embed/IfY2B_pavu0" frameborder="0" allowfullscreen></iframe>
Mirosław Jedynakhttp://www.blogger.com/profile/03407217246259905745noreply@blogger.com0tag:blogger.com,1999:blog-6817808781210919491.post-64741821587527714102012-06-16T16:51:00.001+02:002013-10-22T18:00:30.050+02:00Continuous Delivery story with FIFARecorded 7th of June 2012 at NDC in Oslo (<a href="http://www.ndcoslo.com/" target="_blank">www.ndcoslo.com</a>)<br />
<br />
<iframe src="http://player.vimeo.com/video/43612953" width="800" height="450" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe> <p><a href="http://vimeo.com/43612953">Mirosław Jedynak - Continuous Delivery story with FIFA</a> from <a href="http://vimeo.com/ndcoslo">NDCOslo</a> on <a href="http://vimeo.com">Vimeo</a>.</p>
<b><span style="font-size: large;">Introduction</span></b><br/>
<p>Continuous Delivery is the process of having a shippable product after each check-in to the source control repository. <em>Continuous Delivery</em> is usually implemented as a natural improvement of a <em>Continuous Integration</em> process. This presentation highlights challenges and presents hints on how to start from a raw environment and incrementally build a successful deployment pipeline based on Team Foundation Server (TFS), providing substantial added-value for business.
</p><p>
This presentation will describe the process of establishing Continuous Delivery in a project for FIFA. We describe the starting point, what we achieve in the first phases and what are the plans for further improvements in order to deliver high quality software in schedules defined by business needs – not by process and technology constraints.
</p>
<b><span style="font-size: large;">FIFA project</span></b><br/>
<p>
Making Waves took over as a Services Provider for the development and maintenance of FIFA's Intranet and Extranet platform in 2011. The main challenge was to avoid long release cycles, improve quality and provide a reliable hotfix strategy for urgent issues raised in production.
</p><p>
The first phase of the project was focused on taking over the source code, development, test and production environments. This was a challenging task, mostly because of a lack of automation in build and deployment processes. This part of the presentation will cover possible approaches for how to incrementally create a flexible development environment, supported by a continuous integration process, in a legacy project inherited from an external part.
</p><p>
The goal of the second project phase was to implement a continuous delivery process in the project. I will present the main arguments for investing in tools and processes which enable more frequent and automated releases, and how that brings significant business value.
</p><p>
We will also cover how we implemented a set of practices and principles aimed at building, testing and releasing software faster and more frequently, including (but not limited to): deployment automation, release repository, production configuration tracking and version promotion between environments.
</p><p>
The presentation will briefly cover tools which were used, including Team Foundation Server (TFS), but most of the content is technology agnostic and is relevant for both developers and more-business oriented people.</p>Mirosław Jedynakhttp://www.blogger.com/profile/03407217246259905745noreply@blogger.com0tag:blogger.com,1999:blog-6817808781210919491.post-76829057225327541162011-10-21T10:08:00.000+02:002011-10-21T10:08:17.674+02:00How to track currently deployed code version (revision from source control)<br />
<span style="font-size: small;">General rule of thumb is that at any moment in time you should be able to answer question: <b>Which version (revision in svn) of code is currently deployed on production?</b></span><br />
<span style="font-size: small;"><i> </i></span><span style="font-size: small;"> </span><br />
<span style="font-size: small;">In this post I will present four approaches which can be used to determine this. I will start from simplest but least recommended ending with recommended but also relatively easy to implement solution. </span><br />
<br />
<b><span style="font-size: large;">Introduction</span></b><span style="font-size: small;"><br /><i></i></span><br />
<span style="font-size: small;"> To be able to provide answer for question regarding deployed code version you can:</span><br />
<ol>
<li><span style="font-size: small;">remember version</span></li>
<li><span style="font-size: small;">update svn_revision.txt file on production server</span></li>
<li><span style="font-size: small;">create tag in subversion</span></li>
<li><span style="font-size: small;">store revision in number in assembly</span></li>
</ol>
<br />
<span style="font-size: small;"><b><span style="font-size: large;">1. Remembering - Storing in human memory</span></b><br />I don’t trust even myself that I will manage remember whether 1789 or 1798 revision was deployed yesterday. If I find someone with better short-time memory than my I still would not be convinced to this approach... Forget this way…<br /><span style="font-size: large;"> </span></span><br />
<span style="font-size: small;"><b><span style="font-size: large;">2. Updating svn_revision.txt file</span></b><br />You can promise to each other in your team that you will update this file with current revision just after deploying new version to production, but… Remember you should have consistent deployment procedure for each server so don’t forget to update on:</span><br />
<ul>
<li><span style="font-size: small;">production</span></li>
<li><span style="font-size: small;">staging</span></li>
<li><span style="font-size: small;">acceptance test environment</span></li>
<li><span style="font-size: small;">integration environment (where each build is dropped from build server)</span></li>
</ul>
<span style="font-size: small;">It’s not the case if you forget but when you forget to update this. I can bet that when you’re in hurry deploying hotfix to hotfix when site is down you won’t care about such small detail as revision version in text file. <br /><span style="font-size: large;"> </span></span><br />
<span style="font-size: small;"><b><span style="font-size: large;">3. Creating tag in subversion</span></b><br />It’s common practice to create tag to associate revision with specific application version (check svn book). It can be easily used to track which version goes to production and later any team member can check repository. It seems to be more convenient than storing in text file on server but:</span><br />
<ul>
<li><span style="font-size: small;">if we consistently treat deployment to each environment we should also tag deployment to each of them, It can cause creating many tag for version which has never reached even staging environment</span></li>
<li><span style="font-size: small;">It’s hard to track rollbacks on production environment. Even though we have last tag for revision 1701 we can be sure that production environment was not rolled back to version 1700 yesterday during night</span></li>
<li><span style="font-size: small;">It requires manual process to create tag or having it done by deployment script, what means we have to access SVN repository from production environment – not recommended and very often not possible.</span></li>
</ul>
<br />
<span style="font-size: small;"><b><span style="font-size: large;">4. Storing revision number in assembly</span></b><br />This solution assumes revision number is embedded in assembly. Very often we already have version number by AssemblyVersion attribute in AssemblyInfo.cs set to<br /> </span><br />
<span style="font-size: small;">[AssemblyVersion(1.7.2.0)]<br /> </span><br />
<span style="font-size: small;">Common practice is to update it manually: when big release is planned first or second number is upgraded; when minor fix is release only third or fourth number is changed- see semVer). <br /> </span><br />
<span style="font-size: small;">What I propose is to adapt this notation to follow:</span><br />
<span style="font-size: small;"></span><br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJIKu-5KTvFh1ig351lfE3OyqzzEaMUGgQpZTz409cBzRDj3_UxChth-nU0oiQzX3nf31aO3B2PBqBZDwWEWE4VvCwq_Aymfzc7d0RoVLdo3yxVMx3JRCWynFLheHsqWubX7GKSC1PeaM/s1600/Versioning.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="174" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJIKu-5KTvFh1ig351lfE3OyqzzEaMUGgQpZTz409cBzRDj3_UxChth-nU0oiQzX3nf31aO3B2PBqBZDwWEWE4VvCwq_Aymfzc7d0RoVLdo3yxVMx3JRCWynFLheHsqWubX7GKSC1PeaM/s320/Versioning.png" width="320" /></a></div>
<span style="font-size: small;">Crucial here is that it must be done automatically during build. Only then we can remove human factor and avoid updating this.<br /> </span><br />
<span style="font-size: small;">This approach has following advantages</span><br />
<ul>
<li><span style="font-size: small;">It’s easy to figure out on any environment what version is currently deployed</span></li>
<li><span style="font-size: small;">Even if you rollback or do hotfix current SVN revision is up to date</span></li>
<li><span style="font-size: small;">No room for human error, since it’s done automatically</span></li>
<li><span style="font-size: small;">Access to SVN is not needed during deployment</span></li>
<li><span style="font-size: small;">Revision number can be determined on each environment in same way (production, staging, test etc.)</span></li>
</ul>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiyxMbAG08o6ETzT3VITnWJlxY2HoOJMAV3iPUt5c2CcKoBEkRgFHnH3TgTxHztCmwhBrdR-xah5Aiv8b76MqGUbAEZHLHsBzELeWmRinC1y-EUpS4Xz6cPKH6YcC9Q8JY5Ts_HIf1wwCM/s1600/file_properties.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiyxMbAG08o6ETzT3VITnWJlxY2HoOJMAV3iPUt5c2CcKoBEkRgFHnH3TgTxHztCmwhBrdR-xah5Aiv8b76MqGUbAEZHLHsBzELeWmRinC1y-EUpS4Xz6cPKH6YcC9Q8JY5Ts_HIf1wwCM/s1600/file_properties.png" /></a></div>
<br />
<span style="font-size: small;"><b><span style="font-size: large;">Implementation</span></b><br />Implementation presented below assumes SVN is used but this approach can be used with virtually any version control system (as long as it has notion of revision/check-in/version number)<br /> </span><br />
<span style="font-size: small;">Msbuild script presented below:<br />- Verifies current revision from working copy<br />- Updates [AssemblyInfo(…)] attribute<br />- Builds project with revision number embedded in assembly</span><br />
<pre class="brush: xml"><Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" >
<UsingTask TaskName="SvnInfo" AssemblyFile="tools\build\MSBuild.Community.Tasks.dll"/>
<UsingTask TaskName="AssemblyInfo" AssemblyFile="tools\build\AssemblyInfoTask.dll"/>
<Target Name="setup-version" >
<SvnInfo LocalPath="." ToolPath="tools\svn">
<Output TaskParameter="LastChangedRevision" PropertyName="LastChangedRevision"/>
</SvnInfo>
<!-- it will number like 1789 -->
<Message Text="Last changed revision is: $(LastChangedRevision)"/>
<AssemblyInfo AssemblyInfoFiles="src\CommonAssemblyInfo.cs"
AssemblyRevision="$(LastChangedRevision)"
AssemblyFileRevision="$(LastChangedRevision)" >
<Output TaskParameter="MaxAssemblyVersion"
PropertyName="MaxAssemblyVersion"/>
</AssemblyInfo>
<Message Text="Version: $(MaxAssemblyVersion)"/>
<!-- it will be like 1.7.0.1789 -->
</Target>
</Project>
</pre>
<span style="font-size: small;">
<br /><b>Note</b>: I use MSBuildCommunityTasks to discover revision number and update [AssemblyVersion] attribute<br />Note: everything what is necessary to build project is stored in repository- in this case we need svninfo.exe so it’s also stored in repository beside source code in directory “tools\svn”<br /><br /><b><span style="font-size: large;">Summary</span></b><br />From four presented approaches how to control revision number deployed on specific environment I would recommend using the last one. Having revision number automatically embedded in assembly has several advantages: simplifies discovery, eliminates chance for human error, does not change current deployment process, etc.</span>Mirosław Jedynakhttp://www.blogger.com/profile/03407217246259905745noreply@blogger.com0tag:blogger.com,1999:blog-6817808781210919491.post-39919912297628561392011-03-04T11:05:00.005+01:002011-03-04T12:08:47.753+01:00Mobile URL Rewriter<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjuNsdbOaKYDO5s8FRyBRMtmFt2xpfHJMUdp6QpIMrb5eN5h54YzA_jPC0BAaH7Z_A5SMX4dAVLPnyQBOTeeLJKTJuktxwzuwhAgTxGDKu45b7hGdcO6RfSNr4JKpVqIo503R1A1EIEA-8/s1600/template_matching.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"></a></div>Recently <a href="http://makingwaves.no/">Making Waves</a> has published <a href="https://www.coderesort.com/p/epicode/wiki/MakingWaves.MobileUrlRewriter">Mobile Url Rewriting</a> module on EPiCode. It’s friendly Url engine, which translates incoming Url, so it points to specific template based on configured rules. Most useful scenario is providing device-specific versions (e.g. for mobile devices) using different templates while retrieving data from a single data source (Page Data). <br />
<br />
This flexible solution is editor-friendly because only one form for each page must be filled and it becomes a source for different page templates.<br />
<br />
For latest documentation, source code and binaries visit <a href="https://www.coderesort.com/p/epicode/wiki/MakingWaves.MobileUrlRewriter">module's home page page</a>.<br />
<br />
<br />
<b><span style="font-size: large;">How it works</span></b><br />
<br />
To illustrate how it works, consider differences between using default friendly Url provider and Making Waves’ rule based Url provider <br />
<br />
For default friendly url provider <br />
<ul><li>/Articles/Website-Launch url is translated to /Templates/Articles/NewsTemplate.aspx?id=1234 </li>
<li>/Articles/Website-Launch/Mobile returns 404 error </li>
</ul><br />
For rule based url rewrite provider with configured mobile rule (as suffix rule) <br />
<ul><li>/Articles/Website-Launch is translated to /Templates/Articles/News.aspx?id=1234 </li>
<li>/Articles/Website-Launch/<b>Mobile </b>is translated to /<b>Mobile</b>/Templates/Articles/News.aspx?id=123&Rule=”Mobile” </li>
<li>/Articles/Website-Launch/<b>Lite </b>is translated to /<b>Lite</b>/Templates/Articles/News.aspx?id=123&Rule=”Lite” (e.g. Lite/Full website version like MSDN) </li>
</ul><br />
Additionaly further template selection can be made based on User Agent (sent by browser), so for example IPhone users can be served a different version then other mobile devices, which may have low resolution displays. <br />
<br />
Note: Url suffix (“Mobile”) and path for templates (“/Mobile/Templates/”) are examples and can be configured in web.config <br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiY2x7yGZH1VeD1xAYOK47N8eujAnRQ2ZhU94Vgn_a0dlq_-MOJKRq73lgJDLfdYKwzASiNRLAXz3ApaQFdO9VmXw0tYt9R77qKPX3mpunSOvUKJ9DgqbtEFA0Dghytbr0r_87iNhzuL-Y/s1600/template_matching.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiY2x7yGZH1VeD1xAYOK47N8eujAnRQ2ZhU94Vgn_a0dlq_-MOJKRq73lgJDLfdYKwzASiNRLAXz3ApaQFdO9VmXw0tYt9R77qKPX3mpunSOvUKJ9DgqbtEFA0Dghytbr0r_87iNhzuL-Y/s1600/template_matching.png" /></a></div><br />
<br />
<br />
You can check how it works in production at Making Waves’ website. Additionaly if you visit this website from mobile device you will be automatically redirected to mobile version. <br />
<ul><li> <a href="http://makingwaves.no/">http://makingwaves.no</a> – regular website version </li>
<li><a href="http://www.blogger.com/%20http://m.makingwaves.no"> http://m.makingwaves.no</a> - mobile version using same page data as source for a template </li>
</ul><br />
<b><span style="font-size: large;">Rule selection</span></b><br />
<br />
When client tries to access any Url rule based rewriter, selects rule which will perform further processing. All rules are probed for matching and first, which accepts url, is responsible for template selection. <br />
<br />
In an example below, third rule matches url and then selects the fourth template. After page is rendered, the rule is also responsible for transforming non-friendly urls into corresponding friendly urls for this rule from page markup. <br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBLy5L00sDjqo4QvfINuRrfxqKT4wWjtB_yJXpIU51_JH03ZJp2Lq1lENmEk37ie7NN0C8Oo9QOwfn1bklX-xYUUqJmn9gS2JaxCcuResoVolH76I8jtZ1P8May2Rj6gG-ogpitwrLl9g/s1600/rule_selection.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBLy5L00sDjqo4QvfINuRrfxqKT4wWjtB_yJXpIU51_JH03ZJp2Lq1lENmEk37ie7NN0C8Oo9QOwfn1bklX-xYUUqJmn9gS2JaxCcuResoVolH76I8jtZ1P8May2Rj6gG-ogpitwrLl9g/s1600/rule_selection.png" /></a></div><div class="separator" style="clear: both; text-align: center;"></div><br />
<br />
<br />
<span style="font-size: large;"><b>Features</b></span><br />
<ul><li>Configurable in web.config and programmatically </li>
<li>Extensible rule selection engine </li>
<li>Translates urls in rendered page using rule choosen to select template </li>
</ul><br />
<span style="font-size: large;"><b>Supported rules</b></span><br />
<ul><li><a href="http://agentbasedsubdomainmatchingurlrewriterule/">AgentBasedSubDomainMatchingUrlRewriteRule</a> - Uses sub domain to match rule. </li>
<li><a href="https://www.coderesort.com/p/epicode/wiki/MakingWaves.MobileUrlRewriter/SuffixMatchingUrlRewriteRule">SuffixMatchingUrlRewriteRule</a> - Uses suffix to match rule. </li>
<li><a href="https://www.coderesort.com/p/epicode/wiki/MakingWaves.MobileUrlRewriter/AgentBasedSuffixMatchingUrlRewriteRule">AgentBasedSuffixMatchingUrlRewriteRule</a> - Works like suffix rule but additionaly template selection can be performed based on User Agent sent by browser. </li>
<li><a href="http://prefixmatchingurlrewriterule/">PrefixMatchingUrlRewriteRule</a> - Uses prefix to match rule.</li>
</ul><br />
<span style="font-size: large;"><b>Download</b></span><br />
Visit <a href="https://www.coderesort.com/p/epicode/wiki/MakingWaves.MobileUrlRewriter#Download">dowload section</a> at Module's <a href="https://www.coderesort.com/p/epicode/wiki/MakingWaves.MobileUrlRewriter">page</a><br />
<br />
<br />
<span style="font-size: large;"><b>Sample Configuration</b></span><br />
<br />
In web.config <br />
<pre class="brush: xml; gutter: false"> <configSections>
<section name="makingWaves.urlRewrite" type="MakingWaves.Common.EPiServer.UrlRewriting.Configuration.UrlRewriteSection, MakingWaves.Common.EPiServer.UrlRewriting"/>
...
</configSections>
<makingWaves.urlRewrite>
<rules>
<add name="MobileRule"
type="MakingWaves.Common.EPiServer.UrlRewriting.AgentBasedSuffixMatchingUrlRewriteRule, MakingWaves.Common.EPiServer.UrlRewriting"
rootPath="MobilePath/Regular"
urlSuffix="Mobile">
<ruleProperties>
<add propertyType="agent" agentPattern=".*Android.*" pathSuffix="MobilePath/Android"/>
<add propertyType="agent" agentPattern=".*IPhone.*" pathSuffix="MobilePath/IPhone"/>
</ruleProperties>
</add>
</rules>
</makingWaves.urlRewrite>
</pre><br />
<br />
In episerver.config <br />
<pre class="brush: xml; gutter: false"><episerver>
<urlRewrite
defaultProvider="RuleBasedUrlRewriteProvider">
<providers>
<add name="RuleBasedUrlRewriteProvider"
description="My provider supporting multiple templates"
type="MakingWaves.Common.EPiServer.UrlRewriting.RuleBasedUrlRewriteProvider,MakingWaves.Common.EPiServer.UrlRewriting"/>
...
</providers>
</urlRewrite>
</episerver></pre><br />
<br />
<br />
Additionally, if you want a mobile client to be redirected to a proper version of a page consider using the following snippet using extension method GetLinkUrlByRule: <br />
<pre class="brush: xml; gutter: false">if (HttpContext.Current.Request.Browser.IsMobileDevice)
{
this.Response.Redirect(CurrentPage.GetLinkUrlByRule("MobileRule"));
}</pre><br />
<b><span style="font-size: large;">Contributed by</span></b><br />
<br />
<ul><li>Andrzej Zapotoczny (andrzej.zapotoczny _mail_at_ makingwaves.pl)</li>
<li>Mirosław Jedynak (miroslaw.jedynak _mail_at_ makingwaves.pl)</li>
<li>Krzysztof Danielewisz (krzysztof.danielewicz_mail_at_makingwaves.pl) </li>
</ul>from Making Waves ( <a href="http://www.makingwaves.com/">http://www.makingwaves.com</a>)Mirosław Jedynakhttp://www.blogger.com/profile/03407217246259905745noreply@blogger.com0tag:blogger.com,1999:blog-6817808781210919491.post-84383084486809958912011-02-21T22:24:00.008+01:002013-10-22T10:03:10.362+02:00Writing custom properties: #3 Edit controlIn this post I will present how to create initial version of edit control for Guid property, which was introduced in previous posts. <br />
<ul><li><a href="http://blog.m.jedynak.pl/2011/02/writing-custom-properties-1-custom.html">Writing custom properties: #1 Custom value type</a></li>
<li><a href="http://blog.m.jedynak.pl/2011/02/writing-custom-properties-2-default.html">Writing custom properties: #2 Default value</a></li>
<li>Writing custom properties: #3 Edit control</li>
</ul><br />
<b><span style="font-size: large;">Features</span></b> <br />
Created control will have:<br />
<ul><li>Text input for Guid </li>
<li>Button for creating new Guid</li>
</ul>This post is first step in more complex implementation which will come with AJAX functionality and On-Page-Edit support.<br />
<br />
<b><span style="font-size: large;">Controls render types</span></b><br />
EPiServer properties can be rendered in one of the following modes (defined by <span style="font-family: "Courier New", "Courier", monospace;">EPiServer.Core.RenderType</span>): <br />
<ul><li><b>Default</b> – when viewing property in View Mode. It’s used e.g. when you display property using <span style="font-family: "Courier New", "Courier", monospace;"><episerver:property propertyname="”..”/"></episerver:property></span><span style="font-family: "Courier New", "Courier", monospace;"><episerver:property PropertyName=”..”/></span> . In this mode <span style="font-family: "Courier New", "Courier", monospace;">CreateDefaultControls</span> method is invoked.</li>
<li><b>Edit </b>– when editing page in Edit Mode or in Quick Edit Mode. In this mode <span style="font-family: "Courier New", "Courier", monospace;">CreateEditControls</span> method is invoked.</li>
<li><b>OnPageEdit</b> – if <span style="font-family: "Courier New", "Courier", monospace;">PropertyDataControl.SupportsOnPageEdit</span> returns true and <span style="font-family: "Courier New", "Courier", monospace;"><episerver:property PropertyName=”..”/> </span>is used property is rendered in this mode. Otherwise uses default view. These mode will be covered in more details soon.</li>
</ul><br />
<b><span style="font-size: large;">Basic server control implementation</span></b><br />
All controls for editing custom properties in EPiServer must inherit from <b style="font-family: "Courier New",Courier,monospace;">PropertyDataControl </b>or derived class.<br />
<br />
In simplest scenario when supporting only edit mode functionally <b style="font-family: "Courier New",Courier,monospace;">CreateEditControls </b>must create controls which are used in edit mode. Edited values should be saved in <b style="font-family: "Courier New",Courier,monospace;">ApplyEditChanges </b>method.<br />
Additionaly <b style="font-family: "Courier New",Courier,monospace;">SetupEditControls </b>updates visual representation value based on data from <span style="font-family: "Courier New", "Courier", monospace;">PropertyData </span><br />
<pre class="brush:csharp; gutter:false">public class GuidPropertyControl : PropertyDataControl
{
protected TextBox EditBox { get; set; }
public GuidProperty GuidProperty
{
get { return (GuidProperty)this.PropertyData; }
}
public override void CreateEditControls()
{
//create text box
this.EditBox = new TextBox { MaxLength = 0xff };
this.ApplyControlAttributes(this.EditBox);
this.Controls.Add(this.EditBox);
//button for generating new guid
//consider using LanguageManager for texts
var button = new Button { Text = "New" };
this.ApplyControlAttributes(button);
button.Click +=
(sender, e) => { this.EditBox.Text = FormatGuid(Guid.NewGuid()); };
this.Controls.Add(button);
this.SetupEditControls();
}
private static string FormatGuid(Guid guid)
{
return guid.ToString();
}
//Saves value in underlying PropertyData
public override void ApplyEditChanges()
{
Guid value;
if (string.IsNullOrEmpty(this.EditBox.Text))
{
value = Guid.Empty;
}
else
{
try
{
value = new Guid(this.EditBox.Text);
}
catch (FormatException e)
{
this.AddErrorValidator(e.Message);
return;
}
}
this.SetValue(value);
}
protected override void SetupEditControls()
{
this.EditBox.Text = FormatGuid(this.GuidProperty.Guid);
} </pre><pre class="brush:csharp; gutter:false"> //representation in View Mode
public override void CreateDefaultControls()
{
Label target = new Label();
target.Text = FormatGuid(GuidProperty.Guid);
this.CopyWebAttributes(target);
this.Controls.Add(target);
}
}</pre><br />
<br />
<b><span style="font-size: large;">Post back support</span></b><br />
If you would try to click “New button” in edit mode you would notice strange behavior: Message box warning about leaving page and loosing unsaved value.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh4d7Wuvpgh444DENr34ii_WJcJaa95GXv3dlg3op87ulqbXaVY1TfvO73_r7uvdjnmDWV2lxqiCVA_MIzouecjrs3ii5W2_sJV_Vc1bWD2pSHVeffF_PvfjHhQ5fuCTDnbr9wG838_zM4/s1600/page_leave_check.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh4d7Wuvpgh444DENr34ii_WJcJaa95GXv3dlg3op87ulqbXaVY1TfvO73_r7uvdjnmDWV2lxqiCVA_MIzouecjrs3ii5W2_sJV_Vc1bWD2pSHVeffF_PvfjHhQ5fuCTDnbr9wG838_zM4/s1600/page_leave_check.png" /></a></div><br />
<br />
The reason for this is that Javascipt <span style="font-family: "Courier New", "Courier", monospace;">OnBeforeUnload </span>event is raised and EPiServer warns you that pages is going to be left with possible data loss.<br />
There are two solutions for this:<br />
<ul><li>Use <b style="font-family: "Courier New",Courier,monospace;">ToolButton </b>from EPiServer namespace instead of regular ASP <span style="font-family: "Courier New", "Courier", monospace;">Button </span>and set <b style="font-family: "Courier New",Courier,monospace;">DisablePageLeaveCheck </b>to <span style="font-family: "Courier New", "Courier", monospace;">true</span></li>
</ul><br />
<pre class="brush: csharp; gutter:false">public override void CreateEditControls()
{
//same code creating TextBox
var button = new ToolButton
{
Text = "New",
DisablePageLeaveCheck = true
};
//same code using button
SetupEditControls()
}</pre><br />
<ul><li>Use <b style="font-family: "Courier New",Courier,monospace;">ScriptDisablePageLeaveEvent </b>to disable checking specific event raised from our control. This approach is more flexible because not only button is supported and it’s possible to specify which event should suppress checking.</li>
</ul><br />
<pre class="brush: csharp; gutter:false">public override void CreateEditControls()
{
//same code creating TextBox
//same code creating Button
ScriptDisablePageLeaveEvent scriptDisablePageLeaveEvent =
new ScriptDisablePageLeaveEvent
{
EventTarget = button,
EventType = EventType.Click
};
this.Controls.Add(scriptDisablePageLeaveEvent);
SetupEditControls()
}</pre><br />
<b><span style="font-size: large;">Page leave check considerations</span></b><br />
You may wonder how it happed that without writing any code EPiServer can discover that value of Guid property has changed and warn about it. In fact EPiServer PageLeaveCheck attach to each html’s input event onchange and whenever this event is raise IsPageChanged is set to true. No magic!<br />
<br />
This approach is working really well in most scenarios, since it’s common to store value in text boxes (sometimes in read-only mode like for Page Reference property). However, if you go into scenario where values are stored only in hidden field and displayed to user directly (without textbox) you will notice that Page Leave Check does not work anymore.<br />
<br />
In that case you may manually mark page as changed using following Javascript<br />
<br />
<pre class="brush: javascript; gutter:false">savePropetyValue: function (value) {
//change hidden field value
$("#hidden_field").val("...") ;
//mark page as changed
if (EPi.PageLeaveCheck.enabled) {
EPi.PageLeaveCheck.SetPageChanged(true);
}
};</pre><br />
Please be patient: more advanced Javascript usage in custom controls will be covered in future posts. It will include using JQuery, packaging javacripts, AJAX support, etc.Mirosław Jedynakhttp://www.blogger.com/profile/03407217246259905745noreply@blogger.com0tag:blogger.com,1999:blog-6817808781210919491.post-40217060533407758122011-02-13T22:49:00.003+01:002013-10-22T10:03:06.382+02:00Writing custom properties: #2 Default valueIn previous post I presented how to create simple property returning Guid values. In this post I will briefly describe how to deal with default values for custom properties. <br />
<ol><li><a href="http://blog.m.jedynak.pl/2011/02/writing-custom-properties-1-custom.html">Writing custom properties: #1 Custom value type</a></li>
<li>Writing custom properties: #2 Default value </li>
<li><a href="http://blog.m.jedynak.pl/2011/02/writing-custom-properties-3-edit.html">Writing custom properties: #3 Edit control</a> </li>
</ol><br />
<b><span style="font-size: large;">Admin UI based approach</span></b><br />
The easiest way is to use admin UI and set default values for properties using three available modes: <br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0_s59iu28PKS7mb0fYRw_yMEO9CdkWsRzeDo7MHHegXUs9Hi1ZLBUdQa__O0I4GMXUWzSeQ7J06seHIAuOnOluTzl7DvhO9BbZ1ZinwSS1Pjo407DtEGb8CJD0vHdVAy8r-iuio_Od54/s1600/property_default_value.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0_s59iu28PKS7mb0fYRw_yMEO9CdkWsRzeDo7MHHegXUs9Hi1ZLBUdQa__O0I4GMXUWzSeQ7J06seHIAuOnOluTzl7DvhO9BbZ1ZinwSS1Pjo407DtEGb8CJD0vHdVAy8r-iuio_Od54/s1600/property_default_value.png" /></a><br />
<br />
Unfortunately this approach has several drawbacks:<br />
<ul><li>Error-prone: <i>It’s possible that for some reason administrator will not set this value. Application logic may not be prepared for this case.</i></li>
<li>Default value is applied only when creating page: <i> If you add new property to page type, value will not be updated for empty properties.</i></li>
<li>Editor can remove default value<i> so property will fall back to empty/null value</i></li>
<li>String representation:<i> It may be not easy/possible to specify string representation</i></li>
</ul><br />
In my opinion this value should be used as suggestion for editor, not as way to enforce data correctness.<br />
<br />
<b><span style="font-size: large;">Ensuring default value in custom property</span></b><br />
Second approach for defining default value is setting it inside custom property.<br />
<br />
There is method <b style="font-family: "Courier New",Courier,monospace;">PropertyData.SetDefaultValue()</b>, which is called when property is created (even if is null) or when cleared.<br />
<br />
Together with <span style="font-family: "Courier New",Courier,monospace;">SetDefaultValue</span>, you will usually need implement <b style="font-family: "Courier New",Courier,monospace;">IsNull </b>property returning value indicating if current property value is equal to it's default.<br />
<br />
Sample implementation for GuidPropety may be following:<br />
<pre class="brush:csharp; gutter: false">[PageDefinitionTypePlugIn]
public class GuidProperty : PropertyString
{
// for rest of code check previous post
//.....
protected override void SetDefaultValue()
{
base.SetDefaultValue();
this.value = Guid.Empty;
//or set custom default value – default product code in store etc.
//this.value = new Guid(“12345678-1234-1234-1234-123456789012”);
}
public override bool IsNull
{
get
{
return this.value == Guid.Empty;
//return this.value == new Guid(“12345678-1234-1234-1234-123456789012”);
}
}
}
</pre><br />
<br />
Some readers may ask why setting guid field to Guid.Empty is necessary, when Sytem.Guid is value type and it’s default value is Guid.Empty. Answer is that SetDefaultValue is called when property is cleared with PropertyData.Clear() so reseting to default value is necessary.<br />
<br />
<b><span style="font-size: large;">Usage</span></b><br />
For newly created page or property added to exiting page<br />
<br />
<pre class="brush:csharp; gutter: false">var value = CurrentPage["Guid"]; // “00000000-0000-0000-0000-000000000000”
// or e.g “12345678-1234-1234-1234-123456789012”
var isNull = CurrentPage.Property["Guid"].IsNull; // returns true
</pre><br />
<br />
<b><span style="font-size: large;">Collection based custom properties </span></b><br />
For collection types Microsoft design guidelines state:<br />
<blockquote><i>Array (and collection) properties should never return a null reference. Null can be difficult to understand in this context.</i></blockquote><a href="http://msdn.microsoft.com/en-us/library/k2604h5s.aspx">http://msdn.microsoft.com/en-us/library/k2604h5s.aspx</a><br />
<br />
To follow this guideline using SetDefaultValue is invaluable. Having custom property GuidCollectionProperty it can be implemented like below:<br />
<br />
<pre class="brush:csharp; gutter: false">[PageDefinitionTypePlugIn]
public class GuidCollectionProperty : PropertyString
{
// for rest of code check previous post
//.....
protected override void SetDefaultValue()
{
base.SetDefaultValue();
this.value = new List<Guid>();
}
public override bool IsNull
{
get
{
return this.value == null || this.value.Count == 0;
}
}
}
</pre><br />
<b><span style="font-size: large;">Note: Admin default value override </span></b><br />
It’s still possible to set default value using admin UI to override default value set by property creator. Good thing is that default value format will be verified, so it is not allowed to set “abc” as default value for GuidProperty.<br />
<br />
<span style="font-size: large;"><b>Note: Virtual call in constructor</b></span><br />
Current implementation of PropertyData base class calls virtual method SetDefaultValue() in constructor. This is discouraged because calling virtual method in base class can cause accessing uninitialized values in derived class, since derived constructor would not be invoked by that time.<br />
<blockquote><i>Do not call virtual members from constructors. Calling virtual methods during construction will cause the most derived override to be called, even though the most derived constructor has not been fully run yet</i></blockquote><a href="http://msdn.microsoft.com/en-us/library/ms182331%28v=vs.80%29.aspx">http://msdn.microsoft.com/en-us/library/ms182331(v=vs.80).aspx</a><br />
<a href="http://blogs.msdn.com/b/brada/archive/2004/08/12/213951.aspx">http://blogs.msdn.com/b/brada/archive/2004/08/12/213951.aspx</a><br />
<br />
What does it mean for us? In order to preserve backward compatibility this behavior, I guess, won’t be fixed soon. Therefore we should be aware what we use in SetDafaultValue() and check if it has been initialized in constructor before accessing value.Mirosław Jedynakhttp://www.blogger.com/profile/03407217246259905745noreply@blogger.com0tag:blogger.com,1999:blog-6817808781210919491.post-38903173627099036822011-02-07T10:02:00.003+01:002013-10-22T10:03:00.540+02:00Writing custom properties: #1 Custom value typeThis is first post from series showing how to implement custom property in EPiServer in "proper" way without unnecessary hacking.<br />
<ol><li>Writing custom properties: #1 Custom value type</li>
<li><a href="http://blog.m.jedynak.pl/2011/02/writing-custom-properties-2-default.html">Writing custom properties: #2 Default value</a></li>
<li><a href="http://blog.m.jedynak.pl/2011/02/writing-custom-properties-3-edit.html">Writing custom properties: #3 Edit control</a> </li>
</ol><br />
<b><span style="font-size: large;">Introduction</span></b><br />
In EPiServer all custom properties must inherit from abstract base class <b><span style="font-family: "Courier New","Courier",monospace;">PropertyData</span></b>. It’s value can be stored using one of following underlying types:<br />
• Boolean<br />
• Category<br />
• Date<br />
• FloatNumber<br />
• LinkCollection <br />
• LongString<br />
• Number<br />
• PageReference <br />
• PageType<br />
• String<br />
<br />
Those types are defined in enumeration <b><span style="font-family: "Courier New","Courier",monospace;">EPiServer.Core.PropertyDataType</span></b> and when creating custom property you have to decide which storage type is closest to your requirements. From my experience you will usually use number or variations of string storage. The easiest way to start is to use one of default property type defined in EPiServer as your base class. <br />
<br />
<b><span style="font-size: large;">Guid Property</span></b><br />
In this tutorial series I will show how to create complete custom property used for storing and editing Guid value. Using Guid as example will be easy to understand but still possible to show many features available for custom properties.<br />
<br />
As usage example you may consider integrating with external system where product identifiers are stored as Guids and editor is responsible for providing them to our CMS. <br />
<br />
<b><span style="font-size: large;">Analysis</span></b><br />
<br />
Guid are easily stored in string using different formats (the most popular is “<span style="font-family: "Courier New","Courier",monospace;">{96C7DFDF-668C-4A71-AA79-1E8898B1A0E4}</span>”), therefore as base type I will use PropertyString – originally used for storing up to 255 characters.<br />
<br />
If we would leave property unchanged, editor could easily mistype value, which may cause system failure when application will be parsing invalid string. Creating custom property can prevent human error.<br />
<br />
It’s also crucial to return proper object type, so developer can avoid parsing and, what is even worse, invalid format error handling in every place value is used.<br />
<br />
<pre class="brush:csharp; gutter:false">//bad – requires parsing and error handling
Guid value=new Guid(CurrentPage[“GuidProperty”].Value);
//good – returned value is System.Guid
Guid value= (Guid) CurrentPage[“GuidProperty”].Value; </pre><br />
<b><span style="font-size: large;"> </span></b><b><span style="font-size: large;">Implementation</span></b><br />
<br />
<pre class="brush:csharp; gutter:false">[PageDefinitionTypePlugIn]
public class GuidProperty : PropertyString
{
private Guid value;
public override Type PropertyValueType
{
get { return typeof(Guid); }
}
public override object Value
{
get { return this.value; }
set
{
this.SetPropertyValue(value, delegate
{
//currenty we don't have custom editor control for guid,
//therefore if standard string control value sets value from editor
//we try to parse it. Throwing exception in this place will show nice
//message that provided value "is not valid value for property
//'YourPropertyName'"
this.value = value is string? new Guid((string)value) : (Guid)value;
if (Equals(value, Guid.Empty))
this.Clear();
else
this.Modified();
});
}
}
public override void LoadData(object newValue)
{
this.Value = this.DeserializeValue(newValue as string);
}
public override object SaveData(PropertyDataCollection properties)
{
return this.SerializeValue(this.value);
}
protected string SerializeValue(Guid value)
{
return value.ToString();
}
protected Guid DeserializeValue(string value)
{
if (string.IsNullOrEmpty(value))
{
return Guid.Empty;
}
return new Guid(value);
}
}
</pre><br />
<span style="font-size: large;"><b>Usage</b></span><br />
<br />
<pre class="brush:csharp; gutter:false">var value = CurrentPage["Guid"]; // e.g. c75dfcdb-258c-47a0-a2ea-81f0b92341e4
var type = CurrentPage["Guid"].GetType(); //System.Guid (not System.String!)</pre>Mirosław Jedynakhttp://www.blogger.com/profile/03407217246259905745noreply@blogger.com1tag:blogger.com,1999:blog-6817808781210919491.post-56789264227861422162011-02-04T21:31:00.008+01:002011-02-06T09:16:06.860+01:00Dynamic splash screen in WPFIn WPF 3.5 SP1 new splash screen functionality was introduced. It’s now possible to add static image which is shown before main window is loaded. <br />
<br />
Read more on MSDN: <a href="http://msdn.microsoft.com/en-us/library/cc656886.aspx%20">http://msdn.microsoft.com/en-us/library/cc656886.aspx </a><br />
<br />
Unfortunately current implementation has some limitations. Since only static images are supported (BMP, GIF, JPEG, PNG, or TIFF format): <br />
<ul><li>It’s not possible to use XAML to define splash screen design </li>
<li>It’s not possible to display dynamic informations like: </li>
<ul><li>List of available plugins </li>
<li>Licensee information </li>
<li>Plugin being currently loaded. </li>
</ul></ul><br />
<b><span style="font-size: large;">Solution overview</span></b><br />
Solution for those issues is:<br />
<ol><li>Show built-in WPF splash screen </li>
<li>Load regular WPF window (dynamic splash screen) which is pixel-perfect same as previously displayed splash screen but displays also dynamic information</li>
<li>When all plugins and main window is loaded hide dynamic splash screen and show main window.</li>
</ol><div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCCbLZ9l8qAoArzlqHNconpc9HGH_CG4ErHjGu2Fdcekj92_PL962ujhJzuwygSv7xk7vcdlal26PvtvUnSl_KVImBhFxmzJMc3oZNePsQEInJNl-Lb0_VpR79gwgk6xo83TqcaTWVnLc/s1600/dynamic_splash_screen_overview.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCCbLZ9l8qAoArzlqHNconpc9HGH_CG4ErHjGu2Fdcekj92_PL962ujhJzuwygSv7xk7vcdlal26PvtvUnSl_KVImBhFxmzJMc3oZNePsQEInJNl-Lb0_VpR79gwgk6xo83TqcaTWVnLc/s1600/dynamic_splash_screen_overview.png" /></a></div><div class="separator" style="clear: both; text-align: center;"></div><br />
<br />
<b><span style="font-size: large;">Implementation : DynamicSplashScreen class</span></b><br />
DynamicSplashScreen is reusable class which can be used as base class for your custom splash screen. Check inline comments for details.<br />
<br />
<pre class="brush: csharp; gutter:false">public class DynamicSplashScreen:Window
{
public DynamicSplashScreen()
{
//Defaults for splash screen
this.ShowInTaskbar = false;
this.WindowStartupLocation = WindowStartupLocation.Manual;
this.ResizeMode = ResizeMode.NoResize;
this.WindowStyle = WindowStyle.None;
this.Topmost = true;
this.Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
//calculate it manually since CenterScreen substracts
//taskbar's height from available area
this.Left = (SystemParameters.PrimaryScreenWidth - this.Width) / 2;
this.Top = (SystemParameters.PrimaryScreenHeight - this.Height) / 2;
}
}
</pre><br />
<b><span style="font-size: large;">Creating pixel-perfect static splash screen image</span></b><br />
Creating pixel-perfect static image can be error prone, maintenance hell if you start using external applications like Photoshop – each changed in dynamic version would have to be reflected in static image manually.<br />
<br />
DynamicSplashScreen has another feature: capturing static splash screen image. It used build-in WPF functionality to get Control’s “screen shot” dynamically. <br />
<br />
You can use this method to generate image and add it to Visual Studio project with build action set to SplashScreen. This still requires some manual work, but it’s not possible (and reasonably) to generate splash screen at runtime.<br />
<pre class="brush: csharp; gutter:false">public class DynamicSplashScreen:Window
{
//.... see implementation above ..
public void Capture(string filePath)
{
this.Capture(filePath, new PngBitmapEncoder());
}
public void Capture(string filePath, BitmapEncoder encoder)
{
RenderTargetBitmap bmp = new RenderTargetBitmap(
(int)this.Width, (int)this.Height,
96, 96, PixelFormats.Pbgra32);
bmp.Render(this);
encoder.Frames.Add(BitmapFrame.Create(bmp));
using (Stream stm = File.Create(filePath))
{
encoder.Save(stm);
}
}
}
</pre><br />
<b><span style="font-size: large;">Usage</span></b><br />
<pre class="brush: csharp; gutter:false">public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
// MyDynamicSplashScreen derives from DynamicSplashScreen and
// defines layout and custom properties (like Licensee, Plugins, Message)
MyDynamicSplashScreen splashScreen = new MyDynamicSplashScreen();
splashScreen.Show();
//set basic dynamic data on splash screen
splashScreen.AvailablePlugins = new[] {"Plugin 1", "Plugin 2"};
splashScreen.Licensee = "Mirosław Jedynak";
//use during development to generate image and embed it in application
//splashScreen.Capture(@"c:\StaticSplashScreen.png");
var startupTask=new Task(() =>
{
//Load plugins in non-UI thread - may be time consuming
for (int i = 0; i < 3; i++)
{
//set custom message on screen
splashScreen.Dispatcher.BeginInvoke(
(Action) (()=>splashScreen.Message = "Loading: Plugin " + i));
Thread.Sleep(100);
}
});
//when plugin loading finished, show main window
startupTask.ContinueWith(t =>
{
MainWindow mainWindow = new MainWindow();
//when main windows is loaded close splash screen
mainWindow.Loaded += (sender, args) => splashScreen.Close();
//set application main window;
this.MainWindow = mainWindow;
//and finally show it
mainWindow.Show();
}, TaskScheduler.FromCurrentSynchronizationContext());
startupTask.Start();
}
}</pre><br />
<br />
<b><span style="font-size: large;">Sample Dynamic splash screen</span></b><br />
<pre class="brush: xml; gutter:false"><Startup:DynamicSplashScreen x:Class="DynamicSplashScreenDemo.Startup.SplashScreen"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Startup="clr-namespace:DynamicSplashScreenDemo.Startup"
Title="My application" x:Name="spashScreen"
Width="500" Height="300">
<Border BorderThickness="1" BorderBrush="Black">
<Grid >
<Rectangle>
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0,0" StartPoint="0.5,1.3">
<GradientStop Color="#FF07254E" Offset="1"/>
<GradientStop Color="White" Offset="0.369"/>
<GradientStop Color="White"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<TextBlock FontFamily="Verdana" FontWeight="Bold" FontSize="50"
VerticalAlignment="Top" HorizontalAlignment="Center"
TextAlignment="Center" Margin="0,40,0,0" >
<Run Text="Dynamic" Foreground="#FF006AB3" />
<LineBreak/>
<Run Text="splash screen" Foreground="#FF006AB3"/>
</TextBlock>
<TextBlock Text="{Binding ElementName=spashScreen, Path=Message}"
VerticalAlignment="Bottom" Margin="10"/>
<StackPanel VerticalAlignment="Bottom" HorizontalAlignment="Right"
Margin="10">
<ItemsControl Margin="0,0,0,10" HorizontalAlignment="Right"
ItemsSource="{Binding ElementName=spashScreen, Path=AvailablePlugins}"/>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Licensed to: "/>
<TextBlock Text="{Binding ElementName=spashScreen, Path=Licensee}"/>
</StackPanel>
</StackPanel>
</Grid>
</Border>
</Startup:DynamicSplashScreen>
</pre><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBoErYHlFhrS4oQCpjocfyyIzHXPbHBtJaV16L64No5kjnpbAxw5bjtnLq8BhD71KPhX60jVaIRRnKhnUOICOdlZVKWHxwjdzMA04JAFo2b3tAI0EDkPVFNbBLZPKQUahCnVJzzzyo8zk/s1600/dynamic_splash_screen.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="192" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBoErYHlFhrS4oQCpjocfyyIzHXPbHBtJaV16L64No5kjnpbAxw5bjtnLq8BhD71KPhX60jVaIRRnKhnUOICOdlZVKWHxwjdzMA04JAFo2b3tAI0EDkPVFNbBLZPKQUahCnVJzzzyo8zk/s320/dynamic_splash_screen.png" width="320" /></a></div><br />
Source: <a href="http://m.jedynak.pl/blog/posts/DynamicSplashScreenDemo_source.zip">DynamicSplashScreenDemo_source.zip</a><br />
<br />
<a href="http://dotnetshoutout.com/Dynamic-splash-screen-in-WPF" rev="vote-for"><img alt="Shout it" src="http://dotnetshoutout.com/image.axd?url=http%3A%2F%2Fblog.m.jedynak.pl%2F2011%2F02%2Fdynamic-splash-screen-in-wpf.html" style="border: 0px none;" /></a><br />
<a href="http://www.dotnetkicks.com/kick/?url=http%3a%2f%2fblog.m.jedynak.pl%2f2011%2f02%2fdynamic-splash-screen-in-wpf.html"><img alt="kick it on DotNetKicks.com" border="0" src="http://www.dotnetkicks.com/Services/Images/KickItImageGenerator.ashx?url=http%3a%2f%2fblog.m.jedynak.pl%2f2011%2f02%2fdynamic-splash-screen-in-wpf.html" /></a>Mirosław Jedynakhttp://www.blogger.com/profile/03407217246259905745noreply@blogger.com7tag:blogger.com,1999:blog-6817808781210919491.post-78171070514569603772009-02-03T17:52:00.021+01:002011-02-03T14:57:51.647+01:00Static typed property namesString names of properties is common problem in dynamically developed software.<br />
<br />
<span style="font-size: 130%; font-weight: bold;">Common usages of property names literals</span><br />
Common usages of such literal are:<br />
<ul><li><b>Property names in Nhibernate queries</b><br />
Suppose you want to query all user for specific value of property “LastProjectCode” with detached criteria class:<br />
<pre "="" class="brush: csharp; gutter: false;">DetachedCriteria query = DetachedCriteria.For(typeof(User))
.Add(Expression.Eq("LastProjectCode", 12345));
</pre></li>
</ul><ul><li><b>Implementing INotifyPropertyChanged interface</b><br />
Standard implementation unfortunately have to pass property name as literal when event is raised. It looks like below:<br />
<br />
<pre "="" class="brush: csharp;gutter: false;">public int LastProjectCode
{
get { return this.lastProjectCode; }
set{
if (this.lastProjectCode != value)
{
this.lastProjectCode = value;
OnPropertyChanged(
new PropertyChangedEventArgs("LastProjectCode"));
}
}
}
</pre></li>
</ul><ul><li><b>Providing error information with IDataErrorInfo</b><br />
Component using entity (like user) queries it for error by calling property <i>this[string columnName]</i>. Implementation is usually based on many if condition checking for column name like below:<br />
<pre "="" class="brush: csharp; gutter: false;">public string this[string columnName]
{
get{
string result = null;
if (columnName == "LastProjectCode" && LastProjectCode < 0)
{
result = "Last project code cannot be negative.";
}
return result;
}
}
</pre></li>
</ul><br />
<span style="font-size: 130%; font-weight: bold;">Disadvantages</span><br />
All mentioned situation have at least 2 major disadvantages <br />
<ul><li>Possibility to mistype name</li>
<li>Hard refactoring (since refactoring cannot determine if name of property in literal is really name of property)</li>
</ul>To avoid it I have wrote TypeHelper class where you can find out property name in static way like this: <span style="font-size: 130%; font-weight: bold;"> </span><br />
<br />
<span style="font-size: 130%; font-weight: bold;">Solution</span> <br />
<pre class="brush: csharp; gutter:false">string propertyName = TypeHelper.GetPropertyName<user>(u => u.LastProjectCode);
</user></pre>Now both disadvantages disappeared <br />
<ul><li>You cannot misspell word since compiler checks it</li>
<li>Refactoring is possible because refactoring engine knows all type names at compile type and none of them is "hidden" in literals</li>
</ul><span style="font-size: 130%; font-weight: bold;">Usage</span><br />
Here is how you can extract property names and PropertyInfo object in staticly typed way: <br />
<pre class="brush: csharp; gutter:false">string propertyName = TypeHelper.GetPropertyName<user>(u => u.LastProjectCode);
PropertyInfo property1 = TypeHelper.GetProperty((SomeClass o) => o.InstanceProperty.Length);
PropertyInfo property2 = TypeHelper.GetProperty(() => SomeClass.StaticProperty.Length);
</user></pre><br />
<span style="font-size: 130%; font-weight: bold;">Implementation</span> <br />
<pre class="brush: csharp; gutter:false">public class TypeHelper
{
private static PropertyInfo GetPropertyInternal(LambdaExpression p)
{
MemberExpression memberExpression;
if (p.Body is UnaryExpression)
{
UnaryExpression ue = (UnaryExpression)p.Body;
memberExpression = (MemberExpression)ue.Operand;
}
else
{
memberExpression = (MemberExpression)p.Body;
}
return (PropertyInfo)(memberExpression).Member;
}
public static string GetPropertyName<TObject>(Expression<Func<TObject, object>> p)
{
return GetPropertyNameInternal(p);
}
public static string GetPropertyName<TObject, T>(Expression<Func<TObject, T>> p)
{
return GetPropertyNameInternal(p);
}
public static string GetPropertyName<T>(Expression<Func<T>> p)
{
return GetPropertyNameInternal(p);
}
private static string GetPropertyNameInternal(LambdaExpression p)
{
return GetPropertyInternal(p).Name;
}
public static PropertyInfo GetProperty<TObject>(Expression<Func<TObject, object>> p)
{
return GetPropertyInternal(p);
}
public static PropertyInfo GetProperty<TObject, T>(Expression<Func<TObject, T>> p)
{
return GetPropertyInternal(p);
}
public static PropertyInfo GetProperty<T>(Expression<Func<T>> p)
{
return GetPropertyInternal(p);
}
}
</pre>Mirosław Jedynakhttp://www.blogger.com/profile/03407217246259905745noreply@blogger.com5tag:blogger.com,1999:blog-6817808781210919491.post-90678825727039574822008-06-06T13:25:00.013+02:002008-12-10T22:25:23.899+01:00ASP.NET MVC in legacy solutions (classic Web Forms)ASP.NET MVC is new approach, when building web sites using ASP.NET. It’s still quite new and at this moment version <span style="font-weight: bold;">ASP.NET MVC Preview 3</span> has been released. All examples in this article conform to this version.<br /><br />While MVC framework is still evolving more and more people start using it even in production environment. Creating new application from scratch with ASP.NET MVC is explained in many places and won’t be covered here. The biggest challenge is to introduce new approach into existing solution and then migrate site day by day, page by page to MVC. Before describing required steps which must be taken to use classic Web Forms and MVC in parallel way some assumptions/limitations must be introduced.<br /><br /><span style="font-weight: bold;font-size:130%;" >Assumptions/limitations:</span><br /><br /><ul><li>Pages using MVC controller cannot use classic Web Forms Master Page – new Master Page, which looks same as old one must be created and all changes must be synchronized in both files,</li><li>Classic controls which depends on post back data cannot be used – controls which does not used ViewState will work properly if tag <form runat="”server”"/> surrounds page content,</li><li>All links to new pages which has been rewritten to ASP.NET MVC must be updated – you may also consider using <a href="http://blog.eworldui.net/post/2008/04/ASPNET-MVC---Legacy-Url-Routing.aspx">LegacyRouteHandler</a>, which redirects user from old pages to new one.</li><li>It’s strongly recommended that, ale page content is rewritten to MVC – it means, you shouldn’t use classic controls together with MVC Components. Disadvantage is that it may lead to temporary duplicating functionality, but clever with refactoring code duplication is minimal.</li></ul><br /><span style="font-weight: bold;font-size:130%;" >1. Convert VS 2005 Web Project to VS 2008 Web Application</span><br /><br />ASP.NET MVC may be used only with Web application so all solutions using Web Projects must be converted (both VS 2005 and VS 2008 projects). In details it described <a href="http://msdn.microsoft.com/en-us/library/aa983476.aspx">here</a>, but in short:<br /><ul><li>Create a new Visual Studio 2008 Web application project in a new solution,</li><li>Add references to a Visual Studio 2008 Web application project,</li><li>Copy Web site project files to a Web application project,</li><li>Convert pages and classes to use partial classes in a Web application project,</li><li>Run the Web Application Project</li></ul><br /><span style="font-weight: bold;font-size:130%;" >2. Create routes definition</span><br /><br />Routes definition defines which route handler and which controller will be invoked for specific URL. Goal of this section it to describe configuration which works for both Web Forms and MVC<br /><ul><li>Reference MVC assemblies: <span style="font-style: italic;">System.Web, System.Web.Abstractions, System.Web.Routing</span></li><li>Define routes in Application.Start event – notice adding “.mvc” appendix to route definition which must be handled by MVC framework. Since none of current pages/items uses .mvc extension (usually aspx, ashx) old request will be treated as old Web Forms requests.<br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4KVW4baknI5FBMmWa7Mixh-iUQPf4TJuVOoS5mTmhAYbFfSLQUL9wKgtcctB_LW9ZRYQpK2JXzHW4lwtceIKBlxHixr2c5PjpUiP54CdNJYKVK4Q8NTbpEVBjt9m-CebqHYFtoD-VSTQ/s1600-h/0_application_start_asp_net_mvc.GIF"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4KVW4baknI5FBMmWa7Mixh-iUQPf4TJuVOoS5mTmhAYbFfSLQUL9wKgtcctB_LW9ZRYQpK2JXzHW4lwtceIKBlxHixr2c5PjpUiP54CdNJYKVK4Q8NTbpEVBjt9m-CebqHYFtoD-VSTQ/s400/0_application_start_asp_net_mvc.GIF" alt="" id="BLOGGER_PHOTO_ID_5208729954028614002" border="0" /></a><br /></li></ul><br /><span style="font-weight: bold;font-size:130%;" >3. Update web.config</span><br /><br />You must add some new sections to web config to start using MVC framework. Steps below are necessary however some optional section has been omitted for clearance.<br /><ul><li>Add MVC assemblies to <span style="font-style: italic;"><system.web>system.web\compilation<compilation>\assemblies</compilation></system.web></span><span><system.web><compilation><assemblies> section<br /></assemblies></compilation></system.web></span><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgEMkqqeZYN0UNHP4DIzlrHcDAN3MZqDhvRdfaLDck07L96zqqCIp15E0rUjD-RHxDTbOxaI0ekipZ4j5SCjkiRU-BEkDNP-c4wdafY1qz1y-jQdGzVh-9hTXN9LsZknveJjXFXRqJGmmY/s1600-h/1_compilation_assemblies_asp_net_mvc.gif"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgEMkqqeZYN0UNHP4DIzlrHcDAN3MZqDhvRdfaLDck07L96zqqCIp15E0rUjD-RHxDTbOxaI0ekipZ4j5SCjkiRU-BEkDNP-c4wdafY1qz1y-jQdGzVh-9hTXN9LsZknveJjXFXRqJGmmY/s400/1_compilation_assemblies_asp_net_mvc.gif" alt="" id="BLOGGER_PHOTO_ID_5208730139352869266" border="0" /></a><br /></li><li>Add namespaces to<span style="font-style: italic;"> pages<pages>\namespaces<namespaces></namespaces></pages></span> section<br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEik7Xg7B7hz5UYD-vcMyRz0AVk6EUk6jHaRXJkQHwmoK3nBQVjXHBV6K04HZTkkC8GRZxzyrd9_L6mMWeu95zu2rCvpARgXGEI8QJXvRUr04jQcAfaMt802m2eazogFsEhvTZZuiGjhhaY/s1600-h/2_pages_namespaces_asp_net_mvc.gif"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEik7Xg7B7hz5UYD-vcMyRz0AVk6EUk6jHaRXJkQHwmoK3nBQVjXHBV6K04HZTkkC8GRZxzyrd9_L6mMWeu95zu2rCvpARgXGEI8QJXvRUr04jQcAfaMt802m2eazogFsEhvTZZuiGjhhaY/s400/2_pages_namespaces_asp_net_mvc.gif" alt="" id="BLOGGER_PHOTO_ID_5208730517227310674" border="0" /></a><br /></li><li>Add Http Handlers and Http Modules<br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhx0rcaIrhJpYiCmsJbCSU0Pm4WNN8GKhcCiwlQBH9qlozQBO3dbY6wStOTn_5I5D2Th1c8oZ1c0tA5a57Kc64NObKTBBbIrm2avzKqy-0uadLsS0vldUdJB6yv10M442Xb0C_kgZGMYKw/s1600-h/3_http_handlers_and+modules_asp_net_mvc.JPG"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhx0rcaIrhJpYiCmsJbCSU0Pm4WNN8GKhcCiwlQBH9qlozQBO3dbY6wStOTn_5I5D2Th1c8oZ1c0tA5a57Kc64NObKTBBbIrm2avzKqy-0uadLsS0vldUdJB6yv10M442Xb0C_kgZGMYKw/s400/3_http_handlers_and+modules_asp_net_mvc.JPG" alt="" id="BLOGGER_PHOTO_ID_5208730613950668306" border="0" /></a><br /></li><li>If you plan use it with copy <span style="font-style: italic;"><system.webserver></system.webserver></span> section <span style="font-style: italic;">system.webserver</span> from default web.config for Visual Studio 2008<br /></li></ul>Mirosław Jedynakhttp://www.blogger.com/profile/03407217246259905745noreply@blogger.com2tag:blogger.com,1999:blog-6817808781210919491.post-75665853872172674682008-04-19T14:41:00.010+02:002011-02-03T14:58:22.680+01:00Sharepoint 2007: Creating page from layout with “No parameterless constructor defined for this object” exception<span style="font-size: 130%;"><span style="font-weight: bold;">Symptom</span><br />
<br />
</span>After creating page from layout “No parameterless constructor defined for this object” exception was thrown.<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0a4c5f44u2n2De2a9gbiyuX7dW60mKDfLSrB9cFjIKrfcrVRbaoTPidSDDjlhNxFcy0p6mj6yz-QpMZa4pd46cNV-7Yu4CE6t2W8y71Cm8kQeu9-MJaAJNntcpbj62t2w9T2dSH6zNzw/s1600-h/sharepoint_error.JPG" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"><img alt="" border="0" id="BLOGGER_PHOTO_ID_5190936625216438162" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0a4c5f44u2n2De2a9gbiyuX7dW60mKDfLSrB9cFjIKrfcrVRbaoTPidSDDjlhNxFcy0p6mj6yz-QpMZa4pd46cNV-7Yu4CE6t2W8y71Cm8kQeu9-MJaAJNntcpbj62t2w9T2dSH6zNzw/s400/sharepoint_error.JPG" style="cursor: pointer; display: block; margin: 0px auto 10px; text-align: center;" /></a><br />
<br />
<span style="font-size: 130%;"><span style="font-weight: bold;">Description</span><br />
<br />
</span>I have created site definition using <a href="http://msdn2.microsoft.com/en-us/library/ms447717.aspx">webtemp.xml</a>. Inside site definition I have had few features activated and one of them created layouts for publishing template. Inside some I have added section for automatically adding default webparts using <a href="http://msdn2.microsoft.com/en-us/library/ms997833.aspx">AllUsersWebPart element</a>.<br />
<br />
On development environment manually activating features worked perfect, but when I followed steps below:<br />
• Create site based on custom site definition<br />
• Create paged based on custom layout<br />
I saw error like this:<br />
<br />
<div style="background-color: #ffff99;"><code><span style="font-size: 85%;">No parameterless constructor defined for this object. at System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandle& ctor, Boolean& bNeedSecurityCheck)<br />
at System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean fillCache)<br />
at System.RuntimeType.CreateInstanceImpl(Boolean publicOnly, Boolean skipVisibilityChecks, Boolean fillCache)<br />
at System.Activator.CreateInstance(Type type, Boolean nonPublic)<br />
at System.Activator.CreateInstance(Type type)<br />
at Microsoft.SharePoint.WebPartPages.SPWebPartSerializer.get_DefaultControl()<br />
at Microsoft.SharePoint.WebPartPages.BinaryWebPartSerializer.Serialize(PersonalizationScope scope)<br />
at Microsoft.SharePoint.WebPartPages.BinaryWebPartSerializer.get_Links()<br />
at Microsoft.SharePoint.WebPartPages.SPWebPartManager.AddWebPartToStore(WebPart webPart, Int32 viewId, String viewGuid)<br />
...</span></code></div><br />
<span style="font-size: 130%;"><span style="font-weight: bold;">Semi-solution</span><br />
<br />
</span>I found that removing template from “Master page and layout gallery” and adding it manually again fixed that problem. Of course this solution may be acceptable in development environment, but cannot be used in production environment with a lot of templates.<br />
<br />
<span style="font-size: 130%; font-weight: bold;">Solution</span><br />
<br />
After few hours of investigation I found that site definition didn’t register web parts as safe controls. It wasn’t easy to discover and description of error didn’t make it easier.<br />
Now, you may ask why semi solution worked? After removing layout from gallery AllUsersWebPart section has been reseted and there were no default webparts on page.Mirosław Jedynakhttp://www.blogger.com/profile/03407217246259905745noreply@blogger.com2tag:blogger.com,1999:blog-6817808781210919491.post-83103563126665787112008-04-04T13:13:00.007+02:002013-10-22T10:02:55.514+02:006 steps to successful Continuous IntegrationIn early seventies, when computer aided projects become bigger and bigger, more independent companies were involved. After few months of development there was a special phase called integration. It was the hardest task to integrate all solutions and it wasn’t uncommon that project was successful until it suddenly fails due to integration hell.<br /><br />Cure for this illness is Continuous Integration – everyone should integrate with rest not after months but continuously – each week, each day, each hour – as often as possible. This practice helps avoiding problems later. It’s so called “pay me now or pay me more later”.<br /><br />To increase chance for project it should be integrated continuously. In the rest of article I will introduce 6 practices, which should be used to integrate continuously. Depending on the project and your attitude you can choose only some of described practices, but I strongly advice to fulfill preceding practices before using next one.<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEguV4VIgSpfxNL4F5IaMDJZCVK0gxg7eJaKqqvQh7vOHrAVfdDKIA9O33YTpCB0DoENYSj0CGwzFDd1i0aiQ12TPkWD4u25lq7deIoraeP4DnYwD4hrxwO_PJGwhxkBoJQc8NfWRK45jyE/s1600-h/continous_integration_establishment_vs_maintanance.GIF"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEguV4VIgSpfxNL4F5IaMDJZCVK0gxg7eJaKqqvQh7vOHrAVfdDKIA9O33YTpCB0DoENYSj0CGwzFDd1i0aiQ12TPkWD4u25lq7deIoraeP4DnYwD4hrxwO_PJGwhxkBoJQc8NfWRK45jyE/s400/continous_integration_establishment_vs_maintanance.GIF" alt="" id="BLOGGER_PHOTO_ID_5185348027921142418" border="0" /></a><br /><div style="text-align: center;"><span style="font-style: italic;">Establish vs. maintain cost for Continuous Integration</span><br /></div><br />Chart above shows relationship between cost of initial phase and cost of work required to maintain.<br /><br /><span style="font-weight: bold;font-size:130%;" >1. Use source code repository</span><br /><br />It’s first and required step to start using continuous integration. Initial cost is not so small because you need dedicated machine and backup policy for “heart of software company”. Also each developer must be aware of source control existence and install so on each machine. Consider using Svn, Cvs, Team System.<br /><br /><span style="font-weight: bold;font-size:130%;" >2. Introduce check-in policy</span><br /><br />Check in policy may be introduced after establishing source code repository – quite easy to introduce but some effort is required to follow. Firstly it involves storing all necessary files in repository. Secondly, each developer should check-in as often as possible completed part of work. Frequently integration show problem early and either solution is easy or rollback is necessary for only small part of solution.<br /><br /><span style="font-size:130%;"><span style="font-weight: bold;">3. Automate build</span></span><br /><br />Next step is to perform build automatically – no more than one action should be required to start it. If developer must do more things than he forgets about it or deliberately skips it to save time. Perfect solution is when after each commit build is automatically triggered. Cost of using it is very small after establishment so I strongly advice to use it in all projects.<br />Consider using Nant, Msbuild for scripting build process and CruiseControl.Net, Microsoft Team System or Team City as Continous integration server.<br /><br /><span style="font-size:130%;"><span style="font-weight: bold;">4. Create auto-deployable test environment</span></span><br /><br />After each build new version of binaries should be automatically deployed to test server. It helps improving better integration with customer – after each fix customer may verify new version. Some work is necessary at the beginning but it’s really worth this effort in later phases of project.<br /><br /><span style="font-size:130%;"><span style="font-weight: bold;">5. Use code quality analysis</span></span><br /><br />Using code analysis is very important in big project with many developers. It helps preserving coding conventions and constantly monitors code. If any time some suspicious code is committed warning should be generated. It differs from code reviews, because here approval or disapproval must be done automatically. Trigger for generating warning should be adjusted for each project or for whole company. Consider using NDepend, FxCop, Simian<br /><br /><span style="font-size:130%;"><span style="font-weight: bold;">6. Use unit test</span></span><br /><br />Although I’m a big fan of XP and TDD I must admit that this is the most time consuming practice. At the beginning it requires reasonably small amount of effort, but when project goes on more and more time developers spend on writing, refactoring and verifying test cases. Only some companies can reach this level of maturity and can enjoy all advantages of continuous integration. Consider using NUnit, Team System Testing, TypeMock, RhinoMock, NMock and many others.Mirosław Jedynakhttp://www.blogger.com/profile/03407217246259905745noreply@blogger.com3tag:blogger.com,1999:blog-6817808781210919491.post-22946867072260819022008-03-18T15:13:00.012+01:002013-10-22T10:02:36.235+02:00Encoding mailto in hyperlink against spam bots<span style="font-weight: bold;font-size:130%;" >Scanning pages for hyper link with mail</span><br />On almost each html tutorial you can find how to create link, which openes user's default mail application.<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJUE2xURN8ENoz45xotBPZbgg95Hrz0Yiuv8-sDDzEu-oOgaDvLO0bDj8LTtp25ditIYL8NmOJOoDDi-1sJ_mQi7dbb97bxv3iqheh5MkymfzbTe6x202zgKGqND7cXNlzbxFe-fzu5VM/s1600-h/unsafe_mail.GIF"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJUE2xURN8ENoz45xotBPZbgg95Hrz0Yiuv8-sDDzEu-oOgaDvLO0bDj8LTtp25ditIYL8NmOJOoDDi-1sJ_mQi7dbb97bxv3iqheh5MkymfzbTe6x202zgKGqND7cXNlzbxFe-fzu5VM/s400/unsafe_mail.GIF" alt="" id="BLOGGER_PHOTO_ID_5179095781481334754" border="0" /></a><span style="font-weight: bold;"><br />Don't do that</span> - after creating such page, your mailbox will contain almost only spam e-mails.<br /><br /><span style="font-weight: bold;font-size:130%;" >Solution 1 - creating dummy human readable address</span><br />You can paste on your page:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiitqrcozI7cejim1AhiliAVk3L2mkbFaaPHCo31cd1LWh_W3_PlZHd42Ir8fHkf8ELud7dsLCVgqmJoUJzqa-KPxcgK_AJ6UQ-fu9PUSnbsdiy5qFRfezhWhfVgHg8dfM8vF0l7H4YdJ0/s1600-h/mail_with_removeit.GIF"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiitqrcozI7cejim1AhiliAVk3L2mkbFaaPHCo31cd1LWh_W3_PlZHd42Ir8fHkf8ELud7dsLCVgqmJoUJzqa-KPxcgK_AJ6UQ-fu9PUSnbsdiy5qFRfezhWhfVgHg8dfM8vF0l7H4YdJ0/s400/mail_with_removeit.GIF" alt="" id="BLOGGER_PHOTO_ID_5179097048496687090" border="0" /></a><br />Yes - it's working, but do you really want place it on your company site. Imagine link on home page: "Contact our consultant - send sales_at_professional_REMOVE_IT_company.com" - actually it does not seems to be very professional.<br /><br /><span style="font-weight: bold;font-size:130%;" >Solution 2 - encode with javascript</span><br /><span style="font-weight: bold;">Concept</span>: Instead of creating<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJUE2xURN8ENoz45xotBPZbgg95Hrz0Yiuv8-sDDzEu-oOgaDvLO0bDj8LTtp25ditIYL8NmOJOoDDi-1sJ_mQi7dbb97bxv3iqheh5MkymfzbTe6x202zgKGqND7cXNlzbxFe-fzu5VM/s1600-h/unsafe_mail.GIF"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJUE2xURN8ENoz45xotBPZbgg95Hrz0Yiuv8-sDDzEu-oOgaDvLO0bDj8LTtp25ditIYL8NmOJOoDDi-1sJ_mQi7dbb97bxv3iqheh5MkymfzbTe6x202zgKGqND7cXNlzbxFe-fzu5VM/s400/unsafe_mail.GIF" alt="" id="BLOGGER_PHOTO_ID_5179095781481334754" border="0" /></a><br />Create on output page with javascript encoded email - spam-bots does not parse javascript, so email will not be properly generated:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgl_EERfzTP6dKjiupJ5zxqhqsQHs_-1pjq06spd8MTocsl1BMYz9p1iSI8NBDg-patJ-OjHqTBSuaFZMIgo40tXAaW5I5SJS1y8UF4OULWWKJD2j90oEq326rLCkVQ9LuxrTIG-Q_mPa8/s1600-h/email_javascript.GIF"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgl_EERfzTP6dKjiupJ5zxqhqsQHs_-1pjq06spd8MTocsl1BMYz9p1iSI8NBDg-patJ-OjHqTBSuaFZMIgo40tXAaW5I5SJS1y8UF4OULWWKJD2j90oEq326rLCkVQ9LuxrTIG-Q_mPa8/s400/email_javascript.GIF" alt="" id="BLOGGER_PHOTO_ID_5179099947599611906" border="0" /></a><br />In browsers supporting javascirpt it will be displayed as earlier, because <span style="font-style: italic;">document.write</span> is processed immediately after loading:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNowoAqTsN8HZQuYJWl8LqgSOCHsEDmwDz2C4A1sizf6dta2ZtLFfd90ixvamUz8hXLzQYp15nGuKY629qAZ8h9LCavbLG0yK7l26-8Wa4A9WLgqbCkV-63avqp3AhuO7OMtB4ptzYwDc/s1600-h/email_onpage.GIF"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNowoAqTsN8HZQuYJWl8LqgSOCHsEDmwDz2C4A1sizf6dta2ZtLFfd90ixvamUz8hXLzQYp15nGuKY629qAZ8h9LCavbLG0yK7l26-8Wa4A9WLgqbCkV-63avqp3AhuO7OMtB4ptzYwDc/s400/email_onpage.GIF" alt="" id="BLOGGER_PHOTO_ID_5179101283334440978" border="0" /></a><span style="font-weight: bold;">Steps</span>:<br /><ol><li>Import javascript <span style="font-style: italic;">decode64 </span>function<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXTL_6QRcr0uuWx2UwN51athUgJe-akB_fXG9XSubTjiWYU5OK4OVu8nuZ-PnzU8ThHMjKO23I5JSDFK68bfKYkU3e7grumecOe_Vvfq2Yq7eMbPA7TmthHViWBcefcs7AVU44mfePeUA/s1600-h/decode64.bmp"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXTL_6QRcr0uuWx2UwN51athUgJe-akB_fXG9XSubTjiWYU5OK4OVu8nuZ-PnzU8ThHMjKO23I5JSDFK68bfKYkU3e7grumecOe_Vvfq2Yq7eMbPA7TmthHViWBcefcs7AVU44mfePeUA/s400/decode64.bmp" alt="" id="BLOGGER_PHOTO_ID_5179102962666653730" border="0" /></a><br /></li><li>Create ASP.NET filter which finds all occurences of hyperlinks with mailto:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihIvPyCAgD7sGqIUJQKvYWP16c0O7N4ChwKdzgobLWCHOffdG_IiH9yrGMx0_-6_F4UVeWXZVcf_WeifJwnZwjR0lKrAyVjS_Llazvi2eokxOszN5m8qVMxT7C6bhUPcYcAzq0ndYqjTk/s1600-h/htmlmailtosfilter.GIF"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihIvPyCAgD7sGqIUJQKvYWP16c0O7N4ChwKdzgobLWCHOffdG_IiH9yrGMx0_-6_F4UVeWXZVcf_WeifJwnZwjR0lKrAyVjS_Llazvi2eokxOszN5m8qVMxT7C6bhUPcYcAzq0ndYqjTk/s400/htmlmailtosfilter.GIF" alt="" id="BLOGGER_PHOTO_ID_5179103525307369522" border="0" /></a><br /></li><li>Replace all occurrences with javascript which produces <span style="font-style: italic;">document.write</span> in javascript<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPhJsz05I2tkSMs-pAX-FJYyr2wv8K0DoxjwueIRAk604VFSh2GlsF9LaKyDxqWmjVazkhJ0EiKsdcVGw1dLtUGbj1gKlOuz_ZX02SIRvQQwXY5A2ybwBDGBn8-F4wrhZs8nixMifxXb4/s1600-h/encode.GIF"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPhJsz05I2tkSMs-pAX-FJYyr2wv8K0DoxjwueIRAk604VFSh2GlsF9LaKyDxqWmjVazkhJ0EiKsdcVGw1dLtUGbj1gKlOuz_ZX02SIRvQQwXY5A2ybwBDGBn8-F4wrhZs8nixMifxXb4/s400/encode.GIF" alt="" id="BLOGGER_PHOTO_ID_5179103886084622402" border="0" /></a><br /></li><li>Add configuration in web.config<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-GpmedtKfd5J5cQFDnpVObQ7Kj1k1groi2dBp_KVEULEwM7cHt0KW_mafj8euSuRQ9AIOY-Ym5T6QZvKZml9ofOqos3kOSXoMIPtJvqjO5lJgZQOVS8LJ3G1Kn7SgdtcckAmhR0VTrUc/s1600-h/inwebconfig.GIF"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-GpmedtKfd5J5cQFDnpVObQ7Kj1k1groi2dBp_KVEULEwM7cHt0KW_mafj8euSuRQ9AIOY-Ym5T6QZvKZml9ofOqos3kOSXoMIPtJvqjO5lJgZQOVS8LJ3G1Kn7SgdtcckAmhR0VTrUc/s400/inwebconfig.GIF" alt="" id="BLOGGER_PHOTO_ID_5179104221092071506" border="0" /></a><br /></li></ol>Mirosław Jedynakhttp://www.blogger.com/profile/03407217246259905745noreply@blogger.com0tag:blogger.com,1999:blog-6817808781210919491.post-37171108281190204602008-03-17T12:13:00.033+01:002013-10-23T07:16:08.009+02:00Publishing Web Application with MsBuild<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipY9aTv-mcIsy0FPRrsOJKSXTIxTAqcvidp8_nGVejnGguvENFPMsBgo3H0FBr2uMspwiSfSBIELIm_riemTofiBy9d00390i7oxVBprfEv3LeNbxgOYKbG-orIwF2oSHuSuES2sjgXEc/s1600-h/Visual_Studio_Publish.PNG"><img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipY9aTv-mcIsy0FPRrsOJKSXTIxTAqcvidp8_nGVejnGguvENFPMsBgo3H0FBr2uMspwiSfSBIELIm_riemTofiBy9d00390i7oxVBprfEv3LeNbxgOYKbG-orIwF2oSHuSuES2sjgXEc/s400/Visual_Studio_Publish.PNG" alt="" id="BLOGGER_PHOTO_ID_5178675905478477778" border="0" /></a><br /><br />Visual Studio 2005 has features that can used to publish a website in a production server or a staging server.When you compile files using the Publish Web Site utility the source code is removed. The marked up code in the .aspx files can also be removed optionally. After compilation the .aspx pages point to the compiled versions. With this feature the source code of your pages are safe. Accessing your code by others is difficult. This is one of the features that are more useful for those who want their code to be safe.<br /><br />Question is how to automate it?<br /><br /><span style="font-weight: bold;font-size:130%;" >1. Add target to .csproj file</span><br />Ensure your web application project contains :<br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg17Ud3ev3HDUE6TPTZUP90r3_ARAeeshWMcqMS4FZXr0ScJ82HYMr42fHYoR-w-aHycYewp-ZrqWRy3ppe91B6gSekM9a-HfEA_khM8IEilAGtAq492G5q5TaJHWCS35cvXcqA7wAjO8g/s1600-h/ImportProjectTarget.GIF"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg17Ud3ev3HDUE6TPTZUP90r3_ARAeeshWMcqMS4FZXr0ScJ82HYMr42fHYoR-w-aHycYewp-ZrqWRy3ppe91B6gSekM9a-HfEA_khM8IEilAGtAq492G5q5TaJHWCS35cvXcqA7wAjO8g/s400/ImportProjectTarget.GIF" alt="" id="BLOGGER_PHOTO_ID_5178675342837761986" border="0" /></a><br /><span style="font-weight: bold;font-size:130%;" >2. Call hidden task _CopyWebApplication</span><br />Call task from msbuild:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtTNWTXao9R9bvUtUcD45AUlBbOqTtLmis-tHtKTSdlmNIFV_R4aWGrHWYriFbuhGOS92I4XDgIuNMrQcUGbUIk2DjHpPKXyApVlhR_U559fduNhkufmGH5u5ASlFthnFGelsJAMJ-4Vg/s1600-h/CopyWebApplicationTask.GIF"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtTNWTXao9R9bvUtUcD45AUlBbOqTtLmis-tHtKTSdlmNIFV_R4aWGrHWYriFbuhGOS92I4XDgIuNMrQcUGbUIk2DjHpPKXyApVlhR_U559fduNhkufmGH5u5ASlFthnFGelsJAMJ-4Vg/s400/CopyWebApplicationTask.GIF" alt="" id="BLOGGER_PHOTO_ID_5178673839599208354" border="0" /></a><br /><span style="font-weight: bold;font-size:130%;" >3. Resolve references to other projects</span><br />Unfortunatelly task _CopyWebApplicaiton does not copy output from referenced project so you have to do it manually by calling <span style="font-weight: bold;">ResolveReferences</span> task<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggUm_vFmKMsoaHTEuX36CcYQdR3AIv0U_AnIBzH_T0xN5Al9G16LboN_vTwc5h2kEIF7VVyF553agQbwtr0SYt3wPZdBuBWpJDwoaZsFwkSjmtSKfKlztZSsYib-GTHV5o5PkX3H8wKv4/s1600-h/ResolveReferencesTask.GIF"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggUm_vFmKMsoaHTEuX36CcYQdR3AIv0U_AnIBzH_T0xN5Al9G16LboN_vTwc5h2kEIF7VVyF553agQbwtr0SYt3wPZdBuBWpJDwoaZsFwkSjmtSKfKlztZSsYib-GTHV5o5PkX3H8wKv4/s400/ResolveReferencesTask.GIF" alt="" id="BLOGGER_PHOTO_ID_5178674810261817266" border="0" /></a><br /><br /><span style="font-weight: bold;font-size:130%;" >Update: Publishing Websites with cascade dependency (2009.02.03)</span><br /><br />It's quite common that you have project structure like on image below. Web site on top of Business Logic Layer, which is on top of Data Access Layer.<br /><br />Unfortunately, solution posted above does not solve second degree references (references of references) and therefore. When project is built with code above only first degree are propery copied to output directory.<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNoM5mHPltXB0zq_Xo67Nuto5jrQz4YuClGlhT647Y-CHtHhB4ozgseJVqgpY5zAyvd3Yc96UO8bYX2OhR6ozHVJViImxnhOtVcKA_3rHGAsuHtC00OJuL_NxvOPgvhCNrK4SSTrHSi7E/s1600-h/project_structure.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 187px; height: 150px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNoM5mHPltXB0zq_Xo67Nuto5jrQz4YuClGlhT647Y-CHtHhB4ozgseJVqgpY5zAyvd3Yc96UO8bYX2OhR6ozHVJViImxnhOtVcKA_3rHGAsuHtC00OJuL_NxvOPgvhCNrK4SSTrHSi7E/s320/project_structure.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5298600021206999810" /></a><br />In order to configure all degree references to be copied you have to specify both<ul><li><b>WebProjectOutputDir</b> - where website will be published</li><li><b>OutDir</b> - where all assemblies should be placed (bin folder></li></ul><br />In MsBuild script you can use snippet as below:<br /><br /><div style="font-family: Courier New; font-size: 10pt; color: black; background: white;"><p style="margin: 0px;"><span style="color: blue;"><</span><span style="color: #a31515;">MSBuild</span><span style="color: blue;"> </span><span style="color: red;">Projects</span><span style="color: blue;">=</span>"<span style="color: blue;">WebApplication\WebApplication.csproj</span>"</p><p style="margin: 0px;"><span style="color: blue;"></span><span style="color: red;">Properties</span><span style="color: blue;">=</span>"<span style="color: blue;">Configuration=Release;WebProjectOutputDir=..\build\Release\Web\;OutDir= ..\builda\Release\Web\bin\</span>"</p><p style="margin: 0px;"><span style="color: blue;"></span><span style="color: red;">Targets</span><span style="color: blue;">=</span>"<span style="color: blue;">ResolveReferences;_CopyWebApplication</span>"<span style="color: blue;"> /></span></p></div><br />Thanks to Maciej Grzyb for solution <a href="http://maciek79.secondbrain.com/">http://maciek79.secondbrain.com</a>Mirosław Jedynakhttp://www.blogger.com/profile/03407217246259905745noreply@blogger.com8tag:blogger.com,1999:blog-6817808781210919491.post-28721180898601314582008-02-29T10:58:00.028+01:002008-12-10T22:25:27.300+01:00Configuring Cruise Control .Net with MsBuild and email notification<span style="font-size:130%;"><span style="font-weight: bold;">Best scenario should</span><br /></span><ul><li>Easy to maintain - no extra developer effort should be required</li><li>Easy to extend - time to time it happens that custom action must be performed during build process</li><li>Build failure results should be accessible - via mail and on dashbord to decrease time spent looking for error<br /></li></ul><span style="font-size:130%;"><span style="font-weight: bold;">Possible build scenarions</span></span><br />There are few build scenarios, that could be used with CCNET:<br /><ul><li>Visual studio batch mode</li><li>Nant compilation</li><li>MsBuild compilation</li></ul>I'm going to go fast through first two, and later focus on most suitable ble for me sollution - using msbuild.<br /><br /><span style="font-weight: bold;font-size:130%;" >Using Visual Studio batch mode</span><br />The easiest and fastest to establish way to compile projects is usage of Visual Stuido command line<br />Adventages:<br /><ul><li>Easy to establish</li><li>No developer effort required after enviroment configuration<br /></li></ul>Disadventages:<br /><ul><li>Error report is generated only on local disk, so it's hard for developer to check why it does not compile in build enviroment</li><li>Standard mail contains only changed files listing</li></ul><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjiQjNzfAfYhSsQAa6pVccEQLIWuNUJngC-6PJOUEFig-cieG4SJ9FwpeZYcFLynIfhWaLNIBtOP-fcJVo4eGsW8U2GhOxZXQhpvL0fzqO9vv88LrkBhEt68bIarqfTHVG-yHmskikReuI/s1600-h/5.GIF"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjiQjNzfAfYhSsQAa6pVccEQLIWuNUJngC-6PJOUEFig-cieG4SJ9FwpeZYcFLynIfhWaLNIBtOP-fcJVo4eGsW8U2GhOxZXQhpvL0fzqO9vv88LrkBhEt68bIarqfTHVG-yHmskikReuI/s400/5.GIF" alt="" id="BLOGGER_PHOTO_ID_5174227194255612674" border="0" /></a><div style="text-align: center;"><span style="font-style: italic;">Standard DevEnv configuration</span><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjcMBmlWmVccs8YJz63dfdhHchAYKjxpoTHWYc17Qu32aRhDa5j44rwV_3gS128pig8J3TAGDQjTr4k7hE71clzRa4eGHDdjtkZndYGRiYKC_nQMLddjYfoq0_ZdeHlFrwcbiVdqn5uBHo/s1600-h/1.GIF"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjcMBmlWmVccs8YJz63dfdhHchAYKjxpoTHWYc17Qu32aRhDa5j44rwV_3gS128pig8J3TAGDQjTr4k7hE71clzRa4eGHDdjtkZndYGRiYKC_nQMLddjYfoq0_ZdeHlFrwcbiVdqn5uBHo/s400/1.GIF" alt="" id="BLOGGER_PHOTO_ID_5174224664519875266" border="0" /></a><span style="font-style: italic;">Mail for devenv task</span><br /></div><br /><span style="font-weight: bold;font-size:130%;" >Using Nant</span><br />Nant is another common tool used for building solution, by for me it's biggest disatventage is so uge, that it discards it.<br />Adventages:<br /><ul><li>Very extensible with plenty of custom task available</li><li>Tested in Java enviroment</li><li>Possible to include results in mail<br /></li></ul>Disadventages:<br /><ul><li>Each developer has to update build script after adding<br /></li><ul><li>project</li><li>file to project (it's possible to include **/*.cs but what about *.xml files or not included files)</li></ul><li>Not so easy to configure first time</li></ul>For me, forcing developer to update build script is both unsecure and errorprone. In big build enviroments only build master should be resposible for build process. Also it's not obligatory for every developer (like CSS front side developer) to have knowledge about Nant syntax. Mistakes made in build script will fail build process what we should avoid like fire.<br /><br /><span style="font-weight: bold;font-size:130%;" >Using MsBuild</span><br />For me it's most suitable solution and I would strongly recomend it for teams which have quite common build scenarios. In case of some sofisticated build process extension is also possible.<br /><span style="font-size:100%;">Adventages:<br /></span><ul><li>Possible to include build results in mail</li><li>Extensible</li><li>Easy to maintain - no extra developer effort is required</li></ul>Disadventages:<br /><ul><li>Takes some time to configure<br /></li></ul><span style="font-weight: bold;font-size:130%;" >MsBuild configuration</span><br />Follow the instructions from <a href="http://confluence.public.thoughtworks.org/display/CCNETCOMM/Improved+MSBuild+Integration">ThoughtWorks blog.</a> They have introduced improved MsBuild logger.<br /><ol><li>Copy <a href="http://confluence.public.thoughtworks.org/download/attachments/6253/Rodemeyer.MsBuildToCCnet.dll?version=5">Rodemeyer.MsBuildToCCnet.dll</a> to your \CruiseControl.NET\server folder</li><li>Copy <a href="http://confluence.public.thoughtworks.org/download/attachments/6253/msbuild2ccnet.xsl?version=1">msbuild2ccnet.xsl</a> to your \CruiseControl.NET\webdashboard\xsl and \CruiseControl.NET\servrt\xsl folder</li><li>Copy <a href="http://confluence.public.thoughtworks.org/download/attachments/6253/cruisecontrol.css?version=1">cruisecontrol.css</a> to your \CruiseControl.NET\webdashboard folder <ul><li>If you have modified the dashboards stylesheet, just copy the <em>section-project</em>, <em>section-error</em>, <em>section-warning</em>, <em>error</em> and <em>warning</em> style.</li></ul> </li><li>In your <a style="font-style: italic;" href="http://confluence.public.thoughtworks.org/display/CCNET/Configuring+the+Server" title="Configuring the Server">ccnet.config file</a> locate the <a style="font-style: italic;" href="http://confluence.public.thoughtworks.org/display/CCNET/MsBuild+Task" title="MsBuild Task"><msbuild> task</msbuild></a>. Change the <logger> node to </logger><div class="code panel" style="border-width: 1px;"><div class="codeContent panelContent"> <span class="code-tag"><logger></logger></span><span style="font-family:courier new;">c:\Program Files\CruiseControl.NET\ server\Rodemeyer.MsBuildToCCNet.dll</span></div></div> <p>I highly suggest that you set the verbosity level to quiet or minimal to avoid excessive large log files!</p> <div class="code panel" style="border-width: 1px;"><div class="codeContent panelContent"> <pre class="code-xml"><span class="code-tag"><buildargs></buildargs></span>/v:quiet /noconlog /p:Configuration=Debug<span class="code-tag"></span></pre> </div></div></li><li>In your <a href="http://confluence.public.thoughtworks.org/display/CCNET/Configuring+the+Web+Dashboard" title="Configuring the Web Dashboard">dashboard.config</a> file locate the <buildreportbuildplugin> section. Remove the <em>compile-msbuild.xsl</em> and <em>msbuild.xsl</em> if present. Theses transformations are not compatible with the MsBuildToCCNet logger. Add the <em>msbuild2ccnet.xsl</em> transformation instead.</buildreportbuildplugin><span class="code-tag"><buildreportbuildplugin></buildreportbuildplugin></span> <span class="code-tag"><xslfilenames></xslfilenames></span><br /><div class="code panel" style="border-width: 1px;"><div class="codeContent panelContent"><pre class="code-xml"> <span class="code-tag"><xslfile></xslfile></span>xsl\header.xsl<br /><span class="code-tag"><xslfile></xslfile></span>xsl\msbuild2ccnet.xsl<br /><span class="code-tag"><xslfile></xslfile></span>xsl\modifications.xsl<br />[...]</pre></div></div></li><li>Modify <span style="font-style: italic;">\CruiseControl.NET\server\ccnet.exe.config </span>and <span style="font-style: italic;">\CruiseControl.NET\server\ccservice.exe.config </span>add following lines to add mail sending functionality</li><li>Configure project</li></ol><br /><div style="text-align: center;"><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEglQQGt2wt4UUkJctIlKCYdaN2CCCypI2UmhXqfIXNHuYCJBguQqqKJlFD0emE1OTsRVcvFT7eaBPavpcshKz9L4RnNqtE-5nJRaSYpCMSCnxlHhYSU6-nN1imUDs-T_u9HrpJRSzdPzw8/s1600-h/4.GIF"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEglQQGt2wt4UUkJctIlKCYdaN2CCCypI2UmhXqfIXNHuYCJBguQqqKJlFD0emE1OTsRVcvFT7eaBPavpcshKz9L4RnNqtE-5nJRaSYpCMSCnxlHhYSU6-nN1imUDs-T_u9HrpJRSzdPzw8/s400/4.GIF" alt="" id="BLOGGER_PHOTO_ID_5174225553578105586" border="0" /></a><span style="font-style: italic;">Sample project configuration</span><br /></div><div style="text-align: center;"><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjRDfKEgLkLMHBgJN9tYUnBRakjO8AZcIHdB6M7PgbL86dsfWr3G9dgNNpvJXOZ4XiWYrpJKpF9lQZeIZnnAqJQLLH72yBVzq1EtcYbX6Ur9nEPz6zcSXCVKl6VCLOPi7BhA9beCVRSCMI/s1600-h/2.GIF"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjRDfKEgLkLMHBgJN9tYUnBRakjO8AZcIHdB6M7PgbL86dsfWr3G9dgNNpvJXOZ4XiWYrpJKpF9lQZeIZnnAqJQLLH72yBVzq1EtcYbX6Ur9nEPz6zcSXCVKl6VCLOPi7BhA9beCVRSCMI/s400/2.GIF" alt="" id="BLOGGER_PHOTO_ID_5174224844908501714" border="0" /></a><span style="font-style: italic;">Mail for MsBuild task</span><br /></div><div style="text-align: center;"><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHwoEvz5oG20-ZOmDVdQoGnwOS98NPFmlrUEeAl_TbwHuPdHpD-ka4CI5y4kgtrILOf95SeI49xHejCHUpEW1AYiNx1qepYWXwa4Rhyphenhyphenz895OrWJ9_ypznCyowQDHif9l-NQ67-cMcD0_0/s1600-h/3.GIF"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHwoEvz5oG20-ZOmDVdQoGnwOS98NPFmlrUEeAl_TbwHuPdHpD-ka4CI5y4kgtrILOf95SeI49xHejCHUpEW1AYiNx1qepYWXwa4Rhyphenhyphenz895OrWJ9_ypznCyowQDHif9l-NQ67-cMcD0_0/s400/3.GIF" alt="" id="BLOGGER_PHOTO_ID_5174223908605631154" border="0" /></a><span style="font-style: italic;">Webdashboard output</span><br /></div>Mirosław Jedynakhttp://www.blogger.com/profile/03407217246259905745noreply@blogger.com1