<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-7975584389633834518</id><updated>2012-01-25T23:05:53.322+01:00</updated><category term='HtmlEditor'/><category term='IE6'/><category term='User Agent'/><category term='Software Development'/><category term='Opera'/><category term='Custom Property'/><category term='Misc'/><category term='CSS Sprites'/><category term='Sitecore'/><category term='ASP.NET'/><category term='Configuration'/><category term='PageSpeed'/><category term='500'/><category term='C#'/><category term='Performance Optimization'/><category term='Firefox'/><category term='css'/><category term='Internet Information Services'/><category term='Firebug'/><category term='PageRank'/><category term='Scheduled jobs'/><category term='html'/><category term='EPiServer'/><category term='Device'/><category term='Internet Explorer'/><category term='Open Graph'/><category term='YSlow'/><category term='404'/><category term='Web applications'/><category term='JavaScript'/><category term='Facebook'/><category term='Developer Toolbar'/><category term='.Net Framework'/><title type='text'>breath of fresh marasm</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://marekmusielak.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7975584389633834518/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://marekmusielak.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Marek Musielak</name><uri>http://www.blogger.com/profile/00718594959358144177</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_UExK-VwSpVM/SbUHbibCtAI/AAAAAAAAAB4/3lVUdtSKG9g/S220/maras3.jpeg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>15</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-7975584389633834518.post-2278694432587469097</id><published>2011-11-10T16:17:00.009+01:00</published><updated>2011-11-10T16:40:06.674+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Facebook'/><category scheme='http://www.blogger.com/atom/ns#' term='Web applications'/><category scheme='http://www.blogger.com/atom/ns#' term='User Agent'/><category scheme='http://www.blogger.com/atom/ns#' term='Sitecore'/><category scheme='http://www.blogger.com/atom/ns#' term='Open Graph'/><category scheme='http://www.blogger.com/atom/ns#' term='Device'/><title type='text'>Sitecore Facebook Device - Facebook Open Graph device for Sitecore</title><content type='html'>&lt;p&gt;Recently I've been working on a &lt;b&gt;Sitecore&lt;/b&gt; project for a large company that produces distilled beverages. One of the requirements was that before user can access any content within the site, they must pass age verification process. Still we needed to be able to share any of the pages on the &lt;b&gt;Facebook&lt;/b&gt;, with the content specific for that particular page, not the data which is displayed on the age verification page.&lt;/p&gt;&lt;p&gt;While sharing the page on &lt;b&gt;Facebook&lt;/b&gt;, it crawls the page and looks for the data that can be displayed for other &lt;b&gt;Facebook&lt;/b&gt; users. The first thing which is checked is &lt;b&gt;open graph&lt;/b&gt; data. I'm not going to explain it deeply in this post, but if you want to know more, you can learn it from here &lt;a href="http://developers.facebook.com/docs/opengraph/"&gt;open graph protocol - facebook developers&lt;/a&gt;. What we need to know for the purposes of this article is that there are some meta tags that should be included within the html page header that are used by &lt;b&gt;Facebook&lt;/b&gt;. Our goal was to display the page with those meta tags instead of the age verification page, if the site is crawled by &lt;b&gt;Facebook&lt;/b&gt;.&lt;/p&gt;&lt;div class="fullpost"&gt;&lt;p style="text-align: center; margin: 10px 0px;"&gt;&lt;a target="_blank" href="http://img.marasm.pl/images/sitecore-facebook-device.png"&gt;&lt;img src="http://img.marasm.pl/images/sitecore-facebook-device.png" alt="Sitecore Facebook Device User Agent - 'facebookexternalhit'" width="500" style="margin: 0px;" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;For this purpose we chose the approach that uses &lt;b&gt;Sitecore devices&lt;/b&gt; functionality. When the page is accessed by Facebook, the user agent of the facebook browser introduces itself as&lt;/p&gt;&lt;p style="text-align: center"&gt;&lt;b&gt;facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)&lt;/b&gt;&lt;/p&gt;&lt;p&gt;We created a new &lt;b&gt;device&lt;/b&gt; in the &lt;b&gt;Sitecore&lt;/b&gt; that is used for the browser agent containing &lt;b&gt;facebookexternalhit&lt;/b&gt;. Then we prepared a layout that is used for this device only and which contains all the &lt;b&gt;open graph&lt;/b&gt; meta data required by &lt;b&gt;Facebook&lt;/b&gt;. This allowed us to assure that all the end users will have to age verify before accessing the site, while &lt;b&gt;Facebook&lt;/b&gt; can retrieve meaningful data.&lt;/p&gt;&lt;p&gt;Once you've set up the &lt;b&gt;device&lt;/b&gt; and the layout, you can easily check whether you did it properly using &lt;a href="http://developers.facebook.com/tools/debug/og/object?q=http%3A%2F%2Fwww.marasm.pl%2Fcv%2F" target="_blank"&gt;the official Facebook URL Linter&lt;/a&gt;. The screenshot below shows an example of debug result for a page which has open graph meta data set.&lt;/p&gt;&lt;p style="text-align: center; margin: 10px 0px;"&gt;&lt;a target="_blank" href="http://img.marasm.pl/images/facebook-linter-open-graph-debugger.png"&gt;&lt;img width="500" src="http://img.marasm.pl/images/facebook-linter-open-graph-debugger.png" alt="Facebook Linter - Open Graph Debugger" style="margin: 0px;" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7975584389633834518-2278694432587469097?l=marekmusielak.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='text/html' href='http://marekmusielak.blogspot.com/2011/11/sitecore-facebook-device-facebook-open.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7975584389633834518/posts/default/2278694432587469097'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7975584389633834518/posts/default/2278694432587469097'/><link rel='alternate' type='text/html' href='http://marekmusielak.blogspot.com/2011/11/sitecore-facebook-device-facebook-open.html' title='Sitecore Facebook Device - Facebook Open Graph device for Sitecore'/><author><name>Marek Musielak</name><uri>http://www.blogger.com/profile/00718594959358144177</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_UExK-VwSpVM/SbUHbibCtAI/AAAAAAAAAB4/3lVUdtSKG9g/S220/maras3.jpeg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7975584389633834518.post-3110905708114514999</id><published>2011-07-21T15:13:00.002+02:00</published><updated>2011-07-22T13:28:17.900+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='PageSpeed'/><category scheme='http://www.blogger.com/atom/ns#' term='Performance Optimization'/><category scheme='http://www.blogger.com/atom/ns#' term='Web applications'/><category scheme='http://www.blogger.com/atom/ns#' term='YSlow'/><category scheme='http://www.blogger.com/atom/ns#' term='PageRank'/><title type='text'>Has Google PageRank started to ignore 'www'?</title><content type='html'>&lt;p&gt;Nearly year ago I became crazy about optimizing websites performance. &lt;a href="http://developer.yahoo.com/yslow/" target="_blank"&gt;YSlow&lt;/a&gt; and &lt;a href="http://code.google.com/intl/pl/speed/page-speed/" target="_blank"&gt;PageSpeed&lt;/a&gt; became my closest friends. One of their advices was to serve static content from cookieless domains. Unfortunately, my private web site url was &lt;b&gt;http://marasm.pl&lt;/b&gt; (without &lt;b&gt;'www.'&lt;/b&gt; prefix) so I wasn't able to create any cookieless subdomain as all 'marasm.pl' cookies were used for subdomains.  &lt;/p&gt;&lt;br /&gt;&lt;p&gt;That was the reason why I changed the url of my website to &lt;a href="http://www.marasm.pl" title="marek musielak site" target="_blank"&gt;&lt;b&gt;www.marasm.pl&lt;/b&gt;&lt;/a&gt; and redirected (with 301) all the calls from the url without 'www' to the new addresses. One month later PageRank of all pages within my site dropped to 0. I wasn't the happiest person in the world but there was nothing I could do. I left everything as it was hoping that the value of the content would earn its PageRank back.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;One week ago I noticed that the PageRank of my website pages is back. More surprisingly, now it has the same values for the pages for both urls starting with 'www' and without, despite the fact that urls without 'www' are redirected. I'm sure one year ago Google didn't give any PageRank for the pages that were redirected with 301. Why did they change their minds? Is the current solution better? What would happen if I switch my domain back to the &lt;b&gt;marasm.pl&lt;/b&gt; without 'www'?&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7975584389633834518-3110905708114514999?l=marekmusielak.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='text/html' href='http://marekmusielak.blogspot.com/2011/07/has-google-pagerank-started-to-ignore.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7975584389633834518/posts/default/3110905708114514999'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7975584389633834518/posts/default/3110905708114514999'/><link rel='alternate' type='text/html' href='http://marekmusielak.blogspot.com/2011/07/has-google-pagerank-started-to-ignore.html' title='Has Google PageRank started to ignore &apos;www&apos;?'/><author><name>Marek Musielak</name><uri>http://www.blogger.com/profile/00718594959358144177</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_UExK-VwSpVM/SbUHbibCtAI/AAAAAAAAAB4/3lVUdtSKG9g/S220/maras3.jpeg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7975584389633834518.post-2864778938854499218</id><published>2010-06-18T12:23:00.004+02:00</published><updated>2010-06-20T22:34:39.302+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='EPiServer'/><category scheme='http://www.blogger.com/atom/ns#' term='Software Development'/><category scheme='http://www.blogger.com/atom/ns#' term='Web applications'/><category scheme='http://www.blogger.com/atom/ns#' term='ASP.NET'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><title type='text'>How to determine who deleted the page in EPiServer</title><content type='html'>&lt;p&gt;Couple of days ago a colleague of mine was looking for a way to &lt;b&gt;determine who moved a page to the recycle bin&lt;/b&gt; in &lt;b&gt;EPiServer&lt;/b&gt;. Much to my surprise, there is no way to find that out using &lt;b&gt;EPiServer&lt;/b&gt; edit / admin mode.&lt;/p&gt;&lt;p&gt;I had been sure that when one moves the page to the recycle bin, the new version is created, but it turned out that there is no single trace who deleted the page. That's why I wrote the code below:&lt;/p&gt;&lt;div class="fullpost"&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 480px"&gt;&lt;code&gt;using System;&lt;br /&gt;using System;&lt;br /&gt;using EPiServer;&lt;br /&gt;using EPiServer.Core;&lt;br /&gt;using EPiServer.DataAccess;&lt;br /&gt;&lt;br /&gt;namespace Web&lt;br /&gt;{&lt;br /&gt;    public class Global : EPiServer.Global&lt;br /&gt;    {&lt;br /&gt;        protected void Application_Start(Object sender, EventArgs e)&lt;br /&gt;  {&lt;br /&gt;            DataFactory.Instance.MovedPage += SaveNewVersionIfDeleted;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        private static void SaveNewVersionIfDeleted(object sender, PageEventArgs e)&lt;br /&gt;        {&lt;br /&gt;            if (DataFactory.Instance.IsWastebasket(e.TargetLink))&lt;br /&gt;            {&lt;br /&gt;                PageData deletedPage = DataFactory.Instance.GetPage(e.PageLink).CreateWritableClone();&lt;br /&gt;                SaveAction action = SaveAction.ForceNewVersion;&lt;br /&gt;                action |= (deletedPage.PendingPublish) ? SaveAction.Save : SaveAction.Publish;&lt;br /&gt;                DataFactory.Instance.Save(deletedPage, action);&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The code is very short and simple. In Application_Start method of the Global.asax.cs I added a new method to the DataFactory.Instance.MovedPage handler. This method checks whether the target is recycle bin and forces a new version of the page. Now if you check the version list of any of the page in the recycle bin, the latest version shows you who and when deleted the page.&lt;/p&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7975584389633834518-2864778938854499218?l=marekmusielak.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='text/html' href='http://marekmusielak.blogspot.com/2010/06/how-to-determined-who-deleted-page-in.html#comment-form' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7975584389633834518/posts/default/2864778938854499218'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7975584389633834518/posts/default/2864778938854499218'/><link rel='alternate' type='text/html' href='http://marekmusielak.blogspot.com/2010/06/how-to-determined-who-deleted-page-in.html' title='How to determine who deleted the page in EPiServer'/><author><name>Marek Musielak</name><uri>http://www.blogger.com/profile/00718594959358144177</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_UExK-VwSpVM/SbUHbibCtAI/AAAAAAAAAB4/3lVUdtSKG9g/S220/maras3.jpeg'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7975584389633834518.post-3199178107625414499</id><published>2009-11-30T15:40:00.010+01:00</published><updated>2010-01-18T16:46:47.484+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scheduled jobs'/><category scheme='http://www.blogger.com/atom/ns#' term='EPiServer'/><category scheme='http://www.blogger.com/atom/ns#' term='Software Development'/><category scheme='http://www.blogger.com/atom/ns#' term='Web applications'/><category scheme='http://www.blogger.com/atom/ns#' term='ASP.NET'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><title type='text'>Custom scheduled jobs in EPiServer - advanced tips</title><content type='html'>&lt;p&gt;If you are here, you probably know what &lt;b&gt;EPiServer scheduled jobs&lt;/b&gt; are. If not, I suggest you to take a look at Ted Nyberg post here &lt;a href="http://tednyberg.com/post/Create-a-custom-scheduled-job-in-EPiServer.aspx"&gt;Ted Nyberg: &lt;b&gt;scheduled jobs in EPiServer&lt;/b&gt;&lt;/a&gt; first. If you're still with me, I assume you already have basic knowledge about &lt;b&gt;scheduled jobs&lt;/b&gt; in &lt;b&gt;EPiServer&lt;/b&gt;.&lt;/p&gt;&lt;p&gt;In this post I want to talk about several things you should take into account during your work with &lt;b&gt;EPiServer scheduled jobs&lt;/b&gt; like:&lt;br /&gt;&lt;a rel="no-follow" href="#many-servers"&gt;* running the &lt;b&gt;scheduled job&lt;/b&gt; on the chosen server&lt;/a&gt;&lt;br /&gt;&lt;a rel="no-follow" href="#one-job"&gt;* executing only one &lt;b&gt;EpiServer job&lt;/b&gt; in the same time&lt;/a&gt;&lt;br /&gt;&lt;a rel="no-follow" href="#specified-user"&gt;* executing &lt;b&gt;scheduled job&lt;/b&gt; as a specified user&lt;/a&gt;&lt;br /&gt;&lt;a rel="no-follow" href="#scheduler-process"&gt;* process within the job thread is executed&lt;/a&gt;&lt;/p&gt;&lt;br /&gt;&lt;div class="fullpost"&gt;&lt;br /&gt;&lt;h3 id="many-servers"&gt;Many servers - one job only&lt;/h3&gt;&lt;p&gt;If you have many web servers connected to the single DB server (no matter if this is your development or production environment), you should be aware of the fact, that if the job is started automatically &lt;b&gt;ANY&lt;/b&gt; of the servers can start your &lt;b&gt;scheduled job&lt;/b&gt;. What is more, you can not be sure that the job won't be run on many servers in the same time. In some cases this is desired behavior, while in other it can cause serious problems.&lt;/p&gt;&lt;p&gt;Lets consider a situation when the role of the &lt;b&gt;scheduled job&lt;/b&gt; is to perform some CPU consuming calculations. Assume that we have 4 server: 3 of them can be accessed via Internet and the other one is used only for entering content. In this scenario I would like all those calculations to be performed on the 4th server so the users accessing the site do not encounter any performance issues.&lt;/p&gt;&lt;p&gt;There is simple solution - stop EPiServer Scheduler service on the servers that can be accessed via Internet and leave the service running only on the 4th server. Do not forget to set Startup Type of the service to "Manual", in other case the scheduler will start on the machine after the reboot.&lt;/p&gt;&lt;h3 id="one-job"&gt;Only one job executed in the same time&lt;/h3&gt;&lt;p&gt;There are plenty of situations when we don't want to let 2 same &lt;b&gt;scheduled jobs&lt;/b&gt; to be executed simultaneously. But when we have a &lt;b&gt;scheduled job&lt;/b&gt; that is run very often and execution of which can take a lot of time, we can be pretty sure that such a moment will occur when scheduler will try to execute the same job despite the fact that one job is still running.&lt;/p&gt;&lt;p&gt;To avoid it, we can use System.Threading.Monitor class like this:&lt;/p&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 480px"&gt;&lt;code&gt;using System;&lt;br /&gt;&lt;br /&gt;namespace Maras.ScheduledJobs&lt;br /&gt;{&lt;br /&gt;    [EPiServer.PlugIn.ScheduledPlugIn(DisplayName = &amp;quot;My job&amp;quot;,&lt;br /&gt;        Description = &amp;quot;Only one 'My job' will be executed parallelly&amp;quot;)]&lt;br /&gt;    public class MyJob&lt;br /&gt;    {&lt;br /&gt;        private static readonly object MyJobLock = new object();&lt;br /&gt;&lt;br /&gt;        public static string Execute()&lt;br /&gt;        {&lt;br /&gt;            if (!System.Threading.Monitor.TryEnter(MyJobLock))&lt;br /&gt;            {&lt;br /&gt;                // other job uses the monitor&lt;br /&gt;                return &amp;quot;Job is still running&amp;quot;;&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            try&lt;br /&gt;            {&lt;br /&gt;                // ... here comes the real code of the job&lt;br /&gt;                return String.Empty;&lt;br /&gt;            }&lt;br /&gt;            catch (Exception exc)&lt;br /&gt;            {&lt;br /&gt;                return string.Format(&amp;quot;Unknown exception caught ({0} {1})&amp;quot;, &lt;br /&gt;                    new object[] { exc.Message, exc.StackTrace });&lt;br /&gt;            }&lt;br /&gt;            finally&lt;br /&gt;            {&lt;br /&gt;                System.Threading.Monitor.Exit(MyJobLock);&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It is &lt;b&gt;very important&lt;/b&gt; to exit the monitor in &lt;b&gt;finally&lt;/b&gt; statement. In other case you can block your jobs until the application is restarted.&lt;/p&gt;&lt;h3 id="specified-user"&gt;Executing &lt;b&gt;EPiServer scheduled job&lt;/b&gt; as a specified user&lt;/h3&gt;&lt;p&gt;If you execute your job manually from the admin mode, your job is executed with all privileges that your user has. However, when your job is run by the scheduler, it does &lt;b&gt;not&lt;/b&gt; have you user access rights. Believe me, it's really easy to forget about it while developing new &lt;b&gt;scheduled job&lt;/b&gt;.&lt;/p&gt;&lt;p&gt;Fortunately it's really easy to log with the specified user name in the EPiServer. the only thing to do is to assign new principals to the &lt;b&gt;PrincipalInfo.CurrentPrincipal&lt;/b&gt; like this:&lt;/p&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 480px"&gt;&lt;code&gt;EPiServer.Security.PrincipalInfo.CurrentPrincipal =&lt;br /&gt;    EPiServer.Security.PrincipalInfo.CreatePrincipal(&amp;quot;username&amp;quot;);&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For more information take a look at &lt;a href="http://tednyberg.com/post/Run-a-scheduled-job-as-a-specific-EPiServer-user.aspx"&gt;Ted Nyberg: Run a &lt;b&gt;scheduled job&lt;/b&gt; as a specific &lt;b&gt;EPiServer&lt;/b&gt; user&lt;/a&gt;.&lt;/p&gt;&lt;h3 id="scheduler-process"&gt;Process within the job thread is executed&lt;/h3&gt;&lt;p&gt;In the development phase of the &lt;b&gt;scheduled job&lt;/b&gt;, most of the time you will start your job manually from the admin mode. In this case the job will be run within the web application process and its thread will have all privileges and rights that your user has.&lt;/p&gt;&lt;p&gt;However, when you set the scheduler to run the job automatically, there is a huge difference. Probably the most important is that you don't have user rights, but I already wrote about it &lt;a rel="no-follow" href="#specified-user"&gt;above&lt;/a&gt;. The other thing that you should consider is that you don't have access to the HttpContext.Current stuff. And probably there are dozens of other things specific for your application.&lt;/p&gt;&lt;h3&gt;Summarizing&lt;/h3&gt;&lt;p&gt;While developing your custom &lt;b&gt;scheduled job for EPiServer&lt;/b&gt; one has to remember that there is a big difference between jobs run manually and jobs run automatically by the scheduler. I hope that after reading this post you won't have any problems with your own &lt;b&gt;scheduled jobs&lt;/b&gt;.&lt;/p&gt;&lt;p&gt;If you know about anything else that should be added to the list above, do not hesitate to comment below.&lt;/p&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7975584389633834518-3199178107625414499?l=marekmusielak.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='text/html' href='http://marekmusielak.blogspot.com/2009/11/custom-scheduled-jobs-in-episerver.html#comment-form' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7975584389633834518/posts/default/3199178107625414499'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7975584389633834518/posts/default/3199178107625414499'/><link rel='alternate' type='text/html' href='http://marekmusielak.blogspot.com/2009/11/custom-scheduled-jobs-in-episerver.html' title='Custom scheduled jobs in EPiServer - advanced tips'/><author><name>Marek Musielak</name><uri>http://www.blogger.com/profile/00718594959358144177</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_UExK-VwSpVM/SbUHbibCtAI/AAAAAAAAAB4/3lVUdtSKG9g/S220/maras3.jpeg'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7975584389633834518.post-7442387919737409030</id><published>2009-07-21T11:41:00.016+02:00</published><updated>2009-09-01T15:39:03.991+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='CSS Sprites'/><category scheme='http://www.blogger.com/atom/ns#' term='Performance Optimization'/><category scheme='http://www.blogger.com/atom/ns#' term='Web applications'/><category scheme='http://www.blogger.com/atom/ns#' term='ASP.NET'/><title type='text'>ASP.NET web site performance</title><content type='html'>I've been working on a site for a tile and wood flooring specialist store recently. That was my first own project on such a big scale. Ok, maybe not so big comparing to projects that I worked on as a Cognifide employee, but still pretty big as a single person project. It was a challenging experience for me as I set up a goal for myself - the site has to load as fast as possible.&lt;br /&gt;&lt;br /&gt;The result can be seen here &lt;a href="http://www.el-art.com.pl" target="_blank" title="el-ART Wronki Czarnkow | plytki panele wykladziny"&gt;el-ART&lt;/a&gt;. The site is in Polish but there is no need for understanding the content so don't worry ;)&lt;br /&gt;&lt;br /&gt;Here is the full story of my work with this site.&lt;br /&gt;&lt;br /&gt;&lt;div class="fullpost"&gt;&lt;br /&gt;Many web developers think that if the site is small then they don't have to worry about the speed of the response time. Unfortunately, they are wrong. Look at the site again (&lt;a href="http://www.el-art.com.pl" target="_blank" title="el-ART Wronki Czarnkow"&gt;el-ART&lt;/a&gt;). There was not much html there but there were about 15 images (including logos and css images), several css files and couple of scripts. 30 or 40 requests per every page load. Then I started optimization - there are dozens of tools which can help you with it - I prefer &lt;b&gt;&lt;a href="http://code.google.com/intl/pl-PL/speed/page-speed/" title="Page Speed" target="_blank"&gt;Page Speed&lt;/a&gt;&lt;/b&gt; Firefox addon. &lt;br /&gt;Lets go step by step with several of it's clues:&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Combine external CSS&lt;/h3&gt;&lt;br /&gt;There are several ways of reducing the number of requests that are sent by the browser. One of them is combining external css files. The easiest way for this is just to create a single file and copy the content of all css files into the new file. Then just replace all previous css links with the one to the new file. However, the solution is not perfect. If you have several files, you can easily keep order in them and it's easy to find what you need. With one file it becomes a challenge.&lt;br /&gt;&lt;br /&gt;But there is an easy solution - you can create your own handler that will combine all css files for you and will return them as a single response. If your site is  an ASP.NET site, you can use &lt;a href="http://geekswithblogs.net/rashid/archive/2007/07/25/Combine-Multiple-JavaScript-and-CSS-Files-and-Remove-Overheads.aspx" target="_blank"&gt;custom resource handler like this one&lt;/a&gt;. If you use other environment, you will find something similar for sure. I used my own one with gzip compression enabled, extended caching and setting header values. It not only decreases the number of the requests, but reduces the size of the response as well.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Combine external Javascript&lt;/h3&gt;&lt;br /&gt;This one is pretty similar to combining css files. However, it has to be extended if you are using Web Extensions - the site will use a lot of .axd files and it's a little bit harder to cope with them. But Damian Kulik created a solution for this which can be found here &lt;a href="http://damikulik.blogspot.com/2009/07/script-and-styles-optimizer-for-aspnet.html" title="scripts and styles optimizer" target="_blank"&gt;http://damikulik.blogspot.com/2009/07/script-and-styles-optimizer-for-aspnet.html&lt;/a&gt;. I'm not going to copy and paste from his blog here so if you want to know more about it then just stay on his blog for a couple of minutes.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Optimize the order of styles and scripts&lt;/h3&gt;&lt;br /&gt;The order of the styles and javascript files is really important. Sometimes it is enough just to move link up or down on the page to decrease the time of loading page significantly. How? It's all about the parallelization downloads. The general rule is: css files should be in the &amp;lt; head /&amp;gt; tag, while js files should be placed as far in the content as it is possible, sometimes even just before the end of the &amp;lt; body /&amp;gt; tag. More about ordering on &lt;a href="http://code.google.com/intl/pl/speed/page-speed/docs/rtt.html#PutStylesBeforeScripts" target="_blank"&gt;google code&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Optimizing images&lt;/h3&gt;&lt;br /&gt;There are 2 main rules here:&lt;br /&gt;- use compressed images - in many cases you can compress your images so they look exactly the same but they are smaller. Page Speed will compress the files for you and display links to compressed images so you can use them on your site.&lt;br /&gt;- do not resize images in html - do not display big images that are resized - the browser will have to download the big file anyway and, what is worse, IE browsers use a pretty bad algorithm for resizing images so they look not nice.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;CSS sprites&lt;/h3&gt;&lt;br /&gt;This one is huge! Probably I should mention about it on the very top of the article but never mind. The whole idea of CSS sprites is described on the &lt;a href="http://css-tricks.com/css-sprites/" title="css tricks" target="_blank"&gt;css tricks site&lt;/a&gt;. Instead of downloading dozens of images and wasting time for requesting all of them, you can download only one image and display parts of it wherever you need them. Take a closer look at my site again (&lt;a href="http://www.el-art.com.pl" target="_blank" title="el-ART Wronki Czarnkow"&gt;el-ART&lt;/a&gt;) - there are 6 links with images in the bottom part of the page. If you would check their properties you would see that all of them has the same &lt;a href="http://www.el-art.com.pl/gallery.jpg" target="_blank"&gt;background - gallery.jpg&lt;/a&gt;, just moved with background-position css property. The image is smaller than 6 separate images and browsers load it several times faster.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Compress response&lt;/h3&gt;&lt;br /&gt;One more thing that could be done is compression of the response html. When you write html then you should format it so it could be easily read and maintain, but when you send it to the user, it should be as small as possible. It really easy to apply it in ASP.NET. The only thing to do is to edit the &lt;b&gt;Application_BeginRequest&lt;/b&gt; method in &lt;b&gt;Global.asax.cs&lt;/b&gt; class as follows:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 480px"&gt;&lt;code&gt;protected void Application_BeginRequest(object sender, EventArgs e)&lt;br /&gt;{&lt;br /&gt;    if (Request.RawUrl.ToLower().EndsWith(&amp;quot;.aspx&amp;quot;))&lt;br /&gt;    {&lt;br /&gt;        string acceptEncoding = Request.Headers[&amp;quot;Accept-Encoding&amp;quot;];&lt;br /&gt;&lt;br /&gt;        if (!string.IsNullOrEmpty(acceptEncoding))&lt;br /&gt;        {&lt;br /&gt;            acceptEncoding = acceptEncoding.ToLower();&lt;br /&gt;&lt;br /&gt;            if (acceptEncoding.Contains(&amp;quot;gzip&amp;quot;))&lt;br /&gt;            {&lt;br /&gt;                Response.Filter = new HtmlCompressStream(Response.Filter, CompressionMode.Compress,&lt;br /&gt;                                                         HtmlCompressStream.CompressionType.GZip);&lt;br /&gt;                Response.AddHeader(&amp;quot;Content-encoding&amp;quot;, &amp;quot;gzip&amp;quot;);&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            else if (acceptEncoding.Contains(&amp;quot;deflate&amp;quot;))&lt;br /&gt;            {&lt;br /&gt;                Response.Filter = new HtmlCompressStream(Response.Filter, CompressionMode.Compress,&lt;br /&gt;                                                         HtmlCompressStream.CompressionType.Deflate);&lt;br /&gt;                Response.AddHeader(&amp;quot;Content-encoding&amp;quot;, &amp;quot;deflate&amp;quot;);&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Decrease loading time - summary&lt;/h3&gt;&lt;br /&gt;All those optimization tips helped me to create the site that is displayed very fast. There are only 12 requests for the content, the size of all of them is about 210KB, and the site loads for me in less than 1 second. Just to show you the difference, BBC site uses 82 requests (556KB) and loads about 10 seconds for me. &lt;br /&gt;&lt;br /&gt;If you have any interesting experience with increasing performance of the ASP.NET sites or want to show how you deal with optimizing issues, do not hesitate to leave your comment below.&lt;br /&gt;&lt;br /&gt;&lt;div class="useful_links"&gt;Useful links:&lt;br /&gt;&lt;a href="http://getfirebug.com/" title="Get Firebug" target="_blank"&gt;Firebug&lt;/a&gt;&lt;br /&gt;&lt;a href="http://code.google.com/intl/en-EN/speed/page-speed/" title="Page Speed" target="_blank"&gt;Page Speed&lt;/a&gt;&lt;br /&gt;&lt;a href="http://www.websiteoptimization.com/services/analyze/" title="web analyzer" target="_blank"&gt;Web Analyzer&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7975584389633834518-7442387919737409030?l=marekmusielak.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='text/html' href='http://marekmusielak.blogspot.com/2009/07/aspnet-web-site-performance.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7975584389633834518/posts/default/7442387919737409030'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7975584389633834518/posts/default/7442387919737409030'/><link rel='alternate' type='text/html' href='http://marekmusielak.blogspot.com/2009/07/aspnet-web-site-performance.html' title='ASP.NET web site performance'/><author><name>Marek Musielak</name><uri>http://www.blogger.com/profile/00718594959358144177</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_UExK-VwSpVM/SbUHbibCtAI/AAAAAAAAAB4/3lVUdtSKG9g/S220/maras3.jpeg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7975584389633834518.post-1585675410919811428</id><published>2009-05-15T14:12:00.008+02:00</published><updated>2009-06-22T11:39:41.670+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='EPiServer'/><category scheme='http://www.blogger.com/atom/ns#' term='Software Development'/><category scheme='http://www.blogger.com/atom/ns#' term='Web applications'/><category scheme='http://www.blogger.com/atom/ns#' term='ASP.NET'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><title type='text'>EPiServer - hiding 'Workflow' and 'Statistics' tabs</title><content type='html'>When you use edit mode of the EPiServer CMS and select any of the pages then you see 5 tabs above it: "View", "Edit", "Version List", "Workflow" and "Statistics". While the first 2 of them are used frequently and the third one ("Version List") is pretty useful for advanced users, the last 2 of them ("Workflow" and "Statistics") are redundant in many cases. And editors don't even need to know that they exist. So lets hide them.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://files.myopera.com/MarasM/files/episerver_workflow_statistics_tabs.PNG"&gt;&lt;br /&gt;&lt;img src="http://files.myopera.com/MarasM/files/episerver_workflow_statistics_tabs.PNG" style="width: 500px" alt="EPiServer workflow and statistics tabs" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="fullpost"&gt;&lt;br /&gt;It can be easily done in admin mode with a tool called '&lt;span style="font-weight:bold;"&gt;Plug-in Manager&lt;/span&gt;' which can be found on the '&lt;span style="font-weight:bold;"&gt;Config&lt;/span&gt;' tab.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://files.myopera.com/MarasM/files/episerver_plug-in_manager.PNG"&gt;&lt;br /&gt;&lt;img src="http://files.myopera.com/MarasM/files/episerver_plug-in_manager.PNG" style="width: 500px" alt="EPiServer Plug-in Manager" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The way of hiding the tabs is slightly different for various versions of EPiServer. I tested it in with 5.1.422.256 and 5.2.375.133 versions. &lt;br /&gt;&lt;br /&gt;Hiding '&lt;span style="font-weight:bold;"&gt;Workflow&lt;/span&gt;' tab:&lt;br /&gt;- in 5.1.422.256 version - choose '&lt;span style="font-weight:bold;"&gt;EPiServer.WorkflowFoundation.UI&lt;/span&gt;' plugin, switch to '&lt;span style="font-weight:bold;"&gt;Overview&lt;/span&gt;' tab and deselect '&lt;span style="font-weight:bold;"&gt;Workflow (EditPanel)&lt;/span&gt;' option in the '&lt;span style="font-weight:bold;"&gt;User Interface&lt;/span&gt;' section.&lt;br /&gt;- in 5.2.375.133 version - choose '&lt;span style="font-weight:bold;"&gt;EPiServer User Interface&lt;/span&gt;'  plugin, switch to '&lt;span style="font-weight:bold;"&gt;Overview&lt;/span&gt;' tab and deselect '&lt;span style="font-weight:bold;"&gt;Workflow (EditPanel)&lt;/span&gt;' option in the '&lt;span style="font-weight:bold;"&gt;User Interface&lt;/span&gt;' section.&lt;br /&gt;&lt;br /&gt;I you need to hide '&lt;span style="font-weight:bold;"&gt;Statistics&lt;/span&gt;' tab (in both versions) choose '&lt;span style="font-weight:bold;"&gt;EPiServer User Interface &lt;/span&gt;' plugin, switch to '&lt;span style="font-weight:bold;"&gt;Overview&lt;/span&gt;' tab and deselect '&lt;span style="font-weight:bold;"&gt;Statistics (EditPanel)&lt;/span&gt;' option in the '&lt;span style="font-weight:bold;"&gt;User Interface&lt;/span&gt;' section.&lt;br /&gt;&lt;br /&gt;It can be slightly different in the version of the EPiServer which you use, but the general rule is the same.&lt;br /&gt;&lt;a rev="vote-for" href="http://dotnetshoutout.com/EPiServer-hiding-Workflow-and-Statistics-tabs" style="float: right"&gt;&lt;img alt="Shout it" src="http://dotnetshoutout.com/image.axd?url=http%3A%2F%2Fmarekmusielak.blogspot.com%2F2009%2F05%2Fepiserver-hiding-workflow-and.html&amp;borderColor=000022&amp;textBackColor=000022&amp;countBackColor=999" style="border:0px"/&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7975584389633834518-1585675410919811428?l=marekmusielak.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='text/html' href='http://marekmusielak.blogspot.com/2009/05/episerver-hiding-workflow-and.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7975584389633834518/posts/default/1585675410919811428'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7975584389633834518/posts/default/1585675410919811428'/><link rel='alternate' type='text/html' href='http://marekmusielak.blogspot.com/2009/05/episerver-hiding-workflow-and.html' title='EPiServer - hiding &apos;Workflow&apos; and &apos;Statistics&apos; tabs'/><author><name>Marek Musielak</name><uri>http://www.blogger.com/profile/00718594959358144177</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_UExK-VwSpVM/SbUHbibCtAI/AAAAAAAAAB4/3lVUdtSKG9g/S220/maras3.jpeg'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7975584389633834518.post-1749471351077186040</id><published>2009-04-15T12:46:00.007+02:00</published><updated>2009-06-22T11:39:33.695+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='EPiServer'/><category scheme='http://www.blogger.com/atom/ns#' term='Software Development'/><category scheme='http://www.blogger.com/atom/ns#' term='Web applications'/><category scheme='http://www.blogger.com/atom/ns#' term='ASP.NET'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><title type='text'>EPiServer - filter page tree view</title><content type='html'>Recently I was working on the EPiServer site that contains dozens of subsections. Every of them is managed by different editor. I was asked to display in page tree view in edit mode only those pages, that editor has editing access. It is reasonable as having dozens of pages (all marked with padlock) makes editing harder or at least less comfortable. Lets do not talk more about the background and take a look at implementation...&lt;br /&gt;&lt;br /&gt;&lt;div class="fullpost"&gt;&lt;br /&gt;In general I think that EPiServer code is a little bit too sealed. I hate all those classes where you can not reimplement part of the code on your own or where you can not overwrite methods. However, this time I was nicely surprised. It was enough to create custom implementation of the &lt;b&gt;EPiServer.PlugIn.PlugInAttribute&lt;/b&gt; and use &lt;b&gt;DataFactory.Instance.FinishedLoadingChildren&lt;/b&gt; event. &lt;br /&gt;&lt;br /&gt;Actually, on the beginning I wanted to use &lt;b&gt;DataFactory.Instance.LoadingChildren&lt;/b&gt; and set &lt;b&gt;RequiredAccess&lt;/b&gt; to &lt;b&gt;AccessLevel.Edit&lt;/b&gt; (see below), but it didn't work for me. If you know why, please comment so I can learn something :)&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 480px;"&gt;&lt;code&gt;private static void Instance_LoadingChildren(object sender, ChildrenEventArgs e)&lt;br /&gt;{&lt;br /&gt;    e.RequiredAccess = AccessLevel.Edit;&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;First I created my class inheriting from &lt;b&gt;EPiServer.PlugIn.PlugInAttribute&lt;/b&gt; and implemented Start method so it adds new &lt;b&gt;ChildrenEventHandler&lt;/b&gt; to the &lt;b&gt;DataFactory.Instance.FinishedLoadingChildren&lt;/b&gt; event:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 480px;"&gt;&lt;code&gt;using System.Web;&lt;br /&gt;using EPiServer;&lt;br /&gt;using EPiServer.Configuration;&lt;br /&gt;using EPiServer.Core;&lt;br /&gt;using EPiServer.PlugIn;&lt;br /&gt;using EPiServer.Security;&lt;br /&gt;&lt;br /&gt;namespace Maras.TreeAdapter&lt;br /&gt;{&lt;br /&gt;    public class TreeAdapter : PlugInAttribute&lt;br /&gt;    {&lt;br /&gt;        public static void Start()&lt;br /&gt;        {&lt;br /&gt;            DataFactory.Instance.FinishedLoadingChildren += Instance_FinishedLoadingChildren;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        private static void Instance_FinishedLoadingChildren(object sender, ChildrenEventArgs e)&lt;br /&gt;        {&lt;br /&gt;            if (IsAccessCheckNecessary(e))&lt;br /&gt;            {&lt;br /&gt;                int index = 0;&lt;br /&gt;                while (index &amp;lt; e.Children.Count)&lt;br /&gt;                {&lt;br /&gt;                    if (e.Children[index].ACL.HasAccess(PrincipalInfo.Current.Principal, AccessLevel.Edit))&lt;br /&gt;                    {&lt;br /&gt;                        index++;&lt;br /&gt;                    }&lt;br /&gt;                    else&lt;br /&gt;                    {&lt;br /&gt;                        e.Children.RemoveAt(index);&lt;br /&gt;                    }&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;As you can see, I check whether the access control is necessary and then I remove all the children that current user is not allowed to edit. Pretty important is the &lt;b&gt;IsAccessCheckNecessary&lt;/b&gt; method. My implementation assumes that I want to filter pages in edit mode and for the children of the start page only, so it looks like this:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 480px;"&gt;&lt;code&gt;private static readonly string UI_URL = Settings.Instance.UIUrl.ToString().ToLower();&lt;br /&gt;&lt;br /&gt;private static bool IsAccessCheckNecessary(ChildrenEventArgs e)&lt;br /&gt;{&lt;br /&gt;    return e.PageLink == PageReference.StartPage &amp;amp;&amp;amp;&lt;br /&gt;           HttpContext.Current.Request.Url.ToString().ToLower().StartsWith(UI_URL);&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;If you know any easier way of doing what I needed, feel free to comment below.&lt;br /&gt;&lt;br /&gt;&lt;p&gt;Source code can be downloaded from here: &lt;a href="http://marasm.pl/resources/resource.php?TreeAdapter.zip" target="_blank"&gt;source code&lt;/a&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;a rev="vote-for" href="http://dotnetshoutout.com/EPiServer-online-users-list" style="float: right" target="_blank"&gt;&lt;img alt="Shout it" src="http://dotnetshoutout.com/image.axd?url=http%3A%2F%2Fmarekmusielak.blogspot.com%2F2009%2F04%2Fepiserver-filter-page-tree-view.html&amp;borderColor=000022&amp;textBackColor=000022&amp;countBackColor=999" style="border:0px"/&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7975584389633834518-1749471351077186040?l=marekmusielak.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='text/html' href='http://marekmusielak.blogspot.com/2009/04/episerver-filter-page-tree-view.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7975584389633834518/posts/default/1749471351077186040'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7975584389633834518/posts/default/1749471351077186040'/><link rel='alternate' type='text/html' href='http://marekmusielak.blogspot.com/2009/04/episerver-filter-page-tree-view.html' title='EPiServer - filter page tree view'/><author><name>Marek Musielak</name><uri>http://www.blogger.com/profile/00718594959358144177</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_UExK-VwSpVM/SbUHbibCtAI/AAAAAAAAAB4/3lVUdtSKG9g/S220/maras3.jpeg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7975584389633834518.post-4526682463931854299</id><published>2009-03-12T13:13:00.013+01:00</published><updated>2009-06-22T11:39:05.321+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='EPiServer'/><category scheme='http://www.blogger.com/atom/ns#' term='Software Development'/><category scheme='http://www.blogger.com/atom/ns#' term='Web applications'/><category scheme='http://www.blogger.com/atom/ns#' term='ASP.NET'/><category scheme='http://www.blogger.com/atom/ns#' term='HtmlEditor'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><title type='text'>EPiServer rich text editor</title><content type='html'>PropertyLongString and XHTML String are probably the most frequently used types of&amp;nbsp;properties in&amp;nbsp;all EPiServer sites. They are used on&amp;nbsp;nearly all content pages. However I&amp;nbsp;wanted to&amp;nbsp;use the editor control on&amp;nbsp;the standard aspx page. I&amp;nbsp;thought it&amp;nbsp;would be really easy - just put HtmlEditor control on&amp;nbsp;my&amp;nbsp;page and voila. It&amp;nbsp;was a&amp;nbsp;little bit more complex but finally I&amp;nbsp;managed to&amp;nbsp;do&amp;nbsp;what I&amp;nbsp;needed.&lt;br /&gt;&lt;br /&gt;&lt;div class="fullpost"&gt;&lt;br /&gt;Lets start with an&amp;nbsp;aspx page. What we need to&amp;nbsp;do&amp;nbsp;is&amp;nbsp;to&amp;nbsp;put &amp;lt;EPiServer:Property /&amp;gt; on&amp;nbsp;the page and set &lt;b&gt;EditMode&lt;/b&gt; attribute to&amp;nbsp;true. I&amp;nbsp;set &lt;b&gt;Width&lt;/b&gt; and &lt;b&gt;Height&lt;/b&gt; attributes as&amp;nbsp;well but this is&amp;nbsp;optional. Remember not to&amp;nbsp;forget about setting &lt;b&gt;runat="server"&lt;/b&gt; property. I&amp;nbsp;also put message panel and button on&amp;nbsp;the page so&amp;nbsp;I&amp;nbsp;can show how to&amp;nbsp;access the text put into the wysiwyg editor. Here is&amp;nbsp;my&amp;nbsp;aspx page:&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 480px;"&gt;&lt;code&gt;&amp;lt;%@ Page Language=&amp;quot;C#&amp;quot; AutoEventWireup=&amp;quot;True&amp;quot; CodeBehind=&amp;quot;HtmlEditorPage.aspx.cs&amp;quot; Inherits=&amp;quot;MyProject.HtmlEditorPage&amp;quot; %&amp;gt;&lt;br /&gt;&amp;lt;!DOCTYPE html PUBLIC &amp;quot;-//W3C//DTD XHTML 1.0 Transitional//EN&amp;quot; &amp;quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd&amp;quot;&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;html xmlns=&amp;quot;http://www.w3.org/1999/xhtml&amp;quot; &amp;gt;&lt;br /&gt;    &amp;lt;head runat=&amp;quot;server&amp;quot;&amp;gt;&lt;br /&gt;        &amp;lt;title&amp;gt;Html Editor Page&amp;lt;/title&amp;gt;&lt;br /&gt;        &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; type=&amp;quot;text/css&amp;quot; href=&amp;quot;/App_Themes/Default/Styles/system.css&amp;quot; /&amp;gt;&lt;br /&gt;    &amp;lt;/head&amp;gt;&lt;br /&gt;    &amp;lt;body&amp;gt;&lt;br /&gt;        &amp;lt;form runat=&amp;quot;server&amp;quot; method=&amp;quot;post&amp;quot;&amp;gt;&lt;br /&gt;            &amp;lt;asp:Panel ID=&amp;quot;pnlMessage&amp;quot; runat=&amp;quot;server&amp;quot; Visible=&amp;quot;false&amp;quot; CssClass=&amp;quot;EP-systemMessage&amp;quot;&amp;gt;&lt;br /&gt;                &amp;lt;asp:Literal ID=&amp;quot;litMessage&amp;quot; runat=&amp;quot;server&amp;quot; /&amp;gt;&lt;br /&gt;            &amp;lt;/asp:Panel&amp;gt;&lt;br /&gt;            &amp;lt;EPiServer:Property id=&amp;quot;myHtmlEditor&amp;quot; Width=&amp;quot;500&amp;quot; height=&amp;quot;350&amp;quot; EditMode=&amp;quot;true&amp;quot; runat=&amp;quot;server&amp;quot; /&amp;gt;&lt;br /&gt;            &amp;lt;asp:Button ID=&amp;quot;btnSend&amp;quot; runat=&amp;quot;server&amp;quot; Text=&amp;quot;Send&amp;quot; OnClick=&amp;quot;btnSend_Click&amp;quot; /&amp;gt;&lt;br /&gt;        &amp;lt;/form&amp;gt;&lt;br /&gt;    &amp;lt;/body&amp;gt;&lt;br /&gt;&amp;lt;/html&amp;gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Now it's time to write something in&amp;nbsp;code behind. First we need to register scripts that are required by&amp;nbsp;the editor. I&amp;nbsp;used &lt;b&gt;ResolveUrlFromUI&lt;/b&gt; and &lt;b&gt;ResolveUrlFromUtil&lt;/b&gt; methods but if&amp;nbsp;you know your UI and Util directories then you can put whole paths without using those methods. Now lets create new &lt;b&gt;PropertyLongString&lt;/b&gt;. We&amp;nbsp;can select chosen editor options for the property or&amp;nbsp;just select &lt;b&gt;EditorToolOption.All&lt;/b&gt;. If&amp;nbsp;you don't want to&amp;nbsp;use any of&amp;nbsp;them you don't have to set it at all. Next set &lt;b&gt;Name&lt;/b&gt; of&amp;nbsp;your property and assign it&amp;nbsp;to&amp;nbsp;the &lt;b&gt;InnerProperty&lt;/b&gt; of&amp;nbsp;the editor. That's all .You have pretty HtmlEditor on&amp;nbsp;your page. &lt;br /&gt;&lt;br /&gt;The last thing is accessing the data from the editor. I&amp;nbsp;tried several ways of&amp;nbsp;accessing it&amp;nbsp;but only one worked for me: retrieving content of&amp;nbsp;the property by&amp;nbsp;using value from &lt;b&gt;Request.Form&lt;/b&gt;. If&amp;nbsp;you know any smarter way, let me know. So here is&amp;nbsp;code behind (source code can be downloaded from here: &lt;a href="http://marasm.pl/resources/resource.php?HtmlEditor.zip" target="_blank"&gt;source code&lt;/a&gt;):&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 480px"&gt;&lt;code&gt;#region&lt;br /&gt;&lt;br /&gt;using System;&lt;br /&gt;using EPiServer;&lt;br /&gt;using EPiServer.Core;&lt;br /&gt;using EPiServer.Editor;&lt;br /&gt;&lt;br /&gt;#endregion&lt;br /&gt;&lt;br /&gt;namespace MyProject&lt;br /&gt;{&lt;br /&gt;    public partial class HtmlEditorPage : SimplePage&lt;br /&gt;    {&lt;br /&gt;        protected override void OnInit(EventArgs e)&lt;br /&gt;        {&lt;br /&gt;            base.OnInit(e);&lt;br /&gt;&lt;br /&gt;            if (!Page.ClientScript.IsClientScriptIncludeRegistered(&amp;quot;system.js&amp;quot;))&lt;br /&gt;            {&lt;br /&gt;                Page.ClientScript.RegisterClientScriptInclude(&amp;quot;system.js&amp;quot;,&lt;br /&gt;                                                              ((PageBase) Page).ResolveUrlFromUI(&amp;quot;javascript/system.js&amp;quot;));&lt;br /&gt;                Page.ClientScript.RegisterClientScriptInclude(&amp;quot;system.aspx&amp;quot;,&lt;br /&gt;                                                              ((PageBase) Page).ResolveUrlFromUI(&amp;quot;javascript/system.aspx&amp;quot;));&lt;br /&gt;                Page.ClientScript.RegisterClientScriptInclude(&amp;quot;episerverscriptmanager.js&amp;quot;,&lt;br /&gt;                                                              ((PageBase) Page).ResolveUrlFromUtil(&lt;br /&gt;                                                                  &amp;quot;javascript/EPiServerScriptManager.js&amp;quot;));&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            PropertyLongString longString = new PropertyLongString();&lt;br /&gt;&lt;br /&gt;            // select chosen options or use EditorToolOption.All&lt;br /&gt;            longString.EditorToolOptions = EditorToolOption.Bold &amp;#124; EditorToolOption.Italic &amp;#124; EditorToolOption.Underline;&lt;br /&gt;            longString.Name = &amp;quot;MyLongStringProperty&amp;quot;;&lt;br /&gt;            myHtmlEditor.InnerProperty = longString;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        protected void btnSend_Click(object sender, EventArgs e)&lt;br /&gt;        {&lt;br /&gt;            // yeah this is the only way of accessing what you put in the editor&lt;br /&gt;            litMessage.Text = Request.Form[myHtmlEditor.ID + &amp;quot;$ctl00$&amp;quot; + myHtmlEditor.PropertyName];&lt;br /&gt;            pnlMessage.Visible = true;&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;a rev="vote-for" href="http://dotnetshoutout.com/EPiServer-online-users-list" style="float: right"&gt;&lt;img alt="Shout it" src="http://dotnetshoutout.com/image.axd?url=http%3A%2F%2Fmarekmusielak.blogspot.com%2F2009%2F02%2Fepiserver-online-users-list.html&amp;borderColor=000022&amp;textBackColor=000022&amp;countBackColor=999" style="border:0px"/&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7975584389633834518-4526682463931854299?l=marekmusielak.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='text/html' href='http://marekmusielak.blogspot.com/2009/03/episerver-rich-text-editor.html#comment-form' title='11 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7975584389633834518/posts/default/4526682463931854299'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7975584389633834518/posts/default/4526682463931854299'/><link rel='alternate' type='text/html' href='http://marekmusielak.blogspot.com/2009/03/episerver-rich-text-editor.html' title='EPiServer rich text editor'/><author><name>Marek Musielak</name><uri>http://www.blogger.com/profile/00718594959358144177</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_UExK-VwSpVM/SbUHbibCtAI/AAAAAAAAAB4/3lVUdtSKG9g/S220/maras3.jpeg'/></author><thr:total>11</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7975584389633834518.post-6264186236358103258</id><published>2009-03-06T10:25:00.020+01:00</published><updated>2009-06-22T11:38:57.261+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='JavaScript'/><category scheme='http://www.blogger.com/atom/ns#' term='Web applications'/><category scheme='http://www.blogger.com/atom/ns#' term='Misc'/><title type='text'>TheBugger - to see without looking</title><content type='html'>In my view, one of the most important developer skill is to see without looking. Let me explain what I mean. Assume that we have a piece of code that have some complex functionality. You see it for the very first time and you want to know how it works and what it is used for. You could debug the code of course. However it would be perfect if you were able to &lt;span style="font-weight:bold;"&gt;see without looking&lt;/span&gt; - look at the code, read it and know what it is responsible for.&lt;br /&gt;&lt;br /&gt;As in all other cases training makes a master. But still, doing the same thing to many times makes it boring and even annoying. That's why I do really like to practice my skills in some other ways. One of them is minesweeper. Yeah, the same old minesweeper that you had in your first MS Windows. But the rules of the game are to easy for you. So lets make them a little bit harder.&lt;br /&gt;&lt;br /&gt;&lt;div class="fullpost"&gt;&lt;br /&gt;It's really easy to find all bugs when you have a possibility of marking bugs. But what when you can not tick their location? You have to remember their placement, to see where they are without looking at them. First time is not so effortless. Believe me, the second one is much simpler. And tenth is even easier than you expected. The same is with code reading. So don't give up - practice.&lt;br /&gt;&lt;br /&gt;Are you able to debug the field without a possibility of marking bugs? Sure you are. If you weren't, you wouldn't be here ;)&lt;br /&gt;&lt;br /&gt;&lt;div style="align:center; text-align: center; width: 480px;"&gt;&lt;br /&gt;    &lt;iframe src="http://marasm.pl/pages/debugger.html" style="margin 0 auto; border: 0; width: 352px; height: 352px; border-style: none" frameborder="0" scrolling="no"&gt;&lt;br /&gt;    &lt;/iframe&gt;&lt;br /&gt;&lt;a href="javascript:location.reload(true)" title="TheBugger by Marek Musielak"&gt;Start again&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;So how fast have you debugged the field?&lt;br /&gt;&lt;br /&gt;Any ideas how to make TheBugger more challenging? Leave your comment and I will consider it for sure.&lt;br /&gt;&lt;br /&gt;And what are your ways of training your mind?&lt;br /&gt;&lt;br /&gt;&lt;a rev="vote-for" href="http://dotnetshoutout.com/TheBugger-to-see-without-looking" style="float: right"&gt;&lt;img alt="Shout it" src="http://dotnetshoutout.com/image.axd?url=http%3A%2F%2Fmarekmusielak.blogspot.com%2F2009%2F03%2Fthebugger-to-see-without-looking.html&amp;borderColor=000022&amp;textBackColor=000022&amp;countBackColor=999" style="border:0px"/&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7975584389633834518-6264186236358103258?l=marekmusielak.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='text/html' href='http://marekmusielak.blogspot.com/2009/03/thebugger-to-see-without-looking.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7975584389633834518/posts/default/6264186236358103258'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7975584389633834518/posts/default/6264186236358103258'/><link rel='alternate' type='text/html' href='http://marekmusielak.blogspot.com/2009/03/thebugger-to-see-without-looking.html' title='TheBugger - to see without looking'/><author><name>Marek Musielak</name><uri>http://www.blogger.com/profile/00718594959358144177</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_UExK-VwSpVM/SbUHbibCtAI/AAAAAAAAAB4/3lVUdtSKG9g/S220/maras3.jpeg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7975584389633834518.post-2612452507559566622</id><published>2009-03-03T15:13:00.007+01:00</published><updated>2009-06-22T11:38:49.901+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Software Development'/><category scheme='http://www.blogger.com/atom/ns#' term='Configuration'/><category scheme='http://www.blogger.com/atom/ns#' term='Web applications'/><category scheme='http://www.blogger.com/atom/ns#' term='ASP.NET'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><title type='text'>The easiest way of editing web.config dynamically</title><content type='html'>Some time ago I needed to update web.config programatically from my web site in order to add my own key to &lt;span style="font-weight:bold;"&gt;&amp;lt;appSettings /&amp;gt;&lt;/span&gt; tag. I spent a lot of time looking for a solution on the Internet. I wasted a lot of time trying to reuse existing code from the Web but nothing worked for me. I most cases they thrown an exception, some didn't even compile. Finally I found one on my own. &lt;br /&gt;&lt;br /&gt;&lt;div class="fullpost"&gt;&lt;br /&gt;First lets read existing configuration and value of the settings element:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 480px"&gt;&lt;code&gt;protected void Page_Load(object sender, System.EventArgs e)&lt;br /&gt;{&lt;br /&gt;    if (!IsPostBack)&lt;br /&gt;    {&lt;br /&gt;        System.Configuration.Configuration config = System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration(&amp;quot;~&amp;quot;);&lt;br /&gt;&lt;br /&gt;        System.Configuration.KeyValueConfigurationElement setting = config.AppSettings.Settings[&amp;quot;MyValue&amp;quot;];&lt;br /&gt;&lt;br /&gt;        if (null != setting)&lt;br /&gt;        {&lt;br /&gt;            // lets do something with the value - displaying it in a textbox is an ides&lt;br /&gt;            textboxValue.Text = setting.Value;&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;We can use &lt;span style="font-weight:bold;"&gt;web.config&lt;/span&gt; file from some other directory if we use its name instead of '&lt;span style="font-weight:bold;"&gt;~&lt;/span&gt;' in &lt;span style="font-weight:bold;"&gt;WebConfigurationManager.OpenWebConfiguration("~")&lt;/span&gt; method.&lt;br /&gt;&lt;br /&gt;We've already read the value and displayed it in textbox so we are ready to save updated value (or add new settings element if it hasn't been saved yet).&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 480px"&gt;&lt;code&gt;protected void btnSave_Click(object sender, System.EventArgs e)&lt;br /&gt;{&lt;br /&gt;    try&lt;br /&gt;    {&lt;br /&gt;        System.Configuration.Configuration config = System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration(&amp;quot;~&amp;quot;);&lt;br /&gt;&lt;br /&gt;        System.Configuration.KeyValueConfigurationElement setting = config.AppSettings.Settings[&amp;quot;MyValue&amp;quot;];&lt;br /&gt;&lt;br /&gt;        if (null != setting)&lt;br /&gt;        {&lt;br /&gt;            config.AppSettings.Settings[&amp;quot;MyValue&amp;quot;].Value = textboxValue.Text;&lt;br /&gt;        }&lt;br /&gt;        else&lt;br /&gt;        {&lt;br /&gt;            config.AppSettings.Settings.Add(&amp;quot;MyValue&amp;quot;, textboxValue.Text);&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        config.Save();&lt;br /&gt;        literalMessage.Text = &amp;quot;New value saved. Application will be restarted automatically.&amp;quot;;&lt;br /&gt;    }&lt;br /&gt;    catch (System.Exception exc)&lt;br /&gt;    {&lt;br /&gt;        literalMessage.Text = (exc.Message + &amp;quot;&amp;lt;br /&amp;gt;&amp;quot; + exc.StackTrace).Replace(&amp;quot;\n&amp;quot;, &amp;quot;&amp;lt;br /&amp;gt;&amp;quot;);&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And that's all. The solution is short so the post is short ;) Any comments? Do not hesitate to write them below.&lt;br /&gt;&lt;br /&gt;&lt;a rev="vote-for" href="http://dotnetshoutout.com/The-easiest-way-of-editing-webconfig-dynamically" style="float: right"&gt;&lt;img alt="Shout it" src="http://dotnetshoutout.com/image.axd?url=http%3A%2F%2Fmarekmusielak.blogspot.com%2F2009%2F03%2Feasiest-way-of-editing-webconfig.html&amp;borderColor=000022&amp;textBackColor=000022&amp;countBackColor=999" style="border:0px"/&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7975584389633834518-2612452507559566622?l=marekmusielak.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='text/html' href='http://marekmusielak.blogspot.com/2009/03/easiest-way-of-editing-webconfig.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7975584389633834518/posts/default/2612452507559566622'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7975584389633834518/posts/default/2612452507559566622'/><link rel='alternate' type='text/html' href='http://marekmusielak.blogspot.com/2009/03/easiest-way-of-editing-webconfig.html' title='The easiest way of editing web.config dynamically'/><author><name>Marek Musielak</name><uri>http://www.blogger.com/profile/00718594959358144177</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_UExK-VwSpVM/SbUHbibCtAI/AAAAAAAAAB4/3lVUdtSKG9g/S220/maras3.jpeg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7975584389633834518.post-3674819250460078171</id><published>2009-02-27T10:35:00.019+01:00</published><updated>2009-06-22T11:38:40.839+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='EPiServer'/><category scheme='http://www.blogger.com/atom/ns#' term='Software Development'/><category scheme='http://www.blogger.com/atom/ns#' term='Web applications'/><category scheme='http://www.blogger.com/atom/ns#' term='ASP.NET'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><title type='text'>EPiServer online users list</title><content type='html'>Working with the application which is edited by dozens of people makes the application harder to maintain. Have you ever thought about the people that are doing their job editing the site before you start deploy the new version of the application? I hadn't used to... until I got a lot of complaints from editors that lost their job. "I wanted to save what I'd wrote but I got 'service unavailable' message instead" they repined.&lt;br /&gt;&lt;br /&gt;It made me look for the solution for this problem. Sure, I could do the new builds during the nights but what if the site is hosted by external company and you don't have access to the servers? You won't be able to force anyone from that company to stay in the office over night just to deploy the new version of the site. &lt;br /&gt;&lt;br /&gt;The other solution would be to send emails to all editors saying that server will be restarted so they can can save all their work. This idea has only only small drawback - it assumes that editors check their emails frequently. I've already spent enough time maintaining sites to know that it barely happens.&lt;br /&gt;&lt;br /&gt;&lt;div class="fullpost"&gt;Finally I found a solution that works fine for me. I use &lt;span style="font-style:italic;"&gt;System.Web.Security.Membership&lt;/span&gt; to get all users that are logged in and deploy the new builds when no one but me is editing the site. &lt;br /&gt;&lt;br /&gt;So lets go step by step with this solution. Firstly I created the web page that would be used to diplay active users. I put the repater on it:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 480px"&gt;&lt;code&gt;&amp;lt;asp:Repeater ID=&amp;quot;rptUsers&amp;quot; runat=&amp;quot;server&amp;quot;&amp;gt;&lt;br /&gt;    &amp;lt;HeaderTemplate&amp;gt;&amp;lt;table&amp;gt;&amp;lt;/HeaderTemplate&amp;gt;&lt;br /&gt;    &amp;lt;ItemTemplate&amp;gt;&lt;br /&gt;        &amp;lt;tr&amp;gt;&lt;br /&gt;            &amp;lt;td&amp;gt;&amp;lt;%# ((MembershipUser)Container.DataItem).UserName %&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;            &amp;lt;td&amp;gt;&amp;lt;%# ((MembershipUser)Container.DataItem).LastActivityDate.ToString(&amp;quot;HH:mm:ss&amp;quot;)%&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;        &amp;lt;/tr&amp;gt;&lt;br /&gt;    &amp;lt;/ItemTemplate&amp;gt;&lt;br /&gt;    &amp;lt;FooterTemplate&amp;gt;&amp;lt;/table&amp;gt;&amp;lt;/FooterTemplate&amp;gt;&lt;br /&gt;&amp;lt;/asp:Repeater&amp;gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;and overrode OnLoad method:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 480px"&gt;&lt;code&gt;public partial class OnlineUsers : SimplePage&lt;br /&gt;{&lt;br /&gt;    private static DateTime applicationStartTime;&lt;br /&gt;&lt;br /&gt;    public static DateTime ApplicationStartTime&lt;br /&gt;    {&lt;br /&gt;        set { applicationStartTime = value; }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    protected override void OnLoad(EventArgs e)&lt;br /&gt;    {&lt;br /&gt;        if (!User.Identity.IsAuthenticated)&lt;br /&gt;        {&lt;br /&gt;            AccessDenied();&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        List&amp;lt;MembershipUser&amp;gt; users = new List&amp;lt;MembershipUser&amp;gt;();&lt;br /&gt;&lt;br /&gt;        foreach (MembershipUser user in Membership.GetAllUsers())&lt;br /&gt;        {&lt;br /&gt;            if (user.IsOnline &amp;amp;&amp;amp; user.LastActivityDate &amp;gt; applicationStartTime)&lt;br /&gt;            {&lt;br /&gt;                users.Add(user);&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        users.Sort(delegate(MembershipUser u1, MembershipUser u2)&lt;br /&gt;                       { return u2.LastActivityDate.CompareTo(u1.LastActivityDate); });&lt;br /&gt;&lt;br /&gt;        rptUsers.DataSource = users;&lt;br /&gt;        rptUsers.DataBind();&lt;br /&gt;&lt;br /&gt;        base.OnLoad(e);&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;On the beginning I though it would be enough to check &lt;span style="font-style:italic;"&gt;MembershipUser.IsOnline&lt;/span&gt; property, but it turned out that when application is started all users are treated as online, cause &lt;span style="font-style:italic;"&gt;MembershipUser.LastActivityDate&lt;/span&gt; is set to the time when application started. That's why I added applicationStartTime field that I set in Global.asax.cs file in &lt;span style="font-style:italic;"&gt;Application_Start&lt;/span&gt; method and compare it with &lt;span style="font-style:italic;"&gt;LastActivityDate&lt;/span&gt; property:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 480px"&gt;&lt;code&gt;public class Global : EPiServer.Global&lt;br /&gt;{&lt;br /&gt;    protected void Application_Start(Object sender, EventArgs e)&lt;br /&gt;    {&lt;br /&gt;        OnlineUsers.ApplicationStartTime = DateTime.Now;&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;And that's all. I can check whether my build would interrupt anyone's work. Remember, a happy customer is your walking advertisement. Lets try not to upset them unnecessarily ;)&lt;br /&gt;&lt;br /&gt;&lt;p&gt;Source code can be downloaded from here: &lt;a href="http://marasm.pl/resources/resource.php?OnlineUsers.zip" target="_blank"&gt;source code&lt;/a&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;a rev="vote-for" href="http://dotnetshoutout.com/EPiServer-online-users-list" style="float: right" target="_blank"&gt;&lt;img alt="Shout it" src="http://dotnetshoutout.com/image.axd?url=http%3A%2F%2Fmarekmusielak.blogspot.com%2F2009%2F02%2Fepiserver-online-users-list.html&amp;borderColor=000022&amp;textBackColor=000022&amp;countBackColor=999" style="border:0px"/&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7975584389633834518-3674819250460078171?l=marekmusielak.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='text/html' href='http://marekmusielak.blogspot.com/2009/02/episerver-online-users-list.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7975584389633834518/posts/default/3674819250460078171'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7975584389633834518/posts/default/3674819250460078171'/><link rel='alternate' type='text/html' href='http://marekmusielak.blogspot.com/2009/02/episerver-online-users-list.html' title='EPiServer online users list'/><author><name>Marek Musielak</name><uri>http://www.blogger.com/profile/00718594959358144177</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_UExK-VwSpVM/SbUHbibCtAI/AAAAAAAAAB4/3lVUdtSKG9g/S220/maras3.jpeg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7975584389633834518.post-5701542570719458491</id><published>2009-02-06T13:58:00.014+01:00</published><updated>2009-08-24T12:11:35.755+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='EPiServer'/><category scheme='http://www.blogger.com/atom/ns#' term='Software Development'/><category scheme='http://www.blogger.com/atom/ns#' term='Web applications'/><category scheme='http://www.blogger.com/atom/ns#' term='ASP.NET'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><category scheme='http://www.blogger.com/atom/ns#' term='Custom Property'/><title type='text'>Extended Image Url Property</title><content type='html'>&lt;p&gt;Some time ago I got a request from our customer saying "We have so many images in EPiServer that we frequently choose wrong one. Could you provide a functionality so images are displayed in edit mode?".&lt;/p&gt;&lt;p&gt;Sure we can. It's really easy to create own custom propery or extend existing one in EPiServer 5. Some basics about it can be found &lt;a href="http://world.episerver.com/en/Articles/Items/Creating-custom-properties-in-EPiServer-CMS-5/" target="_blank"&gt;here on world.episerver.com&lt;/a&gt;. In this case I extended PropertyImageUrl property. &lt;/p&gt;&lt;p&gt;First thing that I did was overriding &lt;b&gt;CreatePropertyControl&lt;/b&gt; method in my class:&lt;/p&gt;&lt;div class="fullpost"&gt;&lt;a id="ae_code_4" href="#expand" onclick="document.getElementById('div_code_4').style.display = ''; this.style.display = 'none'; document.getElementById('ac_code_4').style.display = ''; return false;" style="display: none;"&gt;Show code &amp;gt;&lt;/a&gt;&lt;a id="ac_code_4" href="#collapse" onclick="document.getElementById('div_code_4').style.display = 'none'; this.style.display = 'none'; document.getElementById('ae_code_4').style.display = ''; return false;"&gt;&amp;lt; Hide code &lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;div id="div_code_4"&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 480px;"&gt;&lt;code&gt;[PageDefinitionTypePlugIn(DisplayName = &amp;quot;URL to Image (Extended)&amp;quot;)]&lt;br /&gt;public class PropertyImageUrlExtended : PropertyImageUrl&lt;br /&gt;{&lt;br /&gt;    public override IPropertyControl CreatePropertyControl()&lt;br /&gt;    {&lt;br /&gt;        return new PropertyImageUrlExtendedControl();&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then I created &lt;b&gt;PropertyImageUrlExtendedControl&lt;/b&gt; (overriding PropertyUrlControl class of course). The only thing that has to be done here is custom implementation of &lt;b&gt;SetupEditControls&lt;/b&gt; method:&lt;/p&gt;&lt;a id="ae_code_5" href="#expand" onclick="document.getElementById('div_code_5').style.display = ''; this.style.display = 'none'; document.getElementById('ac_code_5').style.display = ''; return false;" style="display: none;"&gt;Show code &amp;gt;&lt;/a&gt;&lt;a id="ac_code_5" href="#collapse" onclick="document.getElementById('div_code_5').style.display = 'none'; this.style.display = 'none'; document.getElementById('ae_code_5').style.display = ''; return false;"&gt;&amp;lt; Hide code &lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;div id="div_code_5"&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 480px;"&gt;&lt;code&gt;protected override void SetupEditControls()&lt;br /&gt;{&lt;br /&gt;    base.SetupEditControls();&lt;br /&gt;&lt;br /&gt;    Image image = new Image ();&lt;br /&gt;    image.Style.Add(&amp;quot;clear&amp;quot;, &amp;quot;both&amp;quot;);&lt;br /&gt;    image.Style.Add(&amp;quot;float&amp;quot;, &amp;quot;left&amp;quot;);&lt;br /&gt;    image.ImageUrl = PropertyUrl.Value as string;&lt;br /&gt;&lt;br /&gt;    if (String.IsNullOrEmpty(image.ImageUrl))&lt;br /&gt;    {&lt;br /&gt;        image.Style.Add(&amp;quot;display&amp;quot;, &amp;quot;none&amp;quot;);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    Controls.Add(image);&lt;br /&gt;&lt;br /&gt;    foreach (Control control in Controls)&lt;br /&gt;    {&lt;br /&gt;        if (control is TextBox)&lt;br /&gt;        {&lt;br /&gt;            TextBox tb = (TextBox) control;&lt;br /&gt;            tb.Attributes.Add(&lt;br /&gt;                // clean source or set the new value; hide or display image&lt;br /&gt;                &amp;quot;OnChange&amp;quot;,&lt;br /&gt;                @&amp;quot;this.parentNode.getElementsByTagName('IMG')[0].src = this.value;&lt;br /&gt;                if (this.value != null) this.parentNode.getElementsByTagName('IMG')[0].style.display = 'block';&lt;br /&gt;                else this.parentNode.getElementsByTagName('IMG')[0].style.display = 'none';&amp;quot;);&lt;br /&gt;            break;&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;First I execute &lt;b&gt;base.SetupEditControls()&lt;/b&gt; in order to create label, textbox and file picker. Then I add image, set its source and hide it if the source is empty. The second part of this method allows to update source of the image every time the content of the textbox holding chosen image is changed. It's a short javascript updating the source of the image and hiding or showing the image if the source is empty or not respectively. Result looks like this:&lt;a style="margin-top: 10px; margin-bottom: 10px; display: block;" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_UExK-VwSpVM/SYw1SvwmIXI/AAAAAAAAABQ/4SAlzwB9Qeo/s1600-h/urlExtended.PNG"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 118px;" src="http://3.bp.blogspot.com/_UExK-VwSpVM/SYw1SvwmIXI/AAAAAAAAABQ/4SAlzwB9Qeo/s320/urlExtended.PNG" border="0" alt="Extended Image Url Property" id="BLOGGER_PHOTO_ID_5299669457682440562" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Source code can be downloaded from here: &lt;a href="http://marasm.pl/resources/resource.php?PropertyImageUrlExtended.zip" target="_blank"&gt;source code&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7975584389633834518-5701542570719458491?l=marekmusielak.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='text/html' href='http://marekmusielak.blogspot.com/2009/02/extended-image-url-property.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7975584389633834518/posts/default/5701542570719458491'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7975584389633834518/posts/default/5701542570719458491'/><link rel='alternate' type='text/html' href='http://marekmusielak.blogspot.com/2009/02/extended-image-url-property.html' title='Extended Image Url Property'/><author><name>Marek Musielak</name><uri>http://www.blogger.com/profile/00718594959358144177</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_UExK-VwSpVM/SbUHbibCtAI/AAAAAAAAAB4/3lVUdtSKG9g/S220/maras3.jpeg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_UExK-VwSpVM/SYw1SvwmIXI/AAAAAAAAABQ/4SAlzwB9Qeo/s72-c/urlExtended.PNG' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7975584389633834518.post-3015644881077298285</id><published>2009-01-27T14:57:00.020+01:00</published><updated>2009-06-22T11:38:16.851+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Internet Explorer'/><category scheme='http://www.blogger.com/atom/ns#' term='Web applications'/><category scheme='http://www.blogger.com/atom/ns#' term='html'/><category scheme='http://www.blogger.com/atom/ns#' term='Opera'/><category scheme='http://www.blogger.com/atom/ns#' term='Firebug'/><category scheme='http://www.blogger.com/atom/ns#' term='IE6'/><category scheme='http://www.blogger.com/atom/ns#' term='Firefox'/><category scheme='http://www.blogger.com/atom/ns#' term='Developer Toolbar'/><category scheme='http://www.blogger.com/atom/ns#' term='css'/><title type='text'>Internet Explorer 6 - Web Developers Nightmare</title><content type='html'>&lt;p&gt;I finally have a short break from fixing bugs and writing C# code. I'm creating new templates for one of our products instead. Yeah, I know, developers should not be responsible for html and css stuff. However I do really want to diversify into new areas, cause no one knows what future will bring to us. So I started my work. It's a pretty relaxing activity. No compiling, no complexity - just html, &lt;a href="http://getfirebug.com/"&gt;Firebug&lt;/a&gt; and me. I wish it were so easy...&lt;/p&gt;&lt;p&gt;Opera, Safari, even Chrome - everything looks smooth. Then I opened what I'd created in IE7. Few fixes and it looks exactly as I wanted. I did it even without &lt;a href="http://www.microsoft.com/downloadS/details.aspx?familyid=E59C3964-672D-4511-BB3E-2D5E1DB91038&amp;displaylang=en"&gt;Developer Toolbar&lt;/a&gt;. I was nearly there - just one last glance in IE6 (&lt;a href="http://tredosoft.com/Multiple_IE"&gt;Multiple IE&lt;/a&gt; is veeeery useful here). Well said "one last glance"... Layout of the page is completely crashed. I feel like I'm writing the page from the scratch. 4 additional lines in &amp;lt;head /&amp;gt;:&lt;/p&gt;&lt;div class="fullpost"&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px 0px; overflow: auto; width: 100%"&gt;&lt;code&gt;&amp;lt;!--[if IE 6]&amp;gt;&lt;br /&gt;  &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;/Styles/ie6.css&amp;quot; type=&amp;quot;text/css&amp;quot;&lt;br /&gt;          media=&amp;quot;screen&amp;quot; title=&amp;quot;IE bugfixes&amp;quot; charset=&amp;quot;utf-8&amp;quot; /&amp;gt;     &lt;br /&gt;&amp;lt;![endif]--&amp;gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;and here we go. "display: inline-block", "border-collapse: collapse" (a friend of mine, master of design, didn't even know that such an attribute exists), "clear: both; overflow: hidden;" nearly everywhere. Is my template so wrong? Or maybe it's the "browser". Heh "browser" - maybe I should not even call it with this word.&lt;/p&gt;&lt;p&gt;Maybe if I catch a goldfish or find Alladin's Lamp, I will have a wish "No IE6!". Maybe if I concentrate really hard... Ehhh who I'm trying to cheat. Hope often blinks at a fool. Let's check the reality. Fortunately I have  an access to the real data - our products :) Let's look at a sport portal first - over milions page views per day - it seems to be a good place to check stats.&lt;/p&gt;&lt;div style="text-align: center; clear: both; margin-bottom: 20px;"&gt;&lt;table class="stats_table"&gt;&lt;caption&gt;Sport portal users&lt;/caption&gt;&lt;tr&gt;&lt;th&gt;Browser&lt;/th&gt;&lt;th&gt;Percentage&lt;/th&gt;&lt;/tr&gt;&lt;tr class="even"&gt;&lt;td&gt;IE 7&lt;/td&gt;&lt;td&gt;32.7%&lt;/td&gt;&lt;/tr&gt;&lt;tr style="color: rgb(102, 102, 102);"&gt;&lt;td&gt;IE 6&lt;/td&gt;&lt;td&gt;24.7%&lt;/td&gt;&lt;/tr&gt;&lt;tr class="even"&gt;&lt;td&gt;Firefox 3&lt;/td&gt;&lt;td&gt;11.2%&lt;/td&gt;&lt;/tr&gt;&lt;tr style="color: rgb(102, 102, 102);"&gt;&lt;td&gt;Unknown&lt;/td&gt;&lt;td&gt;8.0%&lt;/td&gt;&lt;/tr&gt;&lt;tr class="even"&gt;&lt;td&gt;IE 5.5&lt;/td&gt;&lt;td&gt;5.9%&lt;/td&gt;&lt;/tr&gt;&lt;tr style="color: rgb(102, 102, 102);"&gt;&lt;td&gt;Mozilla 5&lt;/td&gt;&lt;td&gt;5.3%&lt;/td&gt;&lt;/tr&gt;&lt;tr class="even"&gt;&lt;td&gt;Safari&lt;/td&gt;&lt;td&gt;4.1%&lt;/td&gt;&lt;/tr&gt;&lt;tr style="color: rgb(102, 102, 102);"&gt;&lt;td&gt;Mozilla 4&lt;/td&gt;&lt;td&gt;2.7%&lt;/td&gt;&lt;/tr&gt;&lt;tr class="even"&gt;&lt;td&gt;Firefox 2&lt;/td&gt;&lt;td&gt;1.4%&lt;/td&gt;&lt;/tr&gt;&lt;tr style="color: rgb(102, 102, 102);"&gt;&lt;td&gt;Opera 9&lt;/td&gt;&lt;td&gt;1.5%&lt;/td&gt;&lt;/tr&gt;&lt;tr class="even"&gt;&lt;td&gt;IE 8.0&lt;/td&gt;&lt;td&gt;0.5%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;...&lt;/td&gt;&lt;td&gt;...&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;Nearly 25% users with IE6? I hoped it would be significantly less. And who still has IE 5.5? Maybe it's because of the fact that 50% of visitors are form UK and about 10% from both USA and Nigeria. Maybe rest of the world prefers other browsers? Let's check another site - 10k users per day, 26% from Canada, 8% from Span, 4,5% from Philippines, 4% from Lithuania, all other countries less than 1%. Yeah this is really international site.&lt;/p&gt;&lt;div style="text-align: center; clear: both; margin-bottom: 20px;"&gt;&lt;table class="stats_table"&gt;&lt;caption&gt;International site users&lt;/caption&gt;&lt;tr&gt;&lt;th&gt;Browser&lt;/th&gt;&lt;th&gt;Percentage&lt;/th&gt;&lt;/tr&gt;&lt;tr class="even"&gt;&lt;td&gt;IE 6.0&lt;/td&gt;&lt;td&gt;45.9%&lt;/td&gt;&lt;/tr&gt;&lt;tr style="color: rgb(102, 102, 102);"&gt;&lt;td&gt;IE 7.0&lt;/td&gt;&lt;td&gt;32.3%&lt;/td&gt;&lt;/tr&gt;&lt;tr class="even"&gt;&lt;td&gt;Firefox 3.0&lt;/td&gt;&lt;td&gt;9.2%&lt;/td&gt;&lt;/tr&gt;&lt;tr style="color: rgb(102, 102, 102);"&gt;&lt;td&gt;Netscape 4.0&lt;/td&gt;&lt;td&gt;4.1%&lt;/td&gt;&lt;/tr&gt;&lt;tr class="even"&gt;&lt;td&gt;Mozilla 5.0&lt;/td&gt;&lt;td&gt;2.5%&lt;/td&gt;&lt;/tr&gt;&lt;tr style="color: rgb(102, 102, 102);"&gt;&lt;td&gt;Unknown 0.0&lt;/td&gt;&lt;td&gt;2.1%&lt;/td&gt;&lt;/tr&gt;&lt;tr class="even"&gt;&lt;td&gt;Safari 5.0&lt;/td&gt;&lt;td&gt;1.3%&lt;/td&gt;&lt;/tr&gt;&lt;tr style="color: rgb(102, 102, 102);"&gt;&lt;td&gt;Firefox 2.0&lt;/td&gt;&lt;td&gt;1.2%&lt;/td&gt;&lt;/tr&gt;&lt;tr class="even"&gt;&lt;td&gt;...&lt;/td&gt;&lt;td&gt;...&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;p&gt;Looks like the world loves IE6. Maybe because it's very slow, maybe because it doesn't display properly thousands of pages, maybe ... I don't even want to guess their reasons. The truth is as you see above. I wish I were allowed to put a big banner on all the sites that my company creates, saying "Still using IE6? With other browsers surfing will be funnier and faster. Don't believe? Try one of the links ... " and display it whenever browser is recognized as IE6 or lower.&lt;/p&gt;&lt;p&gt;Wanna know my opinion? We will never be able to stop supporting IE6. You probably know that old code never dies. I must worry you, old browser never dies neither.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="useful_links"&gt;Useful links:&lt;br /&gt;&lt;a target="_blank" href="http://getfirebug.com/"&gt;Firebug&lt;/a&gt;&lt;br /&gt;&lt;a target="_blank" href="http://www.microsoft.com/downloadS/details.aspx?familyid=E59C3964-672D-4511-BB3E-2D5E1DB91038&amp;displaylang=en"&gt;Developer Toolbar&lt;/a&gt;&lt;br /&gt;&lt;a target="_blank" href="http://tredosoft.com/Multiple_IE"&gt;Multiple IE&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;a rev="vote-for" href="http://dotnetshoutout.com/Internet-Explorer-6-web-developers-nightmare" style="float: right"&gt;&lt;img alt="Shout it" src="http://dotnetshoutout.com/image.axd?url=http%3A%2F%2Fmarekmusielak.blogspot.com%2F2009%2F01%2Finternet-explorer-6-web-developers.html&amp;borderColor=000022&amp;textBackColor=000022&amp;countBackColor=999" style="border:0px"/&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7975584389633834518-3015644881077298285?l=marekmusielak.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='text/html' href='http://marekmusielak.blogspot.com/2009/01/internet-explorer-6-web-developers.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7975584389633834518/posts/default/3015644881077298285'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7975584389633834518/posts/default/3015644881077298285'/><link rel='alternate' type='text/html' href='http://marekmusielak.blogspot.com/2009/01/internet-explorer-6-web-developers.html' title='Internet Explorer 6 - Web Developers Nightmare'/><author><name>Marek Musielak</name><uri>http://www.blogger.com/profile/00718594959358144177</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_UExK-VwSpVM/SbUHbibCtAI/AAAAAAAAAB4/3lVUdtSKG9g/S220/maras3.jpeg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7975584389633834518.post-7836613382748440954</id><published>2009-01-25T13:02:00.006+01:00</published><updated>2009-06-22T11:38:08.754+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='.Net Framework'/><category scheme='http://www.blogger.com/atom/ns#' term='Software Development'/><category scheme='http://www.blogger.com/atom/ns#' term='Web applications'/><category scheme='http://www.blogger.com/atom/ns#' term='ASP.NET'/><category scheme='http://www.blogger.com/atom/ns#' term='500'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><category scheme='http://www.blogger.com/atom/ns#' term='Internet Information Services'/><title type='text'>Internal server errors handling</title><content type='html'>&lt;p&gt;"Page could not be loaded". "Internal server error". Arghhhh ... When I see such a message on the website I feel like leaving the site hastily and never come back. The worse is when this is a site that I've built ... Ok, I can cope with this when I'm the one who tried to open the page. But what when I don't know that on the site that I'm responsible for, someone gets such a message every minute? And visitors one by one give up entering this page. Yeah, this is a serious problem.&lt;/p&gt;&lt;p&gt;Let's do something with this. Sure, we can create custom error page (like I described it for &lt;a href="/2009/01/intelligent-custom-404-page.html" target="_blank"&gt;404 error here&lt;/a&gt;). It would look nicely and inform user that our site encountered some difficulties and that we will deal with them soon surely. Yeah, but we're not able to fix the problem if we don't know about it. Solution seems to be pretty obvious - let's send an email to us informing that the page doesn't work. But what if when we'll try to open the page later, it will be up and running again? Or if the page crashes only in some particular circumstances that we won't be able to reproduce?&lt;/p&gt;&lt;div class="fullpost"&gt;&lt;p&gt;It would be really good to know about every error on the site no matter who tried to browse it. We can use Application_Error handler here. The Application_Error event handler is specified in the Global.asax file of the application. This handler is called whenever an unhandled exception is thrown in your application. So let's add this handler and send a notification error every time when something is wrong on our site.&lt;/p&gt;&lt;br /&gt;&lt;a id="ae_code_3" href="#expand" onclick="document.getElementById('div_code_3').style.display = ''; this.style.display = 'none'; document.getElementById('ac_code_3').style.display = ''; return false;"&gt;Show code &amp;gt;&lt;/a&gt;&lt;a style="display: none;" id="ac_code_3" href="#collapse" onclick="document.getElementById('div_code_3').style.display = 'none'; this.style.display = 'none'; document.getElementById('ae_code_3').style.display = ''; return false;"&gt;&amp;lt; Hide code &lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;div id="div_code_3" style="display: none;"&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: visible; width: 700px"&gt;&lt;code&gt;public class Global : SiteBase&lt;br /&gt;{&lt;br /&gt;    protected void Application_Error(Object sender, EventArgs e)&lt;br /&gt;    {&lt;br /&gt;        SendEmail(new List&amp;lt;string&amp;gt; { &amp;quot;you@yoursite.com&amp;quot; });&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    private void SendEmail(List&amp;lt;string&amp;gt; emails)&lt;br /&gt;    {&lt;br /&gt;        try&lt;br /&gt;        {&lt;br /&gt;            MailMessage objEmail = new MailMessage&lt;br /&gt;            {&lt;br /&gt;                Subject = CreateSubject(),&lt;br /&gt;                Body = CreateMessage(),&lt;br /&gt;                From = new MailAddress(&amp;quot;error500@yoursite.com&amp;quot;)&lt;br /&gt;            };&lt;br /&gt;&lt;br /&gt;            foreach (string email in emails)&lt;br /&gt;            {&lt;br /&gt;                objEmail.To.Add(email);&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            SmtpClient smtpClient = new SmtpClient(&amp;quot;127.0.0.1&amp;quot;); // assuming that smtp is running locally&lt;br /&gt;            smtpClient.Send(objEmail);&lt;br /&gt;        }&lt;br /&gt;        catch (Exception exc)&lt;br /&gt;        {&lt;br /&gt;            // you can log the exception here&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    private string CreateMessage()&lt;br /&gt;    {&lt;br /&gt;        const string message = &amp;quot;500 error on yoursite.com\r\n\r\n&amp;quot; + //&lt;br /&gt;                               &amp;quot;URL: {0}\r\n&amp;quot; + //&lt;br /&gt;                               &amp;quot;RawURL: {1}\r\n&amp;quot; + //&lt;br /&gt;                               &amp;quot;IP: {2}\r\n&amp;quot; + //&lt;br /&gt;                               &amp;quot;Browser: {3}\r\n&amp;quot; + //&lt;br /&gt;                               &amp;quot;Crawler: {4}\r\n&amp;quot; + //&lt;br /&gt;                               &amp;quot;User (if logged in): {5}\r\n&amp;quot; + //&lt;br /&gt;                               &amp;quot;Reason: {6}&amp;quot;;&lt;br /&gt;&lt;br /&gt;        string browserName = &amp;quot;&amp;quot;;&lt;br /&gt;        string crawler = &amp;quot;&amp;quot;;&lt;br /&gt;        string user = &amp;quot;&amp;quot;;&lt;br /&gt;&lt;br /&gt;        if (Request.Browser != null)&lt;br /&gt;        {&lt;br /&gt;            browserName = Request.Browser.Browser;&lt;br /&gt;            crawler = Request.Browser.Crawler.ToString();&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        if (User.Identity.IsAuthenticated)&lt;br /&gt;        {&lt;br /&gt;            user = User.Identity.Name;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        return String.Format(message, Request.Url.AbsoluteUri, Request.RawUrl, Request.UserHostAddress, &lt;br /&gt;            browserName, crawler, user, GetReason());&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    private static string GetReason()&lt;br /&gt;    {&lt;br /&gt;        StringBuilder message = new StringBuilder(&amp;quot;Error 500&amp;quot;);&lt;br /&gt;&lt;br /&gt;        Exception currentException;&lt;br /&gt;&lt;br /&gt;        for (currentException = HttpContext.Current.Server.GetLastError();&lt;br /&gt;             currentException != null;&lt;br /&gt;             currentException = currentException.InnerException)&lt;br /&gt;        {&lt;br /&gt;&lt;br /&gt;            message.AppendFormat(&amp;quot;\r\n\r\ntype={0}\r\n\r\nmessage={1}\r\n\r\nstack=\r\n{2}\r\n\r\n&amp;quot;,&lt;br /&gt;&lt;br /&gt;                                 currentException.GetType().FullName,&lt;br /&gt;&lt;br /&gt;                                 currentException.Message,&lt;br /&gt;&lt;br /&gt;                                 currentException.StackTrace);&lt;br /&gt;&lt;br /&gt;        }&lt;br /&gt;        return message.ToString();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    private string CreateSubject()&lt;br /&gt;    {&lt;br /&gt;        return string.Format(&amp;quot;your site 500 error [ {0} ]&amp;quot;, Request.RawUrl);&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;&lt;p&gt;You probably won't need it in your development environment, but notification about erros should be a part of you production application for sure. Let me just give a short example:&lt;br /&gt; Pretty big portal. Maintained and upgraded all the time. Up to 1,5 milion page views per day. One of the popular pages that hasn't been touched for a long time was down because of some very low level code change... For 2 weeks. After I added notification about application errors, I was able to fix it immediately. Without it... Most probably it would still be down.&lt;/p&gt;&lt;p&gt;You can tell me that you are brilliant developer, that your sites have no problems, that when you create something you don't have to worry about it anymore. I can pretend that I believe you. Maybe even you are perfect. However remember that hardware and software that you depend on are not.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;a rev="vote-for" href="http://dotnetshoutout.com/Internal-server-errors-handling" style="float: right"&gt;&lt;img alt="Shout it" src="http://dotnetshoutout.com/image.axd?url=http%3A%2F%2Fmarekmusielak.blogspot.com%2F2009%2F01%2Finternal-server-errors-handling.html&amp;borderColor=000022&amp;textBackColor=000022&amp;countBackColor=999" style="border:0px"/&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7975584389633834518-7836613382748440954?l=marekmusielak.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='text/html' href='http://marekmusielak.blogspot.com/2009/01/internal-server-errors-handling.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7975584389633834518/posts/default/7836613382748440954'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7975584389633834518/posts/default/7836613382748440954'/><link rel='alternate' type='text/html' href='http://marekmusielak.blogspot.com/2009/01/internal-server-errors-handling.html' title='Internal server errors handling'/><author><name>Marek Musielak</name><uri>http://www.blogger.com/profile/00718594959358144177</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_UExK-VwSpVM/SbUHbibCtAI/AAAAAAAAAB4/3lVUdtSKG9g/S220/maras3.jpeg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7975584389633834518.post-924508943691082593</id><published>2009-01-24T13:40:00.004+01:00</published><updated>2009-06-22T11:38:00.858+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='.Net Framework'/><category scheme='http://www.blogger.com/atom/ns#' term='Software Development'/><category scheme='http://www.blogger.com/atom/ns#' term='Web applications'/><category scheme='http://www.blogger.com/atom/ns#' term='ASP.NET'/><category scheme='http://www.blogger.com/atom/ns#' term='404'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><category scheme='http://www.blogger.com/atom/ns#' term='Internet Information Services'/><title type='text'>Intelligent custom 404 page</title><content type='html'>&lt;p&gt;Have you ever clicked on the link on some page and got beautiful 404 page saying "Please make sure that you have typed the URL correctly"? Have you ever tried to open link that you found in google, the one that you had opened milion times before, and then saw the same page? I have. I know how annoying it can be. After all it is so easy to create custom 404 error page and display a message that is a little bit more intelligent.&lt;/p&gt;&lt;p&gt;Let's start with our own 404 page. Create new aspx file and call it whatever you want. PageNotFound is what I always use. I think it's good idea not to use the same template that we use for all the rest of our site - after all it is an error page. Pretty simple design, short message, maybe logo and few links with the most common used pages would be just fine. Now we need to set IIS to use this page for 404 errors instead of the default one. It's pretty simple. If you have never done this before take a look here &lt;a href="http://technet.microsoft.com/en-us/library/bb727138.aspx" target="_blank"&gt;http://technet.microsoft.com/en-us/library/bb727138.aspx&lt;/a&gt;. It will take you less than 5 minutes.&lt;/p&gt;&lt;div class="fullpost"&gt;&lt;p&gt;Ok, so we have custom error page, but what we were supposed to do was to display some intelligent message. That's the place where we can use url referrer. We can access it from code behind without any problems (Request.UrlReferrer). What does it give to us? In general we have 4 possibilities:&lt;br /&gt;&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Empty referrer. It means that user either typed the url and made a mistake, clicked on link sent by email of instant messenger or used a bookmark. This is what we can display to them: "Got a wrong link? Mis-typed the url? Your bookmarks are out of date?".&lt;/li&gt;&lt;li&gt;Referrer is search engine. If referrer is from Google, Yahoo or any other search engine it is clear that index of this engine is most probably out of date. So tell user about it. We can even try harder. Several lines of code like those:&lt;br /&gt;&lt;br /&gt;&lt;a id="ae_code_1" href="#expand" onclick="document.getElementById('div_code_1').style.display = ''; this.style.display = 'none'; document.getElementById('ac_code_1').style.display = ''; return false;"&gt;Show code &amp;gt;&lt;/a&gt;&lt;a style="display: none;" id="ac_code_1" href="#collapse" onclick="document.getElementById('div_code_1').style.display = 'none'; this.style.display = 'none'; document.getElementById('ae_code_1').style.display = ''; return false;"&gt;&amp;lt; Hide code &lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;span id="div_code_1" style="display: none;"&gt;&lt;br /&gt;&lt;pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: visible; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 700px;"&gt;&lt;code&gt;   string referrer = "";&lt;br /&gt;   if (Request.UrlReferrer != null)&lt;br /&gt;   {&lt;br /&gt;       referrer = Request.UrlReferrer.AbsoluteUri.ToLower();&lt;br /&gt;   }&lt;br /&gt;   if (referrer.Contains("google.") ||&lt;br /&gt;       referrer.Contains("ifind.freeserve") ||&lt;br /&gt;       referrer.Contains("ask.co") ||&lt;br /&gt;       referrer.Contains("altavista.co") ||&lt;br /&gt;       referrer.Contains("msn.co") ||&lt;br /&gt;       referrer.Contains("yahoo.co") ||&lt;br /&gt;       referrer.Contains("looksmart.co"))&lt;br /&gt;   {&lt;br /&gt;       string site = referrer.Split('/')[2];&lt;br /&gt;       string reason = "Incorrect url found in [" + site + "] search engine (searching for: '";&lt;br /&gt;       string[] paramsString = referrer.Split('?');&lt;br /&gt;       if (paramsString.Length &amp;gt; 1)&lt;br /&gt;       {&lt;br /&gt;           List&amp;lt;string&amp;gt; searchTerms = new List&amp;lt;string&amp;gt;(paramsString[1].Split('&amp;amp;'));&lt;br /&gt;           List&amp;lt;string&amp;gt; queryStrings = new List&amp;lt;string&amp;gt;(4) {"q=", "p=", "ask=", "key="};&lt;br /&gt;&lt;br /&gt;           foreach (string searchTerm in searchTerms)&lt;br /&gt;           {&lt;br /&gt;               foreach (string queryString in queryStrings)&lt;br /&gt;               {&lt;br /&gt;                   if (searchTerm.StartsWith(queryString, StringComparison.OrdinalIgnoreCase))&lt;br /&gt;                   {&lt;br /&gt;                       reason += searchTerm.Replace(queryString, "").Replace("+", " ");&lt;br /&gt;                   }&lt;br /&gt;               }&lt;br /&gt;           }&lt;br /&gt;           reason += "')";&lt;br /&gt;       }&lt;br /&gt;   }&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/span&gt;give us information about what user looked for. Maybe we're able to give them a hint with few most probable links?&lt;/li&gt;&lt;li&gt;If referrer is from some other site then we know that link on that site is incorrect. We're not able to do anything about this but we can always inform user that it's not our fault and that the link that they used is wrong.&lt;/li&gt;&lt;li&gt;The last possibility is that referrer is from our site. It would be good to apologize and fix the link.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Yeah, fix the link ... Easy to say but ... we would have to know that we have a bad link somewhere on our site first. It is easier than you think. Let's send an email to us. Another piece of code will send a mail to our addresses with information on where user came from and what wasn't found:&lt;/p&gt;&lt;a id="ae_code_2" href="#expand" onclick="document.getElementById('div_code_2').style.display = ''; this.style.display = 'none'; document.getElementById('ac_code_2').style.display = ''; return false;"&gt;Show code &amp;gt;&lt;/a&gt;&lt;a style="display: none;" id="ac_code_2" href="#collapse" onclick="document.getElementById('div_code_2').style.display = 'none'; this.style.display = 'none'; document.getElementById('ae_code_2').style.display = ''; return false;"&gt;&amp;lt; Hide code &lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;div id="div_code_2" style="display: none;"&gt;&lt;pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 800px;"&gt;&lt;code&gt;   private void SendEmail(List&amp;lt;string&amp;gt; emails)&lt;br /&gt;   {&lt;br /&gt;       MailMessage objEmail = null;&lt;br /&gt;       try&lt;br /&gt;       {&lt;br /&gt;           objEmail = new MailMessage&lt;br /&gt;                          {&lt;br /&gt;                              Subject = CreateSubject(),&lt;br /&gt;                              Body = CreateMessage(),&lt;br /&gt;                              From = new MailAddress("error404@yourdomain.com")&lt;br /&gt;                          };&lt;br /&gt;           foreach (string email in emails)&lt;br /&gt;           {&lt;br /&gt;               objEmail.To.Add(email);&lt;br /&gt;           }&lt;br /&gt;&lt;br /&gt;           SmtpClient smtpClient = new SmtpClient("127.0.0.1"); // assuming that smtp is running locally&lt;br /&gt;           smtpClient.Send(objEmail);&lt;br /&gt;       }&lt;br /&gt;       catch (Exception exc)&lt;br /&gt;       {&lt;br /&gt;           LOG.Error(String.Format("Exception while sending email {0}", objEmail), exc);&lt;br /&gt;       }&lt;br /&gt;   }&lt;br /&gt; &lt;br /&gt;   private string CreateSubject()&lt;br /&gt;   {&lt;br /&gt;       return string.Format("your site 404 error [ {0} ]", Url);&lt;br /&gt;   } &lt;br /&gt; &lt;br /&gt;   private string url; &lt;br /&gt; &lt;br /&gt;   private string Url&lt;br /&gt;   {&lt;br /&gt;       get&lt;br /&gt;       {&lt;br /&gt;           if (null == url)&lt;br /&gt;           {&lt;br /&gt;               string[] urls = Request.RawUrl.Split(new[] { ";" }, StringSplitOptions.RemoveEmptyEntries);&lt;br /&gt;&lt;br /&gt;               if (urls.Length &amp;gt; 1)&lt;br /&gt;               {&lt;br /&gt;                   url = Request.RawUrl.Substring(urls[0].Length + 1);&lt;br /&gt;               }&lt;br /&gt;&lt;br /&gt;               if (url.Contains(":80/"))&lt;br /&gt;               {&lt;br /&gt;                   url = url.Replace(":80", "");&lt;br /&gt;               }&lt;br /&gt;           }&lt;br /&gt;           return url;&lt;br /&gt;       }&lt;br /&gt;   }&lt;br /&gt; &lt;br /&gt;   private string CreateMessage()&lt;br /&gt;   {&lt;br /&gt;       const string message = "404 error on yoursite.com\r\n\r\n" + //&lt;br /&gt;                              "URL: {0}\r\n" + //&lt;br /&gt;                              "Referrer: {1}\r\n" + //&lt;br /&gt;                              "IP: {6}\r\n" + //&lt;br /&gt;                              "Browser: {2}\r\n" + //&lt;br /&gt;                              "Crawler: {3}\r\n" + //&lt;br /&gt;                              "User (if logged in): {4}\r\n" + //&lt;br /&gt;                              "Possible reason: {5}";&lt;br /&gt;&lt;br /&gt;       string browserName = "";&lt;br /&gt;       string crawler = "";&lt;br /&gt;       string referrer = "";&lt;br /&gt;       string user = "";&lt;br /&gt;&lt;br /&gt;       if (Request.Browser != null)&lt;br /&gt;       {&lt;br /&gt;           browserName = Request.Browser.Browser;&lt;br /&gt;           crawler = Request.Browser.Crawler.ToString();&lt;br /&gt;       }&lt;br /&gt;&lt;br /&gt;       if (Request.UrlReferrer != null)&lt;br /&gt;       {&lt;br /&gt;           referrer = Request.UrlReferrer.AbsoluteUri.ToLower();&lt;br /&gt;       }&lt;br /&gt;&lt;br /&gt;       if (User.Identity.IsAuthenticated)&lt;br /&gt;       {&lt;br /&gt;           user = User.Identity.Name;&lt;br /&gt;       }&lt;br /&gt;&lt;br /&gt;       return String.Format(message, Url, referrer, browserName, crawler, user, GetReason(referrer, Url), &lt;br /&gt;           Request.UserHostAddress);&lt;br /&gt;   }&lt;br /&gt; &lt;br /&gt;   private static string GetReason(string referrer, string url)&lt;br /&gt;   {&lt;br /&gt;       try&lt;br /&gt;       {&lt;br /&gt;           string reason;&lt;br /&gt;           if (String.IsNullOrEmpty(referrer))&lt;br /&gt;           {&lt;br /&gt;               reason = "Mis-typed URL - empty referrer";&lt;br /&gt;           }&lt;br /&gt;           else if (referrer.Contains("google.") ||&lt;br /&gt;                    referrer.Contains("ifind.freeserve") ||&lt;br /&gt;                    referrer.Contains("ask.co") ||&lt;br /&gt;                    referrer.Contains("altavista.co") ||&lt;br /&gt;                    referrer.Contains("msn.co") ||&lt;br /&gt;                    referrer.Contains("yahoo.co") ||&lt;br /&gt;                    referrer.Contains("looksmart.co"))&lt;br /&gt;           {&lt;br /&gt;               string site = referrer.Split('/')[2];&lt;br /&gt;               reason = "Incorrect url found in [" + site + "] search engine (searching for: '";&lt;br /&gt;               string[] paramsString = referrer.Split('?');&lt;br /&gt;               if (paramsString.Length &amp;gt; 1)&lt;br /&gt;               {&lt;br /&gt;                   List&amp;lt;string&amp;gt; searchTerms = new List&amp;lt;string&amp;gt;(paramsString[1].Split('&amp;amp;'));&lt;br /&gt;                   List&amp;lt;string&amp;gt; queryStrings = new List&amp;lt;string&amp;gt;(4) {"q=", "p=", "ask=", "key="};&lt;br /&gt;&lt;br /&gt;                   foreach (string searchTerm in searchTerms)&lt;br /&gt;                   {&lt;br /&gt;                       foreach (string queryString in queryStrings)&lt;br /&gt;                       {&lt;br /&gt;                           if (searchTerm.StartsWith(queryString, StringComparison.OrdinalIgnoreCase))&lt;br /&gt;                           {&lt;br /&gt;                               reason += searchTerm.Replace(queryString, "").Replace("+", " ");&lt;br /&gt;                           }&lt;br /&gt;                       }&lt;br /&gt;                   }&lt;br /&gt;                   reason += "')";&lt;br /&gt;               }&lt;br /&gt;           }&lt;br /&gt;           else if (url.Split('/')[2] != referrer.Split('/')[2])&lt;br /&gt;           {&lt;br /&gt;               reason = "Incorrect url on the referring site ( " + referrer + " )";&lt;br /&gt;           }&lt;br /&gt;           else if (url.Split('/')[2] == referrer.Split('/')[2])&lt;br /&gt;           {&lt;br /&gt;               reason = "Incorrect url on yoursite.com ( " + referrer + " )";&lt;br /&gt;           }&lt;br /&gt;           else&lt;br /&gt;           {&lt;br /&gt;               reason = "unknown reason";&lt;br /&gt;           }&lt;br /&gt;           return reason;&lt;br /&gt;       } catch (Exception exc)&lt;br /&gt;       {&lt;br /&gt;           LOG.Error("exception while looking for a reason", exc);&lt;br /&gt;       }&lt;br /&gt;       return "Unknown";&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;&lt;p&gt;What is more, if many users try access the same not existing url (e.g. we change the url of one of the pages on our site), we can redirect them where they most probably wanted to be. I recommed using 301 redirect (permanently moved) instead of 302 (temporarily moved). I'm not going to draw water in a sieve trying to convience anyone of pros and cons of 301 redirect - if anyone is really interested in this, type 301 vs 302 in google. It's enough of this in the web already, let's do not create any artifical crowd ;)&lt;/p&gt;&lt;p&gt;Let's go further with 404. Our custom 404 page will inform us not only about not existing pages, but about the missing images, javascript files or even hacking attempts. If you will notice that some ips flood you with suspicious requests, you can always ban them. And if you are tired of getting and getting 404 emails that you are not interested in, you can always create a list of regexes and filter what do you want to send and what not.&lt;/p&gt;&lt;p&gt;Agree with me or not, but custom error pages for 404 or 500 errors should be one of the first steps in web application development.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;a rev="vote-for" href="http://dotnetshoutout.com/Intelligent-404-page" style="float: right"&gt;&lt;img alt="Shout it" src="http://dotnetshoutout.com/image.axd?url=http%3A%2F%2Fmarekmusielak.blogspot.com%2F2009%2F01%2Fintelligent-custom-404-page.html&amp;borderColor=000022&amp;textBackColor=000022&amp;countBackColor=999" style="border:0px"/&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7975584389633834518-924508943691082593?l=marekmusielak.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='text/html' href='http://marekmusielak.blogspot.com/2009/01/intelligent-custom-404-page.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7975584389633834518/posts/default/924508943691082593'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7975584389633834518/posts/default/924508943691082593'/><link rel='alternate' type='text/html' href='http://marekmusielak.blogspot.com/2009/01/intelligent-custom-404-page.html' title='Intelligent custom 404 page'/><author><name>Marek Musielak</name><uri>http://www.blogger.com/profile/00718594959358144177</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_UExK-VwSpVM/SbUHbibCtAI/AAAAAAAAAB4/3lVUdtSKG9g/S220/maras3.jpeg'/></author><thr:total>5</thr:total></entry></feed>
