<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://weblogs.asp.net/utility/FeedStylesheets/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>Kazi Manzur Rashid's Blog : DotNetShoutout</title><link>http://weblogs.asp.net/rashid/archive/tags/DotNetShoutout/default.aspx</link><description>Tags: DotNetShoutout</description><dc:language>en</dc:language><generator>CommunityServer 2007 SP1 (Build: 20510.895)</generator><item><title>For Us By Us</title><link>http://weblogs.asp.net/rashid/archive/2009/05/02/for-us-by-us.aspx</link><pubDate>Sat, 02 May 2009 01:00:44 GMT</pubDate><guid isPermaLink="false">c06e2b9d-981a-45b4-a55f-ab0d8bbfdc1c:7070173</guid><dc:creator>kazimanzurrashid</dc:creator><slash:comments>0</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/rashid/rsscomments.aspx?PostID=7070173</wfw:commentRss><wfw:comment xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/rashid/commentapi.aspx?PostID=7070173</wfw:comment><comments>http://weblogs.asp.net/rashid/archive/2009/05/02/for-us-by-us.aspx#comments</comments><description>&lt;p&gt;No, this is not at all a post of &lt;a href="http://code.google.com/p/fubumvc/" target="_blank"&gt;FubuMVC&lt;/a&gt;, I just borrowed the words for this post. &lt;/p&gt;  &lt;p&gt;Jeff Atwood &amp;amp; Joel Spolsky thinks it is a compliment when they found there site design is &lt;a href="http://blog.stackoverflow.com/2009/04/podcast-48/" target="_blank"&gt;copied by a Chinese site&lt;/a&gt; and I do agree it completely, specially when it is serving the same community that I belong to. And I also agree with Joel Spolsky that most (at least in my case) English as second language speaking developers prefer the local language when they are communicating between them. We made &lt;a href="http://kigg.codeplex.com/" target="_blank"&gt;KiGG&lt;/a&gt; an Open Source Project from the &lt;a href="http://weblogs.asp.net/rashid/archive/2008/02/18/kigg-a-digg-like-application-developed-in-asp-net-mvc.aspx" target="_blank"&gt;very beginning&lt;/a&gt; and since the &lt;a href="http://weblogs.asp.net/rashid/archive/2009/01/20/introducing-dotnetshoutout-com.aspx" target="_blank"&gt;v2.0 is released&lt;/a&gt; people are really picking it for creating local .NET community sites in their own language:&lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;&lt;a href="http://dotnetomaniak.pl" target="_blank"&gt;&lt;img title="dotnetomaniak.pl" style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="377" alt="dotnetomaniak_pl" src="http://weblogs.asp.net/blogs/rashid/dotnetomaniak_pl_2BA84A4C.png" width="701" border="0" /&gt;&lt;/a&gt;     &lt;br /&gt;&lt;a href="http://dotnetomaniak.pl" target="_blank"&gt;Polish version&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;&lt;a href="http://progg.ru/" target="_blank"&gt;&lt;img title="progg.ru" style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="377" alt="progg_ru" src="http://weblogs.asp.net/blogs/rashid/progg_ru_25412824.png" width="701" border="0" /&gt;&lt;/a&gt;     &lt;br /&gt;&lt;a href="http://progg.ru/" target="_blank"&gt;Russian version&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;&lt;a href="http://9efish.com/" target="_blank"&gt;&lt;img title="9efish_com" style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="370" alt="9efish_com" src="http://weblogs.asp.net/blogs/rashid/9efish_com_2F8226B7.png" width="701" border="0" /&gt;&lt;/a&gt;     &lt;br /&gt;&lt;a href="http://9efish.com/" target="_blank"&gt;Chinese version (I guess it is still under development)&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;And this is the power of Open Source, the existing version contains absolutely zero support for localization, but the community picked and made it local for them, it has the same features and power as the original version and my sincere thanks to them who are behind these. As a side note I want to mention that localization is the highest priority that we will be adding in v3.0&lt;/p&gt;  &lt;p&gt;Recently, I got few requests about the story publishing process from the peoples who are also planning to launch sites based upon KiGG. So instead of answering them individually, I preferred to write a post to explain it.&lt;/p&gt;  &lt;p&gt;Like the many &lt;a href="http://www.seomoz.org/blog/reddit-stumbleupon-delicious-and-hacker-news-algorithms-exposed" target="_blank"&gt;social news/links site&lt;/a&gt; the story appearing in the front-page is done based upon some algorithms and it has the complete support to add/replace/remove any of those algorithms. By default, it comes with 6 different algorithms.&lt;/p&gt;  &lt;p&gt;The story publish process starts when the &lt;code&gt;Publish&lt;/code&gt; method of &lt;code&gt;StoryService&lt;/code&gt; is called.&lt;/p&gt;  &lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:738c2171-67b4-4d6b-a24d-6f88cd119040" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="c#"&gt;public virtual void Publish()
{
    using(IUnitOfWork unitOfWork = UnitOfWork.Begin())
    {
        DateTime currentTime = SystemTime.Now();

        IList&amp;lt;PublishedStory&amp;gt; publishableStories = GetPublishableStories(currentTime);

        if (!publishableStories.IsNullOrEmpty())
        {
            // First penalty the user for marking the story as spam;
            // It is obvious that the Moderator has already reviewed the story
            // before it gets this far.
            PenaltyUsersForIncorrectlyMarkingStoriesAsSpam(publishableStories);

            //Then Publish the stories
            PublishStories(currentTime, publishableStories);

            unitOfWork.Commit();
        }
    }
}&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;As you can see, it first calls the &lt;code&gt;GetPublishableStories&lt;/code&gt; to prepare a list which is publishable at this moment, next it reduces the score of the users who have incorrectly marked any of those stories as spam (will explain&amp;#160; later) and at last it calls another method &lt;code&gt;PublishStories&lt;/code&gt; to actually publish it.&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:3bcb3dc8-68a5-4e62-a7f1-f5d59c3acffc" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="c#"&gt;private IList&amp;lt;PublishedStory&amp;gt; GetPublishableStories(DateTime currentTime)
{
    List&amp;lt;PublishedStory&amp;gt; publishableStories = new List&amp;lt;PublishedStory&amp;gt;();

    DateTime minimumDate = currentTime.AddHours(-_settings.MaximumAgeOfStoryInHoursToPublish);
    DateTime maximumDate = currentTime.AddHours(-_settings.MinimumAgeOfStoryInHoursToPublish);

    int publishableCount = _storyRepository.CountByPublishable(minimumDate, maximumDate);

    if (publishableCount &amp;gt; 0)
    {
        ICollection&amp;lt;IStory&amp;gt; stories = _storyRepository.FindPublishable(minimumDate, maximumDate, 0, publishableCount).Result;

        foreach (IStory story in stories)
        {
            PublishedStory publishedStory = new PublishedStory(story);

            foreach (IStoryWeightCalculator strategy in _storyWeightCalculators)
            {
                publishedStory.Weights.Add(strategy.Name, strategy.Calculate(currentTime, story));
            }

            publishableStories.Add(publishedStory);
        }
    }

    return publishableStories;
}&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;GetPublishableStories&lt;/code&gt; first gets a list of active stories (the stories that has been voted, viewed, commented since the last publish, there is also an age factor which means story in specific age range will only qualify, default is 4-240 hour) form the database, next it applies the different algorithm to calculate its weight. This algorithm (Strategy Pattern) is defined as &lt;code&gt;IStoryWeightCalculator&lt;/code&gt; interface and injected in the &lt;code&gt;StoryService&lt;/code&gt; by the DI container. Once the calculation is done the &lt;code&gt;PublishStories&lt;/code&gt; method is called.&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:0371eb80-9722-45d4-8187-37b5442765d2" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="c#"&gt;private void PublishStories(DateTime currentTime, IList&amp;lt;PublishedStory&amp;gt; publishableStories)
{
    // Now sort it based upon the score
    publishableStories = publishableStories.OrderByDescending(ps =&amp;gt; ps.TotalScore).ToList();

    // Now assign the Rank
    publishableStories.ForEach(ps =&amp;gt; ps.Rank = (publishableStories.IndexOf(ps) + 1));

    // Now take the stories for front page
    ICollection&amp;lt;PublishedStory&amp;gt; frontPageStories = publishableStories.OrderBy(ps =&amp;gt; ps.Rank).Take(_settings.HtmlStoryPerPage).ToList();

    if (!frontPageStories.IsNullOrEmpty())
    {
        foreach (PublishedStory ps in frontPageStories)
        {
            ps.Story.Publish(currentTime, ps.Rank);
        }

        _eventAggregator.GetEvent&amp;lt;StoryPublishEvent&amp;gt;().Publish(new StoryPublishEventArgs(frontPageStories, currentTime));
    }

    // Mark the Story that it has been processed
    publishableStories.ForEach(ps =&amp;gt; ps.Story.LastProcessed(currentTime));
}&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;PublishStories&lt;/code&gt; first ranks the stories based upon its total weight, then it takes the first 20 story (defined in the web.config) for the front-page and marks it as for front-page by updating its rank and published date, at last it updates the last process date of all the stories regardless its publish status so that we can verify its active status for the next publish.&lt;/p&gt;

&lt;p&gt;As you can see the story qualifying is completely decided based upon those weight calculators and by adding/removing/replacing the new calculators you can tweak the whole story publishing process. Now lets see how the default calculators works.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;VoteWeight&lt;/code&gt;: Returns higher value if the vote is given from a different IP address than the story was actually submitted, for example 10 if it is given from a different IP address and 5 from the same IP address. If you do not like it, you can level it in the web.config.&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:d9e4a5ca-7d8f-443b-a916-90a1a429d7d1" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="xml"&gt;&amp;lt;type name="vote" type="IStoryWeightCalculator" mapTo="VoteWeightCalculator"&amp;gt;
    &amp;lt;lifetime type="PerWebRequest"/&amp;gt;
    &amp;lt;typeConfig extensionType="Microsoft.Practices.Unity.Configuration.TypeInjectionElement, Microsoft.Practices.Unity.Configuration"&amp;gt;
        &amp;lt;constructor&amp;gt;
            &amp;lt;param name="voteRepository" parameterType="IVoteRepository"&amp;gt;
                &amp;lt;dependency/&amp;gt;
            &amp;lt;/param&amp;gt;
            &amp;lt;param name="sameIPAddressWeight" parameterType="System.Single"&amp;gt;
                &amp;lt;value type="System.Single" value="5"/&amp;gt;
            &amp;lt;/param&amp;gt;
            &amp;lt;param name="differentIPAddressWeight" parameterType="System.Single"&amp;gt;
                &amp;lt;value type="System.Single" value="10"/&amp;gt;
            &amp;lt;/param&amp;gt;
        &amp;lt;/constructor&amp;gt;
    &amp;lt;/typeConfig&amp;gt;
&amp;lt;/type&amp;gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CommentWeight&lt;/code&gt;: Returns higher value if comment is given from a different Ip&amp;#160; and not by the actual submitter. For example, 4 for different IP, 2 for same IP and 1 for the actual submitter no matter from which IP it was submitted. This can be changed from web.config:&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:6a168f85-25bf-4938-8c4d-ceaab3f37249" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="xml"&gt;&amp;lt;type name="comment" type="IStoryWeightCalculator" mapTo="CommentWeightCalculator"&amp;gt;
    &amp;lt;lifetime type="PerWebRequest"/&amp;gt;
    &amp;lt;typeConfig extensionType="Microsoft.Practices.Unity.Configuration.TypeInjectionElement, Microsoft.Practices.Unity.Configuration"&amp;gt;
        &amp;lt;constructor&amp;gt;
            &amp;lt;param name="commentRepository" parameterType="ICommentRepository"&amp;gt;
                &amp;lt;dependency/&amp;gt;
            &amp;lt;/param&amp;gt;
            &amp;lt;param name="ownerWeight" parameterType="System.Single"&amp;gt;
                &amp;lt;value type="System.Single" value="1"/&amp;gt;
            &amp;lt;/param&amp;gt;
            &amp;lt;param name="sameIPAddressWeight" parameterType="System.Single"&amp;gt;
                &amp;lt;value type="System.Single" value="2"/&amp;gt;
            &amp;lt;/param&amp;gt;
            &amp;lt;param name="differentIPAddressWeight" parameterType="System.Single"&amp;gt;
                &amp;lt;value type="System.Single" value="4"/&amp;gt;
            &amp;lt;/param&amp;gt;
        &amp;lt;/constructor&amp;gt;
    &amp;lt;/typeConfig&amp;gt;
&amp;lt;/type&amp;gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ViewWeight&lt;/code&gt;: Returns the sum of each unique IP address view (view means when a user clicks a link which takes the user to the original source) multiplied by a factor, this can be also changed from the web.config.&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:bfaa208a-ac04-4c29-af5d-407af58e985f" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="xml"&gt;&amp;lt;type name="view" type="IStoryWeightCalculator" mapTo="ViewWeightCalculator"&amp;gt;
    &amp;lt;lifetime type="PerWebRequest"/&amp;gt;
    &amp;lt;typeConfig extensionType="Microsoft.Practices.Unity.Configuration.TypeInjectionElement, Microsoft.Practices.Unity.Configuration"&amp;gt;
        &amp;lt;constructor&amp;gt;
            &amp;lt;param name="storyViewRepository" parameterType="IStoryViewRepository"&amp;gt;
                &amp;lt;dependency/&amp;gt;
            &amp;lt;/param&amp;gt;
            &amp;lt;param name="weightMultiply" parameterType="System.Single"&amp;gt;
                &amp;lt;value type="System.Single" value="0.1"/&amp;gt;
            &amp;lt;/param&amp;gt;
        &amp;lt;/constructor&amp;gt;
    &amp;lt;/typeConfig&amp;gt;
&amp;lt;/type&amp;gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;UserScoreWeight&lt;/code&gt;: Returns the sum of user score multiplied by a factor who voted the story, so that stories voted by the higher score users has much more chance to appear in the front-page. The factor can be also changed from the web.config.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Freshness&lt;/code&gt;: Returns value depending upon the story age, the young the story the higher the value. The freshness threshold and interval can also be changed from web.config.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;KnownSource&lt;/code&gt;: Returns a value based upon the source grade. This ensures that stories from an well known source get higher chance to appear in the front-page. For example, a blog post from Gu should take precedence than a blog post of mine. These sources are maintained in the KnownSource database table. If the source is not known, then it returns nothing.&lt;/p&gt;

&lt;p&gt;So, as you can understand with the above algorithms even a story that got less votes might rank better than a more voted story and another important things I would like to mention that it does not update any specific part (top/bottom) of the front-page, instead it updates the whole page, the existing story of the front-page might also appear in the front-page at the same or different location based upon its recent rank, the benifit is it will never replace much more popular but old story by a less popular new story.&lt;/p&gt;

&lt;p&gt;Okay, if you think the above is a solid publishing process or the process su*ks or you need a much simpler algorithm, my recommendation is to create a new strategy inheriting from the &lt;code&gt;StoryWeightBaseCalculator&lt;/code&gt;. Let us see how to create a dumb strategy which rank the story based upon the number of votes and comments it got.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:2c660b50-c4f8-4663-bd72-290110098ab8" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="c#"&gt;public class SimpleWeightCalculator : StoryWeightBaseCalculator
{
    private readonly IVoteRepository _voteRepository;
    private readonly ICommentRepository _commentRepository;

    public SimpleWeightCalculator(IVoteRepository voteRepository, ICommentRepository commentRepository) : base("Simple")
    {
        _voteRepository = voteRepository;
        _commentRepository = commentRepository;
    }

    public override double Calculate(DateTime publishingTimestamp, IStory story)
    {
        ICollection&amp;lt;IVote&amp;gt; votes = _voteRepository.FindAfter(story.Id, story.LastProcessedAt ?? story.CreatedAt);
        ICollection&amp;lt;IComment&amp;gt; comments = _commentRepository.FindAfter(story.Id, story.LastProcessedAt ?? story.CreatedAt);

        return (votes.Count + comments.Count);
    }
}&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Next, remove the existing strategies and add it in the web.config (I am assuming you know the configuration of MS Unity application block):&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:90c8f8bd-62d2-40f2-b79b-ce8973557bed" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="xml"&gt;&amp;lt;type name="simple" type="IStoryWeightCalculator" mapTo="SimpleWeightCalculator"&amp;gt;
    &amp;lt;lifetime type="PerWebRequest"/&amp;gt;
    &amp;lt;typeConfig extensionType="Microsoft.Practices.Unity.Configuration.TypeInjectionElement, Microsoft.Practices.Unity.Configuration"&amp;gt;
        &amp;lt;constructor&amp;gt;
            &amp;lt;param name="voteRepository" parameterType="IVoteRepository"&amp;gt;
                &amp;lt;dependency/&amp;gt;
            &amp;lt;/param&amp;gt;
            &amp;lt;param name="commentRepository" parameterType="ICommentRepository"&amp;gt;
                &amp;lt;dependency/&amp;gt;
            &amp;lt;/param&amp;gt;
        &amp;lt;/constructor&amp;gt;
    &amp;lt;/typeConfig&amp;gt;
&amp;lt;/type&amp;gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now, when you publish stories, it will rank based upon the vote and comments it got since the last publish.&lt;/p&gt;

&lt;p&gt;I hope the above clarifies the story publish process of KiGG and do let me what else of KiGG you want to highlight next.&lt;/p&gt;&lt;div class="wlWriterHeaderFooter" style="margin:0px; padding:0px 0px 0px 0px;"&gt;&lt;div class="shoutIt"&gt;&lt;a rev="vote-for" href="http://dotnetshoutout.com/Submit?url=http%3a%2f%2fweblogs.asp.net%2frashid%2farchive%2f2009%2f05%2f02%2ffor-us-by-us.aspx&amp;amp;title=For+Us+By+Us"&gt;&lt;img alt="Shout it" src="http://dotnetshoutout.com/image.axd?url=http://weblogs.asp.net/rashid/archive/2009/05/02/for-us-by-us.aspx" style="border:0px" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;img src="http://weblogs.asp.net/aggbug.aspx?PostID=7070173" width="1" height="1"&gt;</description><category domain="http://weblogs.asp.net/rashid/archive/tags/Community+News/default.aspx">Community News</category><category domain="http://weblogs.asp.net/rashid/archive/tags/DotNetShoutout/default.aspx">DotNetShoutout</category><category domain="http://weblogs.asp.net/rashid/archive/tags/KiGG/default.aspx">KiGG</category><category domain="http://weblogs.asp.net/rashid/archive/tags/ocp/default.aspx">ocp</category><category domain="http://weblogs.asp.net/rashid/archive/tags/Open+Closed+Principle/default.aspx">Open Closed Principle</category><category domain="http://weblogs.asp.net/rashid/archive/tags/Open+Source/default.aspx">Open Source</category></item><item><title>Script and CSS Management in ASP.NET MVC</title><link>http://weblogs.asp.net/rashid/archive/2009/04/28/script-and-css-management-in-asp-net-mvc.aspx</link><pubDate>Mon, 27 Apr 2009 21:00:08 GMT</pubDate><guid isPermaLink="false">c06e2b9d-981a-45b4-a55f-ab0d8bbfdc1c:7062177</guid><dc:creator>kazimanzurrashid</dc:creator><slash:comments>18</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/rashid/rsscomments.aspx?PostID=7062177</wfw:commentRss><wfw:comment xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/rashid/commentapi.aspx?PostID=7062177</wfw:comment><comments>http://weblogs.asp.net/rashid/archive/2009/04/28/script-and-css-management-in-asp-net-mvc.aspx#comments</comments><description>&lt;p&gt;If you are familiar with &lt;a href="http://developer.yahoo.com/yslow/" target="_blank"&gt;YSlow&lt;/a&gt; recommendations, I guess you know that it recommends to put your CSS files at the top(&lt;a href="http://developer.yahoo.com/performance/rules.html#css_top" target="_blank"&gt;#5&lt;/a&gt;)&amp;#160; and JavaScript files at the bottom(&lt;a href="http://developer.yahoo.com/performance/rules.html#js_bottom" target="_blank"&gt;#6&lt;/a&gt;) of the pages. Placing the CSS files at the top is not an issue but putting the JavaScript files at the bottom of the pages has some gotchas, specially in heavily ajaxed based site with Master and Content page environment. When developing a Web 2.0 ajax site it is obvious that we will be using quite a number of plug-ins/widgets along with the core framework like jQuery, ExtJS, Prototype etc and of course there will be our hand coded javascript files, depending upon the size and the functionalities, the number of files can vary. This is the screenshot just to show you the usages of javascript files in &lt;a href="http://dotnetshoutout.com/" target="_blank"&gt;DotNetShoutout&lt;/a&gt;/&lt;a href="http://codeplex.com/Kigg" target="_blank"&gt;KiGG&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://weblogs.asp.net/blogs/rashid/JSFiles_0FD6C6E7.png"&gt;&lt;img title="JSFiles" style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="500" alt="JSFiles" src="http://weblogs.asp.net/blogs/rashid/JSFiles_thumb_7349AF39.png" width="400" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;Certainly we can merge all these files into one large file and put it in the page bottom but it wont be very optimal solution, it will make our page unnecessary heavy and add delays prior making the page usable. Some will argue that it is just for the first time as the browser will cache the file and the visitor does not have to download it again. Yes true, but for a new site where a large percentage of visitors is visiting it for the first time, it is really important that it is lightening fast and reducing the number and size of the external files we can make our pages to load faster. So, instead of downloading a single gigantic file, we preferred to divide and merge those files in terms of functionality into different file sets and add specific sets to each page. The Master page contains the core javascript framework(jQuery), common parts/initialization that are reused in the content pages and the content pages have its own specific file sets and initialization scripts. And this is where it starts throwing javascript exceptions. Consider the following simple scenario:&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;In your master page at the bottom you have added the &lt;code&gt;Utility.js&lt;/code&gt; with the jQuery framework:&lt;/p&gt;  &lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:a37f2cf0-ae58-4c4f-86e1-db8c9fa42729" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="xml"&gt;    &amp;lt;script type="text/javascript" src="&amp;lt;%= Url.Content("~/Scripts/jquery-1.3.2.min.js")%&amp;gt;"&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script type="text/javascript" src="&amp;lt;%= Url.Content("~/Scripts/utillity.js")%&amp;gt;"&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script type="text/javascript"&amp;gt;
        $(document).ready(
                            function()
                            {
                                utility.init();
                            }
                        );
    &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And in content page that is using the above master page you have added the &lt;code&gt;dummyObject.js&lt;/code&gt; file at the bottom of the content page:&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:a16e3dd2-bcb0-4d1d-b419-8a00d99795ca" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="xml"&gt;    &amp;lt;script type="text/javascript" src="&amp;lt;%= Url.Content("~/Scripts/dummyObject.js")%&amp;gt;"&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script type="text/javascript"&amp;gt;
        $(document).ready(
                            function()
                            {
                                dummyObject.init();
                            }
                        );
    &amp;lt;/script&amp;gt;
&amp;lt;/asp:Content&amp;gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you run the above in VS, you will find that VS enters into debug mode with a message “object expected”&amp;#160; like the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://weblogs.asp.net/blogs/rashid/VSException_26393FA1.png"&gt;&lt;img title="VS-Exception" style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="208" alt="VS-Exception" src="http://weblogs.asp.net/blogs/rashid/VSException_thumb_44374D95.png" width="719" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Can you guess what is the problem in the above? yes, before the jQuery framework is downloaded the browser encounter the $ which is not yet defined and starts shooting exceptions.This is a very common gotchas working with Master and Content Pages and it is not ASP.NET MVC specific, the Web Forms also falls into this trap. I guess this is the reason why we have to put the ASP.NET AJAX ScriptManager prior any ajaxable control when working with the Web Forms.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;There are quite a few things, we would like to solve:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Ensure that all script tags are rendered first no matter where it is located (Master Page/Content Page/User Control) before the initialization (&lt;code&gt;document.ready&lt;/code&gt; of jQuery). &lt;/li&gt;

  &lt;li&gt;Merge all initialization statements and put into a single initialization block. The ordering should be Master –&amp;gt; Content Page –&amp;gt; User Control, again no matter how deep the nesting is. &lt;/li&gt;

  &lt;li&gt;Merge all cleanup statements and put into a single cleanup block (&lt;code&gt;$(window).unload&lt;/code&gt; of jQuery) same as above initialization but the ordering should be reverse - User Control –&amp;gt; Content Page – &amp;gt; Master Page. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now meet my tiny little component &lt;code&gt;AssetManagement&lt;/code&gt; which solves the above issues very elegantly.&lt;/p&gt;

&lt;p&gt;First we will see how the above issues can be solved with the controls of &lt;code&gt;AssetManagement&lt;/code&gt;. Yes, it contains very similar controls like ASP.NET Ajax, but the key differences are, you can place it anywhere in the page, it does not generate any extra server tags, just the script tag, no view state, no hidden control nothing, you can easily use it in your ASP.NET MVC application. For the above, we will first put the &lt;code&gt;jQueryScriptManager&lt;/code&gt; in the master page like the following:&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:ed47baff-8bc4-4a58-859e-dd8ee78d2d6b" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="xml"&gt;    &amp;lt;readZ:jQueryScriptManager id="scriptManager" runat="Server"&amp;gt;
        &amp;lt;Scripts&amp;gt;
            &amp;lt;readZ:JavaScriptReference Path="~/Scripts/jquery-1.3.2.min.js"/&amp;gt;
            &amp;lt;readZ:JavaScriptReference Path="~/Scripts/utility.js"/&amp;gt;
        &amp;lt;/Scripts&amp;gt;
        &amp;lt;OnPageLoad&amp;gt;
            utility.init();
        &amp;lt;/OnPageLoad&amp;gt;
    &amp;lt;/readZ:jQueryScriptManager&amp;gt;
&amp;lt;/body&amp;gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next, in the Content Page:&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:8ca39512-c278-40f9-b12b-00a2380b8ba4" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="xml"&gt;    &amp;lt;readZ:JavaScriptManagerProxy id="scriptManagerProxy" runat="Server"&amp;gt;
        &amp;lt;Scripts&amp;gt;
            &amp;lt;readZ:JavaScriptReference Path="~/Scripts/dummyObject.js"/&amp;gt;
        &amp;lt;/Scripts&amp;gt;
        &amp;lt;OnPageLoad&amp;gt;
            dummyObject.init();
        &amp;lt;/OnPageLoad&amp;gt;
    &amp;lt;/readZ:JavaScriptManagerProxy&amp;gt;
&amp;lt;/asp:Content&amp;gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now, when you run, it will generate the following html output:&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:62a11cc2-4684-485a-a509-b434b62c22fe" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="xml"&gt;&amp;lt;script type="text/javascript" src="Scripts/jquery-1.3.2.min.js"&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script type="text/javascript" src="Scripts/utility.js"&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script type="text/javascript" src="Scripts/dummyObject.js"&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script type="text/javascript"&amp;gt;
//&amp;lt;![CDATA[
jQuery(document).ready(function(){
            utility.init();
            dummyObject.init();
});
//]]&amp;gt;
&amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;You can also use the &lt;code&gt;OnPageUnload&lt;/code&gt; property for cleanup. for example:&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:fe6b8695-437e-48c2-b758-1596ee7ed662" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="xml"&gt;&amp;lt;readZ:jQueryScriptManager id="scriptManager" runat="Server"&amp;gt;
    &amp;lt;Scripts&amp;gt;
        &amp;lt;readZ:JavaScriptReference Path="~/Scripts/jquery-1.3.2.min.js"/&amp;gt;
        &amp;lt;readZ:JavaScriptReference Path="~/Scripts/utility.js"/&amp;gt;
    &amp;lt;/Scripts&amp;gt;
    &amp;lt;OnPageLoad&amp;gt;
        utility.init();
    &amp;lt;/OnPageLoad&amp;gt;
    &amp;lt;OnPageUnload&amp;gt;
        alert('Cleanup for master page.');
    &amp;lt;/OnPageUnload&amp;gt;
&amp;lt;/readZ:jQueryScriptManager&amp;gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;And&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:74b59035-7889-49fb-829d-26556f68e619" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="xml"&gt;&amp;lt;readZ:JavaScriptManagerProxy id="scriptManagerProxy" runat="Server"&amp;gt;
    &amp;lt;Scripts&amp;gt;
        &amp;lt;readZ:JavaScriptReference Path="~/Scripts/dummyObject.js"/&amp;gt;
    &amp;lt;/Scripts&amp;gt;
    &amp;lt;OnPageLoad&amp;gt;
        dummyObject.init();
    &amp;lt;/OnPageLoad&amp;gt;
    &amp;lt;OnPageUnload&amp;gt;
        alert('Cleanup for content page.');
    &amp;lt;/OnPageUnload&amp;gt;
&amp;lt;/readZ:JavaScriptManagerProxy&amp;gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It will generate:&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:ffaab289-5643-436a-9dec-086ea97bcd14" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="c#"&gt;&amp;lt;script type="text/javascript" src="Scripts/jquery-1.3.2.min.js"&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script type="text/javascript" src="Scripts/utility.js"&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script type="text/javascript" src="Scripts/dummyObject.js"&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script type="text/javascript"&amp;gt;
//&amp;lt;![CDATA[
jQuery(document).ready(function(){
            utility.init();        
            dummyObject.init();
});

jQuery(window).unload(function(){
            alert('Cleanup for content page.');
            alert('Cleanup for master page.');
});
//]]&amp;gt;
&amp;lt;/script&amp;gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt; Okay, if you have promised that you will not use any server control in your ASP.NET MVC application anymore, here is the fluent version of the exact same thing:&lt;/p&gt;

&lt;p&gt;Master Page:&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:80b1a95e-0e57-4296-a833-217c7c04f512" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="xml"&gt;&amp;lt;%= Html.jQuery().Scripts(
                            script =&amp;gt;
                            {
                                script.Source("http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js");
                                script.Source("~/Scripts/utility.js");
                            }
                         )
                 .OnPageLoad("utility.init();")
                 .OnPageUnload("alert('Cleanup for master page.');")
%&amp;gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Content Page:&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:6876168d-3290-4988-800d-0e90b753a11f" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="c#"&gt;&amp;lt;% Html.jQuery().Scripts(script =&amp;gt; script.Source("~/Scripts/dummyObject.js"))
                 .OnPageLoad("dummyObject.init();")
                 .OnPageUnload("alert('Cleanup for content page.');");
%&amp;gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;And it will generate the same output as the controls. Note that for Content Page we are only using &amp;lt;% but for the master page we are using &amp;lt;%= which means we want to dump the result. Also the fluent syntax is progressive interface (inspired by this &lt;a href="http://elegantcode.com/2009/03/21/progressive-interfaces/" target="_blank"&gt;excellent post&lt;/a&gt; of&amp;#160; Jan Van Ryswyck) which means once you call a method, it will show the only available methods in that context like the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://weblogs.asp.net/blogs/rashid/AssetFluentSyntax_20BAA930.png"&gt;&lt;img title="AssetFluentSyntax" style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="210" alt="AssetFluentSyntax" src="http://weblogs.asp.net/blogs/rashid/AssetFluentSyntax_thumb_105F3177.png" width="765" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;The component also contains an &lt;code&gt;HttpHandler&lt;/code&gt; which you can use to combine multiple css and javascript files, the above codes can also refer these assets. To use the asset handler, you have to first define the assets in the &lt;code&gt;web.config&lt;/code&gt; like the following:&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:e0e6b161-de72-4441-af07-091a4836ca83" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="xml"&gt;&amp;lt;configSections&amp;gt;
    &amp;lt;section name="assetSettings" type="ReadZ.AssetManagement.AssetSettingsSection, ReadZ.AssetManagement" requirePermission="false"/&amp;gt;
&amp;lt;/configSections&amp;gt;
&amp;lt;assetSettings version="1.0.0.0" cacheDurationInDays="365" compress="true"&amp;gt;
    &amp;lt;assets&amp;gt;
        &amp;lt;clear/&amp;gt;
        &amp;lt;add
            name="css"
            contentType="text/css"
            directory="~/assets/css"
            files="site.min.css;ui.jquery.min.css;marItUp.min.css;colorPicker.min.css"
        /&amp;gt;
        &amp;lt;add
            name="js2"
            contentType="application/x-javascript"
            directory="~/assets/scripts"
            files="OpenID.min.js;jquery-1.2.6.min.js;jquery.form.min.js;jquery.validate.min.js;ui.core.min.js;ui.tabs.min.js;ui.draggable.min.js;ui.resizable.min.js;ui.dialog.min.js;Utility.min.js;Search.min.js;Tag.min.js;Membership.min.js;Story.min.js;Analytics.min.js"
        /&amp;gt;
        &amp;lt;add
            name="js3"
            contentType="application/x-javascript"
            directory="~/assets/scripts"
            files="ui.autocomplete.min.js;jquery.markitup.min.js;showdown.js;colorpicker.min.js;RichEditor.min.js;ImageCode.min.js;Comment.min.js"
        /&amp;gt;
    &amp;lt;/assets&amp;gt;
    &amp;lt;system.web&amp;gt;
        &amp;lt;pages&amp;gt;
            &amp;lt;controls&amp;gt;
                &amp;lt;add tagPrefix="readZ" namespace="ReadZ.AssetManagement" assembly="ReadZ.AssetManagement"/&amp;gt;
                &amp;lt;add tagPrefix="readZ" namespace="ReadZ.AssetManagement.Controls" assembly="ReadZ.AssetManagement"/&amp;gt;
            &amp;lt;/controls&amp;gt;
            &amp;lt;namespaces&amp;gt;
                &amp;lt;add namespace="ReadZ.AssetManagement"/&amp;gt;
                &amp;lt;add namespace="ReadZ.AssetManagement.HtmlHelpers"/&amp;gt;
            &amp;lt;/namespaces&amp;gt;
        &amp;lt;/pages&amp;gt;
        &amp;lt;httpHandlers&amp;gt;
            &amp;lt;add verb="GET,HEAD" path="asset.axd" validate="false" type="ReadZ.AssetManagement.AssetHandler, ReadZ.AssetManagement"/&amp;gt;
        &amp;lt;/httpHandlers&amp;gt;
    &amp;lt;/system.web&amp;gt;
    &amp;lt;system.webServer&amp;gt;
        &amp;lt;handlers&amp;gt;
            &amp;lt;remove name="AssetHandler"/&amp;gt;
            &amp;lt;add name="AssetHandler" preCondition="integratedMode" verb="GET,HEAD" path="asset.axd" type="ReadZ.AssetManagement.AssetHandler, ReadZ.AssetManagement"/&amp;gt;
        &amp;lt;/handlers&amp;gt;
    &amp;lt;/system.webServer&amp;gt;
&amp;lt;/assetSettings&amp;gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As you can see, each asset has an unique name, the content type and number of files with the location. When an asset is requested it will merge the specified files into a single response, cache and compress it before sending the response. You can define global version, cache duration and compression (line 4) which will apply to all, you can also override the global settings as per asset. When referring these assets in your code you can use:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For Control:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:4ccfe9c2-69e8-4960-b930-0c291367c027" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="xml"&gt;&amp;lt;readZ:jQueryScriptManager id="scriptManager" runat="Server"&amp;gt;
    &amp;lt;Scripts&amp;gt;
        &amp;lt;readZ:JavaScriptReference AssetName="js2"/&amp;gt;
    &amp;lt;/Scripts&amp;gt;
&amp;lt;/readZ:jQueryScriptManager&amp;gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;For Fluent Html:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:ce1cec23-909b-4745-bbf4-4cfb759817d5" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="xml"&gt;&amp;lt;%= Html.jQuery().Scripts(script =&amp;gt; script.Asset("js2")) %&amp;gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;When referring the CSS, you can use:&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:cd136269-2df3-481d-9149-675fad33658e" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="xml"&gt;&amp;lt;head runat="server"&amp;gt;
    &amp;lt;title&amp;gt;&amp;lt;asp:ContentPlaceHolder ID="TitleContent" runat="server" /&amp;gt;&amp;lt;/title&amp;gt;
    &amp;lt;link href="&amp;lt;%= Url.Asset("css")%&amp;gt;" rel="stylesheet" type="text/css"/&amp;gt;
&amp;lt;/head&amp;gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The component has the extension methods for both &lt;code&gt;UrlHelper&lt;/code&gt; and &lt;code&gt;HtmlHelper&lt;/code&gt;, so that you can use it conveniently in your ASP.NET MVC views.&lt;/p&gt;

&lt;p&gt;So far the codes that I have shown is for jQuery only, If you are thinking what about the others like ExtJS, prototype etc. The component has some nice extensibilities which you can use to support other popular libraries as well. For example, lets add the support for the ExtJS. First you have to create a class which implements the &lt;code&gt;IScriptWrapper&lt;/code&gt; interface, next add codes for &lt;code&gt;WrapOnPageLoad&lt;/code&gt; and &lt;code&gt;WrapOnPageUnload&lt;/code&gt; methods like the following:&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:5e105b0c-ab97-4dab-b945-6fc6b849500a" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="c#"&gt;public class ExtJSScriptWrapper : IScriptWrapper
{
    public string WrapOnPageLoad(string scripts)
    {
        return string.IsNullOrEmpty((scripts ?? string.Empty).Trim()) ? string.Empty : string.Concat("Ext.onReady(function(){\r\n", scripts, "\r\n});");
    }

    public string WrapOnPageUnload(string scripts)
    {
        return string.IsNullOrEmpty((scripts ?? string.Empty).Trim()) ? string.Empty : string.Concat("Ext.EventManager.on(window, 'unload', function(){\r\n", scripts, "\r\n});");
    }
}&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Control:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Next create a new control which inherits from base &lt;code&gt;JavaScriptManager&lt;/code&gt; and create a new instance of the above wrapper in the constructor and that’s it.&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:25486fec-992c-4ad9-a55f-218a8dc98e7b" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="c#"&gt;public class ExtJSScriptManager : JavaScriptManager
{
    public ExtJSScriptManager() : base(new ExtJSScriptWrapper())
    {
    }
}&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Now you will be able to use the &lt;code&gt;ExtJSScriptManager&lt;/code&gt; like the same way as we did with &lt;code&gt;jQueryScriptManager&lt;/code&gt; in the above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fluent Html:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First, create a class which inherits from base &lt;code&gt;AbstractScriptHtmlHelper&lt;/code&gt; and create a new instance of the above wrapper in the constructor:&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:bb7c88d1-0a54-4948-9bc1-adb43f1f9ce2" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="c#"&gt;public class ExtJSScriptHtmlHelper : AbstractScriptHtmlHelper
{
    public ExtJSScriptHtmlHelper(HtmlHelper htmlHelper) : base(new ExtJSScriptWrapper(), htmlHelper)
    {
    }
}&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next create an extension method for the &lt;code&gt;HtmlHelper&lt;/code&gt; which will return it.&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:8c37e041-ee58-4a03-a0ef-9dcda9cf6886" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="c#"&gt;public static class HtmlHelperExtension
{
    public static ExtJSScriptHtmlHelper ExtJS(this HtmlHelper helper)
    {
        return new ExtJSScriptHtmlHelper(helper);
    }
}&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And that’s it, now you will be able to refer ExtJS in the mvc view.&lt;/p&gt;

&lt;p&gt;The component also has the unit tests with the amazing duo (&lt;a href="http://xunit.codeplex.com/" target="_blank"&gt;xUnit&lt;/a&gt; + &lt;a href="http://code.google.com/p/moq/" target="_blank"&gt;Moq&lt;/a&gt;) and interestingly I was able to achieve 100% code coverage which is worth to check (Yes you will find some unnecessary tests, I did it intentionally to achieve more code coverage).&lt;/p&gt;

&lt;p&gt;&lt;a href="http://weblogs.asp.net/blogs/rashid/CodeCoverage_7D3E04CA.png"&gt;&lt;img title="CodeCoverage" style="border-right: 0px; border-top: 0px; display: inline; border-left: 0px; border-bottom: 0px" height="194" alt="CodeCoverage" src="http://weblogs.asp.net/blogs/rashid/CodeCoverage_thumb_4E0C1933.png" width="456" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;You can download the above sample code and the component from the bottom of this post.&amp;#160; If you have any feedback/bug report/enhancements, do let me know.&lt;/p&gt;

&lt;p&gt;Happy YSlow scoring in your ASP.NET MVC App!!!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Download&lt;/strong&gt;: &lt;a href="http://weblogs.asp.net/blogs/rashid/Asset.Management.zip" target="_blank"&gt;Source Code&lt;/a&gt;&lt;/p&gt;&lt;div class="wlWriterHeaderFooter" style="margin:0px; padding:0px 0px 0px 0px;"&gt;&lt;div class="shoutIt"&gt;&lt;a rev="vote-for" href="http://dotnetshoutout.com/Submit?url=http%3a%2f%2fweblogs.asp.net%2frashid%2farchive%2f2009%2f04%2f28%2fscript-and-css-management-in-asp-net-mvc.aspx&amp;amp;title=Script+and+CSS+Management+in+ASP.NET+MVC"&gt;&lt;img alt="Shout it" src="http://dotnetshoutout.com/image.axd?url=http://weblogs.asp.net/rashid/archive/2009/04/28/script-and-css-management-in-asp-net-mvc.aspx" style="border:0px" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;img src="http://weblogs.asp.net/aggbug.aspx?PostID=7062177" width="1" height="1"&gt;</description><category domain="http://weblogs.asp.net/rashid/archive/tags/Asp.net/default.aspx">Asp.net</category><category domain="http://weblogs.asp.net/rashid/archive/tags/MVC/default.aspx">MVC</category><category domain="http://weblogs.asp.net/rashid/archive/tags/DotNetShoutout/default.aspx">DotNetShoutout</category><category domain="http://weblogs.asp.net/rashid/archive/tags/ASPNETMVC/default.aspx">ASPNETMVC</category><category domain="http://weblogs.asp.net/rashid/archive/tags/KiGG/default.aspx">KiGG</category><category domain="http://weblogs.asp.net/rashid/archive/tags/ASP.NET+MVC/default.aspx">ASP.NET MVC</category><category domain="http://weblogs.asp.net/rashid/archive/tags/Unit+Test/default.aspx">Unit Test</category><category domain="http://weblogs.asp.net/rashid/archive/tags/JavaScript/default.aspx">JavaScript</category></item><item><title>AdRotator for ASP.NET MVC</title><link>http://weblogs.asp.net/rashid/archive/2009/04/20/adrotator-for-asp-net-mvc.aspx</link><pubDate>Mon, 20 Apr 2009 02:32:49 GMT</pubDate><guid isPermaLink="false">c06e2b9d-981a-45b4-a55f-ab0d8bbfdc1c:7050356</guid><dc:creator>kazimanzurrashid</dc:creator><slash:comments>10</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/rashid/rsscomments.aspx?PostID=7050356</wfw:commentRss><wfw:comment xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/rashid/commentapi.aspx?PostID=7050356</wfw:comment><comments>http://weblogs.asp.net/rashid/archive/2009/04/20/adrotator-for-asp-net-mvc.aspx#comments</comments><description>&lt;p&gt;I was looking&amp;#160; for an Ad Rotator for &lt;a href="http://dotnetshoutout.com/" target="_blank"&gt;DotNetShoutout&lt;/a&gt;, as the sponsors are coming and most of them wants to show different images for the site and as well as in the feed. Certainly, I can use the built-in AdRotator control for the site, but it does not at all feels MVC-ish. So I decided to create a small class which mimics the same behavior as the original control. &lt;/p&gt;  &lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:486ac164-db12-4f5b-bd76-4a79c2620505" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="c#"&gt;namespace Kigg.Web
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web.Mvc;
    using System.Web.Routing;

    public class Ad
    {
        public string NavigateUrl
        {
            get;
            set;
        }

        public string Target
        {
            get;
            set;
        }

        public object LinkAttributes
        {
            get;
            set;
        }

        public string ImageUrl
        {
            get;
            set;
        }

        public string AlternateText
        {
            get;
            set;
        }

        public object ImageAttributes
        {
            get;
            set;
        }

        public string Keyword
        {
            get;
            set;
        }

        public int Impressions
        {
            get;
            set;
        }

        public static string Rotate(string keywordFilter, params Ad[] ads)
        {
            Ad ad = PickAd(keywordFilter, ads);

            string html = (ad == null) ? string.Empty : GenerateHtml(ad);

            return html;
        }

        public static string Rotate(params Ad[] ads)
        {
            return Rotate(null, ads);
        }

        private static Ad PickAd(string keywordFilter, params Ad[] ads)
        {
            Ad targetAd = null;

            IList&amp;lt;Ad&amp;gt; matchedAds = ads.Where(ad =&amp;gt; string.Compare(ad.Keyword, keywordFilter, StringComparison.InvariantCultureIgnoreCase) == 0)
                                      .OrderBy(ad =&amp;gt; ad.Impressions)
                                      .ToList();

            if (matchedAds.Count &amp;gt; 0)
            {
                int max = matchedAds.Sum(ad =&amp;gt; ad.Impressions);
                int random = new Random().Next(max + 1);
                int runningTotal = 0;

                foreach(Ad ad in matchedAds)
                {
                    runningTotal += ad.Impressions;

                    if (random &amp;lt;= runningTotal)
                    {
                        targetAd = ad;
                        break;
                    }
                }

                if (targetAd == null)
                {
                    targetAd = matchedAds.Last();
                }
            }

            return targetAd;
        }

        private static string GenerateHtml(Ad ad)
        {
            Action&amp;lt;TagBuilder, object&amp;gt; merge = (builder, values) =&amp;gt;
                                               {
                                                   if (values != null)
                                                   {
                                                       builder.MergeAttributes(new RouteValueDictionary(values));
                                                   }
                                               };

            Action&amp;lt;TagBuilder, string, string&amp;gt; mergeIfNotBlank =    (builder, name, value) =&amp;gt;
                                                                    {
                                                                         if (!string.IsNullOrEmpty(value))
                                                                         {
                                                                             builder.MergeAttribute(name, value, true);
                                                                         }
                                                                    };

            TagBuilder imageBuilder = new TagBuilder("img");

            merge(imageBuilder, ad.ImageAttributes);
            mergeIfNotBlank(imageBuilder, "src", ad.ImageUrl);
            mergeIfNotBlank(imageBuilder, "alt", ad.AlternateText);

            if (!imageBuilder.Attributes.ContainsKey("alt"))
            {
                imageBuilder.Attributes.Add("alt", string.Empty);
            }

            TagBuilder linkBuilder = new TagBuilder("a");

            merge(linkBuilder, ad.LinkAttributes);
            mergeIfNotBlank(linkBuilder, "href", ad.NavigateUrl);
            mergeIfNotBlank(linkBuilder, "target", ad.Target);

            linkBuilder.InnerHtml = imageBuilder.ToString(TagRenderMode.SelfClosing);

            return linkBuilder.ToString();
        }
    }
}&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And in the view I can use it like the following:&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:0e40be1d-6eb3-40cc-9c10-abfc33135d4b" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="xml"&gt;&amp;lt;%= Ad.Rotate(
                new Ad { NavigateUrl = "http://mysponsor1.com?tid=1", LinkAttributes = new { rel = "nofollow" }, ImageUrl = Url.Image("sponsors/mysponsor1/1.gif"), AlternateText = "My Cool Sponsor", ImageAttributes = new { style = "border:0"}, Impressions = 60 },
                new Ad { NavigateUrl = "http://mysponsor1.com?tid=2", LinkAttributes = new { rel = "nofollow" }, ImageUrl = Url.Image("sponsors/mysponsor1/2.gif"), AlternateText = "My Cool Sponsor", ImageAttributes = new { style = "border:0" }, Impressions = 40 },
                new Ad { NavigateUrl = "http://mysponsor1.com?tid=3", LinkAttributes = new { rel = "nofollow" }, ImageUrl = Url.Image("sponsors/mysponsor1/3.gif"), AlternateText = "My Cool Sponsor", ImageAttributes = new { style = "border:0"}, Impressions = 20 }
             )%&amp;gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It will randomly rotate based upon the impression value like the original control.&amp;#160; Though keyword filtering is not required for &lt;a href="http://dotnetshoutout.com/" target="_blank"&gt;DotNetShoutout&lt;/a&gt;, but I have implemented it, maybe you can find it useful.&lt;/p&gt;&lt;div class="wlWriterHeaderFooter" style="margin:0px; padding:0px 0px 0px 0px;"&gt;&lt;div class="shoutIt"&gt;&lt;a rev="vote-for" href="http://dotnetshoutout.com/Submit?url=http%3a%2f%2fweblogs.asp.net%2frashid%2farchive%2f2009%2f04%2f20%2fadrotator-for-asp-net-mvc.aspx&amp;amp;title=AdRotator+for+ASP.NET+MVC"&gt;&lt;img alt="Shout it" src="http://dotnetshoutout.com/image.axd?url=http://weblogs.asp.net/rashid/archive/2009/04/20/adrotator-for-asp-net-mvc.aspx" style="border:0px" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;img src="http://weblogs.asp.net/aggbug.aspx?PostID=7050356" width="1" height="1"&gt;</description><category domain="http://weblogs.asp.net/rashid/archive/tags/Asp.net/default.aspx">Asp.net</category><category domain="http://weblogs.asp.net/rashid/archive/tags/MVC/default.aspx">MVC</category><category domain="http://weblogs.asp.net/rashid/archive/tags/DotNetShoutout/default.aspx">DotNetShoutout</category><category domain="http://weblogs.asp.net/rashid/archive/tags/ASPNETMVC/default.aspx">ASPNETMVC</category><category domain="http://weblogs.asp.net/rashid/archive/tags/ASP.NET+MVC/default.aspx">ASP.NET MVC</category></item><item><title>KiGG is now upgraded to ASP.NET MVC RTM 1.0</title><link>http://weblogs.asp.net/rashid/archive/2009/04/07/kigg-is-now-upgraded-to-asp-net-mvc-rtm-1-0.aspx</link><pubDate>Tue, 07 Apr 2009 12:02:46 GMT</pubDate><guid isPermaLink="false">c06e2b9d-981a-45b4-a55f-ab0d8bbfdc1c:7032967</guid><dc:creator>kazimanzurrashid</dc:creator><slash:comments>7</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/rashid/rsscomments.aspx?PostID=7032967</wfw:commentRss><wfw:comment xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/rashid/commentapi.aspx?PostID=7032967</wfw:comment><comments>http://weblogs.asp.net/rashid/archive/2009/04/07/kigg-is-now-upgraded-to-asp-net-mvc-rtm-1-0.aspx#comments</comments><description>&lt;p&gt;Just to let you know that I have uploaded the latest source of &lt;a href="http://kigg.codeplex.com" target="_blank"&gt;KiGG&lt;/a&gt; in Codeplex. Other than upgrading to ASP.NET MVC RTM, there are few enhancements:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Implementing EventAggregator. &lt;/li&gt;    &lt;li&gt;Background Services like:      &lt;ul&gt;       &lt;li&gt;Broadcast in Twitter. &lt;/li&gt;        &lt;li&gt;Ping different Feed Servers automatically. &lt;/li&gt;     &lt;/ul&gt;   &lt;/li&gt;    &lt;li&gt;Restrict Story submit from specific domains. &lt;/li&gt;    &lt;li&gt;SQL Server Full Text search. &lt;/li&gt;    &lt;li&gt;Other minor enhancements and bug fixes. &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;And yes &lt;a href="http://dotnetshoutout.com"&gt;http://dotnetshoutout.com&lt;/a&gt; is currently running the same codebase. Those who are assuming whether &lt;a href="http://kigg.codeplex.com" target="_blank"&gt;KiGG&lt;/a&gt; is following the same practices which I have mentioned recently over &lt;a href="http://weblogs.asp.net/rashid/archive/2009/04/01/asp-net-mvc-best-practices-part-1.aspx" target="_blank"&gt;here&lt;/a&gt; and &lt;a href="http://weblogs.asp.net/rashid/archive/2009/04/03/asp-net-mvc-best-practices-part-2.aspx" target="_blank"&gt;here&lt;/a&gt;, let me tell you, no it is not following all the items, I am in a process of refactoring it following those rest of the items which I hope to deliver in a few weeks or I will post the tiny app that I am also developing.&lt;/p&gt;  &lt;p&gt;Stay tuned.&lt;/p&gt;&lt;div class="wlWriterHeaderFooter" style="margin:0px; padding:0px 0px 0px 0px;"&gt;&lt;div class="shoutIt"&gt;&lt;a rev="vote-for" href="http://dotnetshoutout.com/Submit?url=http%3a%2f%2fweblogs.asp.net%2frashid%2farchive%2f2009%2f04%2f07%2fkigg-is-now-upgraded-to-asp-net-mvc-rtm-1-0.aspx&amp;amp;title=KiGG+is+now+upgraded+to+ASP.NET+MVC+RTM+1.0"&gt;&lt;img alt="Shout it" src="http://dotnetshoutout.com/image.axd?url=http://weblogs.asp.net/rashid/archive/2009/04/07/kigg-is-now-upgraded-to-asp-net-mvc-rtm-1-0.aspx" style="border:0px" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;img src="http://weblogs.asp.net/aggbug.aspx?PostID=7032967" width="1" height="1"&gt;</description><category domain="http://weblogs.asp.net/rashid/archive/tags/Asp.net/default.aspx">Asp.net</category><category domain="http://weblogs.asp.net/rashid/archive/tags/DotNetShoutout/default.aspx">DotNetShoutout</category><category domain="http://weblogs.asp.net/rashid/archive/tags/ASPNETMVC/default.aspx">ASPNETMVC</category><category domain="http://weblogs.asp.net/rashid/archive/tags/KiGG/default.aspx">KiGG</category><category domain="http://weblogs.asp.net/rashid/archive/tags/ASP.NET+MVC/default.aspx">ASP.NET MVC</category></item><item><title>Use Event Aggregator to make your application more extensible</title><link>http://weblogs.asp.net/rashid/archive/2009/03/05/use-event-aggregator-to-make-your-application-more-extensible.aspx</link><pubDate>Thu, 05 Mar 2009 14:35:28 GMT</pubDate><guid isPermaLink="false">c06e2b9d-981a-45b4-a55f-ab0d8bbfdc1c:6938393</guid><dc:creator>kazimanzurrashid</dc:creator><slash:comments>6</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/rashid/rsscomments.aspx?PostID=6938393</wfw:commentRss><wfw:comment xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/rashid/commentapi.aspx?PostID=6938393</wfw:comment><comments>http://weblogs.asp.net/rashid/archive/2009/03/05/use-event-aggregator-to-make-your-application-more-extensible.aspx#comments</comments><description>&lt;p&gt;Recently, in &lt;a href="http://codeplex.com/Kigg" target="_blank"&gt;KiGG&lt;/a&gt;/&lt;a href="http://dotnetshoutout.com/" target="_blank"&gt;DotNetShoutout&lt;/a&gt; we have integrated &lt;a href="http://twitter.com/dotnetshoutout/" target="_blank"&gt;Twitter&lt;/a&gt;, nothing complex, very basic thing like when a story is submitted or appears in the front page it will broadcast in Twitter and like our feed it will post the short url of the original story (Cant resist to do some shameless marketing for &lt;a href="http://dotnetshoutout.com/" target="_blank"&gt;DotNetShoutout&lt;/a&gt;).&lt;/p&gt;  &lt;p&gt;Since it is a new requirement, initially I have modified the &lt;font face="Courier New"&gt;StoryService&lt;/font&gt; constructor to include the twitter client as a new argument and use it after the story is added in the database, the code is something like the following:&lt;/p&gt;  &lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:9bfefbac-cf47-4e6c-8c86-22799e0c6b9d" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="c#"&gt;//Other codes

//Add it in database
_storyRepository.Add(story);

//Increase User Score
_userScoreService.StorySubmitted(byUser);

// Send Trackback
PingStory(content, story, detailUrl);

//Ping the Feed Servers
PingServers();

//Send Tweet
_twitter.UpdateStatus(story);

result = new StoryCreateResult { NewStory = story, DetailUrl = detailUrl };

return result;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As you can see there are quite a few things that I have do:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Increase the User Score. &lt;/li&gt;

  &lt;li&gt;Send Trackback (Thank you for submitting this cool story) &lt;/li&gt;

  &lt;li&gt;Ping Feedburner/Pingomatic/technorati servers. &lt;/li&gt;

  &lt;li&gt;Send Tweet. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So for this new requirement we have to modify it and will again have to do if we add more features in future and we do have a plan to implement the Spy/Log (don’t confuse it with the code logger, it is ajax view which shows what is happening currently in the application) and this obviously not a path we should follow. It violets the OCP completely, tie us to write some transactional script&amp;#160; kind of code. We need to find a way where we can add new features without modifying the &lt;font face="Courier New"&gt;StoryService&lt;/font&gt;. One option would be to pass an array of &lt;font face="Courier New"&gt;IBackgroundService&lt;/font&gt; (As these are noting but some background services)in the &lt;font face="Courier New"&gt;StoryService&lt;/font&gt; constructor which these classes will implement and later on the &lt;font face="Courier New"&gt;StoryService&lt;/font&gt; will call the &lt;font face="Courier New"&gt;Execute&lt;/font&gt; method of each background service. But Story Submit, Story Publish is more like a system event which should be globally available.&lt;/p&gt;

&lt;h3&gt;Enter Event Aggregator&lt;/h3&gt;

&lt;p&gt;As per &lt;a href="http://martinfowler.com/eaaDev/EventAggregator.html" target="_blank"&gt;Martin Fowler&lt;/a&gt; “An Event Aggregator acts as a single source of events for many objects. It registers for all the events of the many objects allowing clients to register with just the aggregator.” &lt;a href="http://codebetter.com/blogs/jeremy.miller/" target="_blank"&gt;Jeremy D Miller&lt;/a&gt; also used this pattern in his excellent &lt;a href="http://codebetter.com/blogs/jeremy.miller/archive/2008/01/11/build-your-own-cab-extensible-pub-sub-event-aggregator-with-generics.aspx" target="_blank"&gt;Build your own CAB series&lt;/a&gt;. And recently I was browsing through the &lt;a href="http://compositewpf.codeplex.com/" target="_blank"&gt;CompositeWpf/Prism&lt;/a&gt; code and found the &lt;font face="Courier New"&gt;EventAggregator&lt;/font&gt; which seems a perfect candidate for my situation. According to its &lt;a href="http://msdn.microsoft.com/en-us/library/dd458915.aspx" target="_blank"&gt;documentation&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;“The EventAggregator service is primarily a container for events that allow decoupling of publishers and subscribers so they can evolve independently.”&lt;/p&gt;

&lt;p&gt;&lt;a href="http://weblogs.asp.net/blogs/rashid/EventAggregator_62D394A8.png"&gt;&lt;img title="EventAggregator" style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="557" alt="EventAggregator" src="http://weblogs.asp.net/blogs/rashid/EventAggregator_thumb_2325E5BE.png" width="800" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;But it has some dependency on .NET WPF which I would like to remove, and after talking with &lt;a href="http://codebetter.com/blogs/glenn.block/" target="_blank"&gt;Glen Block&lt;/a&gt;, he confirmed that since it is licensed under MS-PL, I can copy and modify it according to my need. Also most of the example you will find of Event Aggregator demonstrate the UI events, but it is a very powerful pattern and can be used in non UI events too which we will see next.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;As mentioned in the above that &lt;font face="Courier New"&gt;UserScoreService&lt;/font&gt;, &lt;font face="Courier New"&gt;PingStory&lt;/font&gt;, &lt;font face="Courier New"&gt;PingServer&lt;/font&gt;, &lt;font face="Courier New"&gt;SendTweet&lt;/font&gt; is some kind of background task which can be run independently and it will act as subscriber in the above diagram, so lets create an interface:&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:68ebaaa5-d82f-4db2-b61b-cfb33e200c50" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="c#"&gt;public interface IBackgroundTask
{
    bool IsRunning
    {
        get;
    }

    void Start();

    void Stop();
}&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next we will create an abstract class which will implement this interface to reduce the duplicate codes:&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:b5fc14c6-f3f6-4ffd-afbc-00e2b15f56bd" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="c#"&gt;public abstract class BaseBackgroundTask : IBackgroundTask
{
    private readonly IEventAggregator _eventAggregator;

    protected BaseBackgroundTask(IEventAggregator eventAggregator)
    {
        if (eventAggregator == null)
        {
            throw new ArgumentNullException("eventAggregator");
        }

        _eventAggregator = eventAggregator;
    }

    public bool IsRunning
    {
        get;
        private set;
    }

    protected IEventAggregator EventAggregator
    {
        get
        {
            return _eventAggregator;
        }
    }

    public void Start()
    {
        OnStart();
        IsRunning = true;
    }

    public void Stop()
    {
        OnStop();
        IsRunning = false;
    }

    protected abstract void OnStart();

    protected abstract void OnStop();

    protected SubscriptionToken Subscribe&amp;lt;TEvent, TEventArgs&amp;gt;(Action&amp;lt;TEventArgs&amp;gt; action) where TEvent : BaseEvent&amp;lt;TEventArgs&amp;gt; where TEventArgs : class
    {
        return EventAggregator.GetEvent&amp;lt;TEvent&amp;gt;().Subscribe(action, true);
    }

    protected void Unsubscribe&amp;lt;TEvent&amp;gt;(SubscriptionToken token) where TEvent : BaseEvent
    {
        EventAggregator.GetEvent&amp;lt;TEvent&amp;gt;().Unsubscribe(token);
    }
}&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now lets create some concrete implementation of these background tasks.&lt;/p&gt;

&lt;p&gt;First, the Twitter:&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:f98aa985-6ad1-449b-962e-6b584dc055ac" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="c#"&gt;public class SendTweet : BaseBackgroundTask
{
    private SubscriptionToken _storySubmitToken;

    public SendTweet(IEventAggregator eventAggregator) : base(eventAggregator)
    {
    }

    protected override void OnStart()
    {
        if (!IsRunning)
        {
            _storySubmitToken = Subscribe&amp;lt;StorySubmitEvent, StorySubmitEventArgs&amp;gt;(StorySubmitted);
        }
    }

    protected override void OnStop()
    {
        if (IsRunning)
        {
            Unsubscribe&amp;lt;StorySubmitEvent&amp;gt;(_storySubmitToken);
        }
    }

    private void StorySubmitted(StorySubmitEventArgs eventArgs)
    {
        UpdateStatus(eventArgs.Story);
    }

    private void UpdateStatus(Story story)
    {
        //Update Twitter status goes here
    }
}&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next the UserScoreService:&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:baa09bd9-2598-4629-bf77-1b7b3cae0e73" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="c#"&gt;public class UserScoreService : BaseBackgroundTask
{
    private SubscriptionToken _storySubmitToken;

    public UserScoreService(IEventAggregator eventAggregator) : base(eventAggregator)
    {
    }

    protected override void OnStart()
    {
        if (!IsRunning)
        {
            _storySubmitToken = Subscribe&amp;lt;StorySubmitEvent, StorySubmitEventArgs&amp;gt;(StorySubmitted);
        }
    }

    protected override void OnStop()
    {
        if (IsRunning)
        {
            Unsubscribe&amp;lt;StorySubmitEvent&amp;gt;(_storySubmitToken);
        }
    }

    private void StorySubmitted(StorySubmitEventArgs eventArgs)
    {
        IncreaseUserScore(eventArgs.Story.PostedBy);
    }

    private void IncreaseUserScore(User user)
    {
        // Increase user score goes here
    }
}&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I am skipping the &lt;font face="Courier New"&gt;PingStory&lt;/font&gt; and &lt;font face="Courier New"&gt;PingServer&lt;/font&gt; as they are very much same as above. &lt;/p&gt;

&lt;p&gt;Next the published part, now the &lt;font face="Courier New"&gt;StoryService&lt;/font&gt; will forward the &lt;font face="Courier New"&gt;StorySubmit&lt;/font&gt; event to &lt;font face="Courier New"&gt;EventAggregator&lt;/font&gt; instead of calling these class individually.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:3bb33836-0fb2-40f0-897e-fbb45b1c17fb" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="c#"&gt;//Other codes

//Add it in database
_storyRepository.Add(story);

//Publish the event
_eventAggregator.GetEvent&amp;lt;StorySubmitEvent&amp;gt;().Publish(new StorySubmitEventArgs(story, detailUrl));

result = new StoryCreateResult { NewStory = story, DetailUrl = detailUrl };

return result;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;And now we have to ensure that these background services can start listing this event. So lets create a &lt;font face="Courier New"&gt;BootstrapperTask&lt;/font&gt; which I have shown in my &lt;a href="http://weblogs.asp.net/rashid/archive/2009/02/17/use-bootstrapper-in-your-asp-net-mvc-application-and-reduce-code-smell.aspx" target="_blank"&gt;previous post&lt;/a&gt;.&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:bcf1556a-42c2-4b4f-9378-0b8419d1e103" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="c#"&gt;public class StartBackgroundTasks : IBootstrapperTask
{
    private readonly IBackgroundTask[] _tasks;

    public StartBackgroundTasks(IBackgroundTask[] tasks)
    {
        _tasks = tasks;
    }

    public void Execute()
    {
        foreach(IBackgroundTask task in _tasks)
        {
            task.Start();
        }
    }
}&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;And at last the wiring of Unity container, I am using the fluent version but you can also use the xml configuration if you want.&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:f0874b46-aebf-47ba-896f-dcff7604baa7" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="c#"&gt;IUnityContainer container = new UnityContainer();

container.RegisterType&amp;lt;IBackgroundTask, UserScoreService&amp;gt;("userScore", new ContainerControlledLifetimeManager())
         .RegisterType&amp;lt;IBackgroundTask, SendTweet&amp;gt;("sendTweet", new ContainerControlledLifetimeManager())
         .RegisterType&amp;lt;IBackgroundTask, PingStory&amp;gt;("pingStory", new ContainerControlledLifetimeManager())
         .RegisterType&amp;lt;IBackgroundTask, PingServer&amp;gt;("pingServer", new ContainerControlledLifetimeManager())
         .RegisterType&amp;lt;IEventAggregator, EventAggregator&amp;gt;(new ContainerControlledLifetimeManager())
         .RegisterType&amp;lt;IControllerFactory, CommonServiceLocatorControllerFactory&amp;gt;(new ContainerControlledLifetimeManager())
         .RegisterType&amp;lt;IBootstrapperTask, RegisterRoutes&amp;gt;("route", new ContainerControlledLifetimeManager(), new InjectionConstructor(new [] { RouteTable.Routes}))
         .RegisterType&amp;lt;IBootstrapperTask, RegisterControllerFactory&amp;gt;("controllerFactory", new ContainerControlledLifetimeManager())
         .RegisterType&amp;lt;IBootstrapperTask, StartBackgroundTasks&amp;gt;("startBackgroundTasks", new ContainerControlledLifetimeManager())
         .RegisterType&amp;lt;IFormsAuthentication, FormsAuthenticationService&amp;gt;(new ContainerControlledLifetimeManager())
         .RegisterType&amp;lt;IMembershipService, AccountMembershipService&amp;gt;(new InjectionConstructor())
         .RegisterType&amp;lt;AccountController&amp;gt;()
         .RegisterType&amp;lt;HomeController&amp;gt;();

ServiceLocator.SetLocatorProvider(() =&amp;gt; new UnityServiceLocator(container));&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;My initial plan was to refer you the &lt;a href="http://codeplex.com/Kigg" target="_blank"&gt;KiGG&lt;/a&gt;/&lt;a href="http://dotnetshoutout.com/" target="_blank"&gt;DotNetShoutout&lt;/a&gt; source code rather than posting a separate version, but as you know that &lt;a href="http://haacked.com/archive/2009/03/03/aspnetmvc-changes-for-rc2.aspx" target="_blank"&gt;ASP.NET MVC RC2 has released&lt;/a&gt; few days ago I would rather upgrade it to that version.&lt;/p&gt;

&lt;p&gt;Further Reference:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://denisvuyka.wordpress.com/2009/02/21/using-eventaggregator-with-mef/" target="_blank"&gt;Denis Vuyka: Using EventAggregator with MEF&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://codebetter.com/blogs/glenn.block/archive/2009/02/23/event-aggregation-with-mef-with-and-without-eventaggregator.aspx" target="_blank"&gt;Glen Block: Event Aggregation with MEF (with and without EventAggregator)&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Comments/Suggestions?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Download:&lt;/strong&gt; &lt;a href="http://weblogs.asp.net/blogs/rashid/EventAggregator.zip" target="_blank"&gt;EventAggregator.zip&lt;/a&gt;&lt;/p&gt;&lt;div class="wlWriterHeaderFooter" style="margin:0px; padding:0px 0px 0px 0px;"&gt;&lt;div class="shoutIt"&gt;&lt;a rev="vote-for" href="http://dotnetshoutout.com/Submit?url=http%3a%2f%2fweblogs.asp.net%2frashid%2farchive%2f2009%2f03%2f05%2fuse-event-aggregator-to-make-your-application-more-extensible.aspx&amp;amp;title=Use+Event+Aggregator+to+make+your+application+more+extensible"&gt;&lt;img alt="Shout it" src="http://dotnetshoutout.com/image.axd?url=http://weblogs.asp.net/rashid/archive/2009/03/05/use-event-aggregator-to-make-your-application-more-extensible.aspx" style="border:0px" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;img src="http://weblogs.asp.net/aggbug.aspx?PostID=6938393" width="1" height="1"&gt;</description><category domain="http://weblogs.asp.net/rashid/archive/tags/Asp.net/default.aspx">Asp.net</category><category domain="http://weblogs.asp.net/rashid/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://weblogs.asp.net/rashid/archive/tags/DotNetShoutout/default.aspx">DotNetShoutout</category><category domain="http://weblogs.asp.net/rashid/archive/tags/Common+Service+Locator/default.aspx">Common Service Locator</category><category domain="http://weblogs.asp.net/rashid/archive/tags/IoC_2F00_DI/default.aspx">IoC/DI</category><category domain="http://weblogs.asp.net/rashid/archive/tags/Unity/default.aspx">Unity</category><category domain="http://weblogs.asp.net/rashid/archive/tags/ocp/default.aspx">ocp</category><category domain="http://weblogs.asp.net/rashid/archive/tags/Open+Closed+Principle/default.aspx">Open Closed Principle</category><category domain="http://weblogs.asp.net/rashid/archive/tags/Design+Patterns/default.aspx">Design Patterns</category><category domain="http://weblogs.asp.net/rashid/archive/tags/EventAggregator/default.aspx">EventAggregator</category><category domain="http://weblogs.asp.net/rashid/archive/tags/Patterns/default.aspx">Patterns</category></item><item><title>Use Bootstrapper in Your ASP.NET MVC Application and Reduce Code Smell</title><link>http://weblogs.asp.net/rashid/archive/2009/02/17/use-bootstrapper-in-your-asp-net-mvc-application-and-reduce-code-smell.aspx</link><pubDate>Tue, 17 Feb 2009 17:56:00 GMT</pubDate><guid isPermaLink="false">c06e2b9d-981a-45b4-a55f-ab0d8bbfdc1c:6911302</guid><dc:creator>kazimanzurrashid</dc:creator><slash:comments>19</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/rashid/rsscomments.aspx?PostID=6911302</wfw:commentRss><wfw:comment xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/rashid/commentapi.aspx?PostID=6911302</wfw:comment><comments>http://weblogs.asp.net/rashid/archive/2009/02/17/use-bootstrapper-in-your-asp-net-mvc-application-and-reduce-code-smell.aspx#comments</comments><description>&lt;p&gt;In this post, I will show how you can use a simple &lt;font face="Courier New"&gt;bootstrapper&lt;/font&gt; to make your application more extensible. In a typical ASP.NET MVC application you will find there are lots of thing going on in &lt;font face="Courier New"&gt;Application_Start&lt;/font&gt;, like configuring your IoC/DI, registering routes, model binders etc. The following is the code snippet of latest &lt;a href="http://www.codeplex.com/oxite/Release/ProjectReleases.aspx?ReleaseId=23315" target="_blank" mce_href="http://www.codeplex.com/oxite/Release/ProjectReleases.aspx?ReleaseId=23315"&gt;Oxite&lt;/a&gt; source:&lt;/p&gt;  &lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:0e76f612-3dca-4010-97d7-d9b5eb81467d" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="c#"&gt;protected virtual void OnStart()
{
    setupContiner();

    setupSite();

    registerRoutes();

    registerActionFilters();

    registerModelBinders();

    registerViewEngines();

    registerControllerFactory();

    launchBackgroundServices();
}&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Don't you think there are too many things going on, Is there any way to extend it, how do you add new routes/model binder/view engine without modifying it, does not it violet the &lt;a href="http://www.objectmentor.com/resources/articles/ocp.pdf" target="_blank" mce_href="http://www.objectmentor.com/resources/articles/ocp.pdf"&gt;Open Closed Principle&lt;/a&gt;? &lt;/p&gt;

&lt;h3&gt;The Bootstrapper&lt;/h3&gt;

&lt;p&gt;The bootstrapper should have only one responsibility, execute the start up tasks, so first lets create a Task interface:&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:d25a58d3-f0fd-4236-a648-0fa0bb70af6c" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="c#"&gt;public interface IBootstrapperTask
{
    void Execute();
}&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&amp;#160;&lt;/p&gt;

&lt;p&gt;Next, create few small classes for each kind of task which implements this interface, I will use my &lt;a href="http://weblogs.asp.net/rashid/archive/2009/02/15/asp-net-mvc-unity-and-common-service-locator.aspx" mce_href="http://weblogs.asp.net/rashid/archive/2009/02/15/asp-net-mvc-unity-and-common-service-locator.aspx"&gt;previous blog post&lt;/a&gt; example in this case:&lt;/p&gt;

&lt;p&gt;&lt;font face="Courier New"&gt;RegisterControllerFactory:&lt;/font&gt;&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:a7f0b01f-6a19-4f9a-bb1c-8bc2bf4852ed" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="c#"&gt;public class RegisterControllerFactory : IBootstrapperTask
{
    private readonly IControllerFactory _controllerFactory;

    public RegisterControllerFactory(IControllerFactory controllerFactory)
    {
        _controllerFactory = controllerFactory;
    }

    public void Execute()
    {
        ControllerBuilder.Current.SetControllerFactory(_controllerFactory);
    }
}&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;font face="Courier New"&gt;RegisterRoutes:&lt;/font&gt;&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:86403de5-38fa-48f8-9a5e-48ac3c67ed88" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="c#"&gt;public class RegisterRoutes : IBootstrapperTask
{
    private readonly RouteCollection _routes;

    public RegisterRoutes() : this(RouteTable.Routes)
    {
    }

    public RegisterRoutes(RouteCollection routes)
    {
        _routes = routes;
    }

    public void Execute()
    {
        _routes.Clear();

        _routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
        _routes.MapRoute("Default", "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = "" });
    }
}&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As you can understand there can be more, for example, I am using it in &lt;a href="http://dotnetshoutout.com" target="_blank" mce_href="http://dotnetshoutout.com"&gt;DotNetShoutout&lt;/a&gt; for creating default users, running background tasks etc. Next create a &lt;font face="Courier New"&gt;static&lt;/font&gt; class &lt;font face="Courier New"&gt;Bootstrapper&lt;/font&gt; which will execute these tasks:&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:6c330a5c-f9ea-4d4f-8851-4dde7bc51b24" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="c#"&gt;public static class Bootstrapper
{
    static Bootstrapper()
    {
        ConfigureContainer();
    }

    public static void Run()
    {
        var tasks = ServiceLocator.Current.GetAllInstances&amp;lt;IBootstrapperTask&amp;gt;();

        foreach(var task in tasks)
        {
            task.Execute();
        }
    }

    private static void ConfigureContainer()
    {
        IUnityContainer container = new UnityContainer();

        UnityConfigurationSection configuration = (UnityConfigurationSection) ConfigurationManager.GetSection("unity");
        configuration.Containers.Default.Configure(container);

        ServiceLocator.SetLocatorProvider(() =&amp;gt; new UnityServiceLocator(container));
    }
}&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And in the &lt;font face="Courier New"&gt;Global.asax&lt;/font&gt;:&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:fd60d60d-a36d-48f5-9c56-f6c9e484d539" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="c#"&gt;protected void Application_Start()
{
    Bootstrapper.Run();
}&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&amp;#160;&lt;/p&gt;

&lt;p&gt;And in the &lt;font face="Courier New"&gt;web.config&lt;/font&gt;, we can wired the tasks like the following:&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:fdc1a11a-0c54-420c-ad2e-b6522be81985" style="padding-right: 0px; display: inline; padding-left: 0px; float: none; padding-bottom: 0px; margin: 0px; padding-top: 0px"&gt;&lt;pre name="code" class="xml"&gt;&amp;lt;unity&amp;gt;
    &amp;lt;typeAliases&amp;gt;
        &amp;lt;typeAlias alias="IControllerFactory" type="System.Web.Mvc.IControllerFactory, System.Web.Mvc"/&amp;gt;
        &amp;lt;typeAlias alias="ControllerFactory" type="UnityCommonServiceLocatorMVC.CommonServiceLocatorControllerFactory, UnityCommonServiceLocatorMVC"/&amp;gt;
        &amp;lt;typeAlias alias="IBootstrapperTask" type="UnityCommonServiceLocatorMVC.IBootstrapperTask, UnityCommonServiceLocatorMVC"/&amp;gt;
        &amp;lt;typeAlias alias="RegisterRoutes" type="UnityCommonServiceLocatorMVC.RegisterRoutes, UnityCommonServiceLocatorMVC"/&amp;gt;
        &amp;lt;typeAlias alias="RegisterControllerFactory" type="UnityCommonServiceLocatorMVC.RegisterControllerFactory, UnityCommonServiceLocatorMVC"/&amp;gt;
    &amp;lt;/typeAliases&amp;gt;
    &amp;lt;containers&amp;gt;
        &amp;lt;container&amp;gt;
            &amp;lt;types&amp;gt;
                &amp;lt;type name="registerRoutes" type="IBootstrapperTask" mapTo="RegisterRoutes"/&amp;gt;
                &amp;lt;type type="IControllerFactory" mapTo="ControllerFactory"/&amp;gt;
                &amp;lt;type name="registerControllerFactory" type="IBootstrapperTask" mapTo="RegisterControllerFactory"&amp;gt;
                    &amp;lt;typeConfig extensionType="Microsoft.Practices.Unity.Configuration.TypeInjectionElement, Microsoft.Practices.Unity.Configuration"&amp;gt;
                        &amp;lt;constructor&amp;gt;
                            &amp;lt;param name="controllerFactory" parameterType="IControllerFactory"&amp;gt;
                                &amp;lt;dependency/&amp;gt;
                            &amp;lt;/param&amp;gt;
                        &amp;lt;/constructor&amp;gt;
                    &amp;lt;/typeConfig&amp;gt;
                &amp;lt;/type&amp;gt;
            &amp;lt;/types&amp;gt;
        &amp;lt;/container&amp;gt;
    &amp;lt;/containers&amp;gt;
&amp;lt;/unity&amp;gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So by using this bootstrapper we are making our application completely extensible, we can create new task and execute it in the application start without modifying anything.&lt;/p&gt;

&lt;p&gt;Comments/Suggestions?&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Download&lt;/b&gt;: &lt;a href="http://weblogs.asp.net/blogs/rashid/Bootstrapper.zip" target="_blank" mce_href="http://weblogs.asp.net/blogs/rashid/Bootstrapper.zip"&gt;Complete Source&lt;/a&gt;&lt;/p&gt;&lt;div class="wlWriterHeaderFooter" style="margin:0px; padding:0px 0px 0px 0px;"&gt;&lt;div class="shoutIt"&gt;&lt;a rev="vote-for" href="http://dotnetshoutout.com/Submit?url=http%3a%2f%2fweblogs.asp.net%2frashid%2farchive%2f2009%2f02%2f17%2fuse-bootstrapper-in-your-asp-net-mvc-application-and-reduce-code-smell.aspx&amp;amp;title=Use+Bootstrapper+in+Your+ASP.NET+MVC+Application+and+Reduce+Code+Smell"&gt;&lt;img alt="Shout it" src="http://dotnetshoutout.com/image.axd?url=http://weblogs.asp.net/rashid/archive/2009/02/17/use-bootstrapper-in-your-asp-net-mvc-application-and-reduce-code-smell.aspx" style="border:0px" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;img src="http://weblogs.asp.net/aggbug.aspx?PostID=6911302" width="1" height="1"&gt;</description><category domain="http://weblogs.asp.net/rashid/archive/tags/Asp.net/default.aspx">Asp.net</category><category domain="http://weblogs.asp.net/rashid/archive/tags/DotNetShoutout/default.aspx">DotNetShoutout</category><category domain="http://weblogs.asp.net/rashid/archive/tags/ASPNETMVC/default.aspx">ASPNETMVC</category><category domain="http://weblogs.asp.net/rashid/archive/tags/ASP.NET+MVC/default.aspx">ASP.NET MVC</category><category domain="http://weblogs.asp.net/rashid/archive/tags/Common+Service+Locator/default.aspx">Common Service Locator</category><category domain="http://weblogs.asp.net/rashid/archive/tags/IoC_2F00_DI/default.aspx">IoC/DI</category><category domain="http://weblogs.asp.net/rashid/archive/tags/Unity/default.aspx">Unity</category><category domain="http://weblogs.asp.net/rashid/archive/tags/ocp/default.aspx">ocp</category><category domain="http://weblogs.asp.net/rashid/archive/tags/Open+Closed+Principle/default.aspx">Open Closed Principle</category></item><item><title>ASP.NET MVC RC1, ValidateInput, A potential dangerous request and the Pitfall</title><link>http://weblogs.asp.net/rashid/archive/2009/02/14/asp-net-mvc-rc1-validateinput-a-potential-dangerous-request-and-the-pitfall.aspx</link><pubDate>Sat, 14 Feb 2009 00:12:30 GMT</pubDate><guid isPermaLink="false">c06e2b9d-981a-45b4-a55f-ab0d8bbfdc1c:6904014</guid><dc:creator>kazimanzurrashid</dc:creator><slash:comments>6</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/rashid/rsscomments.aspx?PostID=6904014</wfw:commentRss><wfw:comment xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/rashid/commentapi.aspx?PostID=6904014</wfw:comment><comments>http://weblogs.asp.net/rashid/archive/2009/02/14/asp-net-mvc-rc1-validateinput-a-potential-dangerous-request-and-the-pitfall.aspx#comments</comments><description>&lt;p&gt;In the latest release of ASP.NET MVC, a new attribute &lt;font face="Courier New"&gt;ValidateInput&lt;/font&gt; is introduced which is same as Web Forms &lt;font face="Courier New"&gt;ValidateRequest&lt;/font&gt; page directive, certainly a good choice. &lt;a href="http://haacked.com/archive/2009/02/07/take-charge-of-your-security.aspx"&gt;Phil Haack blogged it&lt;/a&gt;, so as &lt;a href="http://stevesmithblog.com/blog/asp-net-mvc-request-validation/"&gt;Steven Smith&lt;/a&gt; and also &lt;a href="http://www.coderjournal.com/2009/02/potentially-dangerous-requestform-detected-aspnet-mvc/"&gt;Nick Berarrdi&lt;/a&gt;. But it starts to break when you want to accept html tags from your user and using &lt;font face="Courier New"&gt;RenderAction&lt;/font&gt; of MVC Future to render different parts of your page. Consider the following screenshot &lt;a href="http://dotnetshoutout.com/Submit"&gt;DotNetShoutout story submit page&lt;/a&gt;:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://weblogs.asp.net/blogs/rashid/Submit_5C0ECDD5.png"&gt;&lt;img title="Submit" style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="528" alt="Submit" src="http://weblogs.asp.net/blogs/rashid/Submit_thumb_61F16F3D.png" width="702" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Rendered by &lt;font face="Courier New"&gt;Html.RenderAction&amp;lt;MembershipController&amp;gt;(c =&amp;gt; c.Menu())&lt;/font&gt; in Master Page. &lt;/li&gt;    &lt;li&gt;Rendered by &lt;font face="Courier New"&gt;Html.RenderAction&amp;lt;CategoryController&amp;gt;(c =&amp;gt; c.Menu())&lt;/font&gt; in Master Page. &lt;/li&gt;    &lt;li&gt;This is original request &lt;font face="Courier New"&gt;StoryController.Submit.&lt;/font&gt; &lt;/li&gt;    &lt;li&gt;Rendered by &lt;font face="Courier New" size="1"&gt;Html.RenderAction&amp;lt;TagController&amp;gt;(c =&amp;gt; c.Tabs())&lt;/font&gt; in Master Page. &lt;/li&gt;    &lt;li&gt;Rendered by &lt;font face="Courier New"&gt;Html.RenderAction&amp;lt;CategoryController&amp;gt;(c =&amp;gt; c.RadioButtonList())&lt;/font&gt; in Content Page. &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;The story description box will allow few known html tags and for this I have to decorate all of the above controllers method with &lt;font face="Courier New"&gt;ValidateInput(false)&lt;/font&gt; which is bit of annoying. I think the problem is mainly in MVC Future as it takes the same &lt;font face="Courier New"&gt;HttpContext&lt;/font&gt; but does not mark it as child/worker request (which I think it should) so when the &lt;font face="Courier New"&gt;ControllerActionInvoker.InvokeAction()&lt;/font&gt; comes into the action it does not takes the original attribute decoration into consideration. This is not a big deal for a small app like &lt;a href="http://codeplex.com/Kigg" target="_blank"&gt;KiGG&lt;/a&gt;, but is definitely going to be if I am planning to develop DotNetNuke like CMS with ASP.NET MVC.&lt;/p&gt;  &lt;p&gt;I think I need to take a look at the &lt;a href="http://www.codeplex.com/MVCContrib"&gt;MVCContrib&lt;/a&gt;, how they have handled this situation with SubController.&lt;/p&gt;&lt;div class="wlWriterHeaderFooter" style="margin:0px; padding:0px 0px 0px 0px;"&gt;&lt;div class="shoutIt"&gt;&lt;a rev="vote-for" href="http://dotnetshoutout.com/Submit?url=http%3a%2f%2fweblogs.asp.net%2frashid%2farchive%2f2009%2f02%2f14%2fasp-net-mvc-rc1-validateinput-a-potential-dangerous-request-and-the-pitfall.aspx&amp;amp;title=ASP.NET+MVC+RC1%2c+ValidateInput%2c+A+potential+dangerous+request+and+the+Pitfall"&gt;&lt;img alt="Shout it" src="http://dotnetshoutout.com/image.axd?url=http://weblogs.asp.net/rashid/archive/2009/02/14/asp-net-mvc-rc1-validateinput-a-potential-dangerous-request-and-the-pitfall.aspx" style="border:0px" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;img src="http://weblogs.asp.net/aggbug.aspx?PostID=6904014" width="1" height="1"&gt;</description><category domain="http://weblogs.asp.net/rashid/archive/tags/Asp.net/default.aspx">Asp.net</category><category domain="http://weblogs.asp.net/rashid/archive/tags/MVC/default.aspx">MVC</category><category domain="http://weblogs.asp.net/rashid/archive/tags/DotNetShoutout/default.aspx">DotNetShoutout</category><category domain="http://weblogs.asp.net/rashid/archive/tags/ASPNETMVC/default.aspx">ASPNETMVC</category><category domain="http://weblogs.asp.net/rashid/archive/tags/ASP.NET+MVC/default.aspx">ASP.NET MVC</category></item><item><title>DotNetShoutout : Few Productivity Tips</title><link>http://weblogs.asp.net/rashid/archive/2009/02/13/dotnetshoutout-few-productivity-tips.aspx</link><pubDate>Fri, 13 Feb 2009 00:21:13 GMT</pubDate><guid isPermaLink="false">c06e2b9d-981a-45b4-a55f-ab0d8bbfdc1c:6900451</guid><dc:creator>kazimanzurrashid</dc:creator><slash:comments>2</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/rashid/rsscomments.aspx?PostID=6900451</wfw:commentRss><wfw:comment xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/rashid/commentapi.aspx?PostID=6900451</wfw:comment><comments>http://weblogs.asp.net/rashid/archive/2009/02/13/dotnetshoutout-few-productivity-tips.aspx#comments</comments><description>&lt;p&gt;I just want share few tips with the &lt;a href="http://dotnetshoutout.com"&gt;DotNetShoutout&lt;/a&gt; users, maybe you have already discovered it, if you have not then it might be handy.&lt;/p&gt;  &lt;h3&gt;Feed&lt;/h3&gt;  &lt;p&gt;If you subscribe to our &lt;a href="http://feed.dotnetshoutout.com/dotnetshoutout-published"&gt;published&lt;/a&gt; or &lt;a href="http://feed.dotnetshoutout.com/dotnetshoutout-upcoming"&gt;upcoming&lt;/a&gt; feed, you will notice that clicking on the link does not take you back to DotNetShoutout story page, instead it will take you directly to the original Story page, saving you from one unnecessary click.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://weblogs.asp.net/blogs/rashid/Feed_586AF310.png"&gt;&lt;img title="Feed" style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="316" alt="Feed" src="http://weblogs.asp.net/blogs/rashid/Feed_thumb_43B5322B.png" width="770" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;With the story text you will also find the number of shout and a small page preview, if you want to shout the story click the shout it counter, it will take you to the DotNetShoutout page. And by the way you can subscribe to our &lt;a href="http://feed.dotnetshoutout.com/dotnetshoutout-upcoming"&gt;Upcoming feed&lt;/a&gt; without worrying about spam, our system is quite equipped fighting with those spammers.&lt;/p&gt;  &lt;h3&gt;Sharing&lt;/h3&gt;  &lt;p&gt;Like the feed when you share a story, it will share the original url instead of the DotNetShoutout url. Currently we are only supporting MSDN Social(though I am not sure how many people are using it), delicious, facebook and twitter and for twitter we are even shrinking the original url to fit in 140 characters.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://weblogs.asp.net/blogs/rashid/Share_5E51A877.png"&gt;&lt;img title="Share" style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="185" alt="Share" src="http://weblogs.asp.net/blogs/rashid/Share_thumb_0AEA3C84.png" width="1034" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;And in Twitter:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://weblogs.asp.net/blogs/rashid/Twitter_6E20A1A1.png"&gt;&lt;img title="Twitter" style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="93" alt="Twitter" src="http://weblogs.asp.net/blogs/rashid/Twitter_thumb_251A7FDB.png" width="532" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;h3&gt;Shout it Badge&lt;/h3&gt;  &lt;p&gt;If you are blogging from Live write and putting the Shout it badge manually, please stop. We do a have plug-in for the latest Live Writer which autmattically inserts the counter in the footer of your post. Just &lt;a href="http://www.codeplex.com/Kigg/Release/ProjectReleases.aspx?ReleaseId=21737"&gt;visit the release section of codeplex&lt;/a&gt; and download the LiveWriterBinary.zip, it is just a simple dll (you can also get the source code version from the release page) which you have to copy in the plug-in folder of Live Writer (e.g . C:\Program Files\Windows Live\Writer\Plugins). Once you copied the dll, you find the plug-in name in Live Writer menu –&amp;gt; Tool-&amp;gt;Options-&amp;gt;Plug-ins, like the following:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://weblogs.asp.net/blogs/rashid/LiveWriterOptions_3C65844C.png"&gt;&lt;img title="LiveWriter-Options" style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="572" alt="LiveWriter-Options" src="http://weblogs.asp.net/blogs/rashid/LiveWriterOptions_thumb_3350D0CD.png" width="878" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;Make sure it is enabled, you can also change the default colors from the options depending upon your blog theme. Now write your post and click publish, live writer might ask you do you want to execute this plug-in, click yes. The counter will appear at the bottom of your post like this post.&lt;/p&gt;  &lt;p&gt;Enjoy!!!&lt;/p&gt;&lt;div class="wlWriterHeaderFooter" style="margin:0px; padding:0px 0px 0px 0px;"&gt;&lt;div class="shoutIt"&gt;&lt;a rev="vote-for" href="http://dotnetshoutout.com/Submit?url=http%3a%2f%2fweblogs.asp.net%2frashid%2farchive%2f2009%2f02%2f13%2fdotnetshoutout-few-productivity-tips.aspx&amp;amp;title=DotNetShoutout+%3a+Few+Productivity+Tips"&gt;&lt;img alt="Shout it" src="http://dotnetshoutout.com/image.axd?url=http://weblogs.asp.net/rashid/archive/2009/02/13/dotnetshoutout-few-productivity-tips.aspx" style="border:0px" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;img src="http://weblogs.asp.net/aggbug.aspx?PostID=6900451" width="1" height="1"&gt;</description><category domain="http://weblogs.asp.net/rashid/archive/tags/Community+News/default.aspx">Community News</category><category domain="http://weblogs.asp.net/rashid/archive/tags/DotNetShoutout/default.aspx">DotNetShoutout</category></item><item><title>Domain Model (Developing KiGG v2.0 Part 1)</title><link>http://weblogs.asp.net/rashid/archive/2009/02/12/domain-model-developing-kigg-v2-0-part-1.aspx</link><pubDate>Thu, 12 Feb 2009 00:27:00 GMT</pubDate><guid isPermaLink="false">c06e2b9d-981a-45b4-a55f-ab0d8bbfdc1c:6898096</guid><dc:creator>kazimanzurrashid</dc:creator><slash:comments>8</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/rashid/rsscomments.aspx?PostID=6898096</wfw:commentRss><wfw:comment xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/rashid/commentapi.aspx?PostID=6898096</wfw:comment><comments>http://weblogs.asp.net/rashid/archive/2009/02/12/domain-model-developing-kigg-v2-0-part-1.aspx#comments</comments><description>&lt;P&gt;As mention in my &lt;A href="http://weblogs.asp.net/rashid/archive/2009/01/20/introducing-dotnetshoutout-com.aspx" target=_blank mce_href="http://weblogs.asp.net/rashid/archive/2009/01/20/introducing-dotnetshoutout-com.aspx"&gt;previous post&lt;/A&gt; that I will be discussing the technical side of &lt;A href="http://codeplex.com/Kigg" target=_blank mce_href="http://codeplex.com/Kigg"&gt;KiGG&lt;/A&gt;. So this is the beginning and it will be a multi-part series. I will try to put as much detail as possible, do let me know if I missed anything.&lt;/P&gt;
&lt;P&gt;Just for a recap, KiGG is Web 2.0 style social news application where I am trying to exercise some of the best practice like TDD, DDD, SOLID etc with Microsoft supported tooling. If you want to see it in action just visit &lt;A href="http://dotnetshoutout.com/" mce_href="http://dotnetshoutout.com"&gt;http://dotnetshoutout.com&lt;/A&gt;.&lt;/P&gt;
&lt;P&gt;KiGG is already a fully functional application, but here I am starting from scratch, so the actual code might not look the same with following but it will show you how it is evolved as I am going to post more on it over the time. &lt;/P&gt;
&lt;P&gt;Let us begin with the core functionalities:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;User should be able to submit story. 
&lt;UL&gt;
&lt;LI&gt;User will select a category and can specify multiple tags when submitting story. &lt;/LI&gt;
&lt;LI&gt;User should not be able to submit a story which already exists (based upon Url). &lt;/LI&gt;
&lt;LI&gt;The system will ensure the story is a .NET related story, otherwise it will need moderator approval prior make it visible to everyone. &lt;/LI&gt;&lt;/UL&gt;&lt;/LI&gt;
&lt;LI&gt;User should be able to promote story. 
&lt;UL&gt;
&lt;LI&gt;When the story has not been promoted by him/her. &lt;/LI&gt;
&lt;LI&gt;When the story has not been marked as spam by him/her. &lt;/LI&gt;&lt;/UL&gt;&lt;/LI&gt;
&lt;LI&gt;User should be able to demote story. 
&lt;UL&gt;
&lt;LI&gt;When the story has not been submitted by him/her. &lt;/LI&gt;
&lt;LI&gt;When the story has been previously promoted by him/her. &lt;/LI&gt;&lt;/UL&gt;&lt;/LI&gt;
&lt;LI&gt;User should be able to mark story as spam. 
&lt;UL&gt;
&lt;LI&gt;When the story has not been promoted by him/her. &lt;/LI&gt;
&lt;LI&gt;When the story has not been marked as spam by him/her. &lt;/LI&gt;
&lt;LI&gt;When story is not published. &lt;/LI&gt;&lt;/UL&gt;&lt;/LI&gt;
&lt;LI&gt;User should be able to post comment. 
&lt;UL&gt;
&lt;LI&gt;The system will ensure the comment is not a spam. &lt;/LI&gt;&lt;/UL&gt;&lt;/LI&gt;
&lt;LI&gt;User should be able to subscribe/unsubscribe comments of a Story. &lt;/LI&gt;
&lt;LI&gt;User should be able to Tag story. &lt;/LI&gt;
&lt;LI&gt;User should be able to view the original story by clicking the link and the count should be maintained. &lt;/LI&gt;
&lt;LI&gt;User Score should be increased/decreased based upon the above actions. &lt;/LI&gt;
&lt;LI&gt;User should be able to view the story list by published/upcoming/category/tag/user etc. &lt;/LI&gt;
&lt;LI&gt;Moderator should be able to Edit Story. &lt;/LI&gt;
&lt;LI&gt;Moderator should be able to Delete Story. &lt;/LI&gt;
&lt;LI&gt;Moderator should be able to Confirm a Story/Comment as Spam. &lt;/LI&gt;
&lt;LI&gt;Moderator should be able to Approve a Story as not spam which was previously marked as spam. &lt;/LI&gt;
&lt;LI&gt;Admin should be able to Lock/Unlock a User. &lt;/LI&gt;
&lt;LI&gt;Admin should be able to change role of a User. &lt;/LI&gt;
&lt;LI&gt;Admin should be able to Publish stories at periodic interval. &lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;With the above functionalities we can come up with an object model which consists classes like User, Story, Tag, Category, Vote, MarkAsSpam, Comment:&lt;/P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;A href="http://weblogs.asp.net/blogs/rashid/PreDomainObjects_459EB542.png" mce_href="http://weblogs.asp.net/blogs/rashid/PreDomainObjects_459EB542.png"&gt;&lt;IMG title=Pre-DomainObjects style="BORDER-TOP-WIDTH: 0px; DISPLAY: inline; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height=493 alt=Pre-DomainObjects src="http://weblogs.asp.net/blogs/rashid/PreDomainObjects_thumb_758568D1.png" width=751 border=0 mce_src="http://weblogs.asp.net/blogs/rashid/PreDomainObjects_thumb_758568D1.png"&gt;&lt;/A&gt; 
&lt;P&gt;Now, lets refine this domain model with TDD (I will skip the red part of red-green-refactor to save space of this post, but you should always exercise the red part while developing in this way).&lt;/P&gt;
&lt;H3&gt;Promoting&lt;/H3&gt;
&lt;P&gt;When promoting the story we have to check two things, user has not previously promoted it and not marked it as spam. Lets write the tests:&lt;/P&gt;
&lt;DIV class=wlWriterSmartContent id=scid:812469c5-0cb0-4c63-8c15-c81123a09de7:196bd658-2d59-4ede-8c3f-8666ebdf6b6a style="PADDING-RIGHT: 0px; DISPLAY: inline; PADDING-LEFT: 0px; FLOAT: none; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px"&gt;&lt;PRE class=c# name="code"&gt;[Fact]
public void HasPromoted_Should_Return_False_When_User_Has_Not_Previously_Promoted()
{
    var user = new User();

    Assert.False(_story.HasPromoted(user));
}

[Fact]
public void HasMarkedAsSpam_Should_Return_False_When_User_Has_Not_Previously_MarkedAsSpam()
{
    var user = new User();

    Assert.False(_story.HasMarkedAsSpam(user));
}&lt;/PRE&gt;&lt;/DIV&gt;
&lt;P&gt;And in Story:&lt;/P&gt;
&lt;DIV class=wlWriterSmartContent id=scid:812469c5-0cb0-4c63-8c15-c81123a09de7:33de9fd9-3e98-43a2-bcbf-fa7e7c1eb6f0 style="PADDING-RIGHT: 0px; DISPLAY: inline; PADDING-LEFT: 0px; FLOAT: none; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px"&gt;&lt;PRE class=c# name="code"&gt;public bool HasPromoted(User byUser)
{
    return Votes.Any(v =&amp;gt; v.ByUser.Id == byUser.Id);
}

public bool HasMarkedAsSpam(User byUser)
{
    return MarkAsSpams.Any(m =&amp;gt; m.ByUser.Id == byUser.Id);
}&lt;/PRE&gt;&lt;/DIV&gt;
&lt;P&gt;Now, create a new method that internally calls these two methods, so that client can only call this single method instead of calling those, test:&lt;/P&gt;
&lt;DIV class=wlWriterSmartContent id=scid:812469c5-0cb0-4c63-8c15-c81123a09de7:5121cb18-8e88-4ea5-b8eb-49803ece83c6 style="PADDING-RIGHT: 0px; DISPLAY: inline; PADDING-LEFT: 0px; FLOAT: none; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px"&gt;&lt;PRE class=c# name="code"&gt;[Fact]
public void CanPromote_Should_Return_True_When_Story_Has_Not_Been_Promoted()
{
    var user = new User { Id = Guid.NewGuid() };

    Assert.True(_story.CanPromote(user));
}

[Fact]
public void CanPromote_Should_Return_True_When_Story_Has_Not_Been_MarkedAsSpam_By_The_User()
{
    var user = new User { Id = Guid.NewGuid() };

    Assert.True(_story.CanPromote(user));
}&lt;/PRE&gt;&lt;/DIV&gt;
&lt;P&gt;And in Story:&lt;/P&gt;
&lt;DIV class=wlWriterSmartContent id=scid:812469c5-0cb0-4c63-8c15-c81123a09de7:612dc092-e45c-4edb-ac81-4c5eb0d19b74 style="PADDING-RIGHT: 0px; DISPLAY: inline; PADDING-LEFT: 0px; FLOAT: none; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px"&gt;&lt;PRE class=c# name="code"&gt;public bool CanPromote(User byUser)
{
    return !HasPromoted(byUser) &amp;amp;&amp;amp; !HasMarkedAsSpam(byUser);
}&lt;/PRE&gt;&lt;/DIV&gt;
&lt;P&gt;Now the actual promote, note that we have introduced a new property&amp;nbsp; Timestamp in Vote, tests:&lt;/P&gt;
&lt;DIV class=wlWriterSmartContent id=scid:812469c5-0cb0-4c63-8c15-c81123a09de7:bd7beb9a-f801-4465-8d4c-32f40007c690 style="PADDING-RIGHT: 0px; DISPLAY: inline; PADDING-LEFT: 0px; FLOAT: none; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px"&gt;&lt;PRE class=c# name="code"&gt;[Fact]
public void Promote_Should_Return_True_When_User_Can_Promote()
{
    var user = new User { Id = Guid.NewGuid() };

    Assert.True(_story.Promote(DateTime.UtcNow, user));
}

[Fact]
public void Promote_Should_Add_User_Vote_In_Votes()
{
    var user = new User { Id = Guid.NewGuid() };

    _story.Promote(DateTime.UtcNow, user);

    Assert.True(_story.Votes.Count(v =&amp;gt; v.ByUser.Id == user.Id) &amp;gt; 0);
}&lt;/PRE&gt;&lt;/DIV&gt;
&lt;P&gt;And in Story:&lt;/P&gt;
&lt;DIV class=wlWriterSmartContent id=scid:812469c5-0cb0-4c63-8c15-c81123a09de7:d27fb79d-b86b-442f-be19-16374c1ff674 style="PADDING-RIGHT: 0px; DISPLAY: inline; PADDING-LEFT: 0px; FLOAT: none; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px"&gt;&lt;PRE class=c# name="code"&gt;private readonly List&amp;lt;Vote&amp;gt; _votes = new List&amp;lt;Vote&amp;gt;();

public ICollection&amp;lt;Vote&amp;gt; Votes
{
    get
    {
        return _votes.AsReadOnly();
    }
}

public bool Promote(DateTime at, User byUser)
{
    if (CanPromote(byUser))
    {
        Vote vote = new Vote
                        {
                            ByUser = byUser,
                            ForStory = this,
                            Timestamp = at
                        };

        _votes.Add(vote);

        return true;
    }

    return false;
}&lt;/PRE&gt;&lt;/DIV&gt;
&lt;P&gt;Notice that I have exposed the Votes as read only collection, though the ReadOnlyCollection&amp;lt;T&amp;gt; of ObjectModel namespace should be more appropriate, but I would like to stick with the interface instead of concrete class. So adding vote in Votes will raise exception, the client always have to use the Promote method.&lt;/P&gt;
&lt;H3&gt;Demoting&lt;/H3&gt;
&lt;P&gt;Demoting is similar to promoting, we have to check that the User has not submitted the story and the story has been previously promoted by the user(You can only demote if you previously promoted it), tests:&lt;/P&gt;
&lt;DIV class=wlWriterSmartContent id=scid:812469c5-0cb0-4c63-8c15-c81123a09de7:335011a9-7c8f-453f-a336-455093cf1be3 style="PADDING-RIGHT: 0px; DISPLAY: inline; PADDING-LEFT: 0px; FLOAT: none; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px"&gt;&lt;PRE class=c# name="code"&gt;[Fact]
public void CanDemote_Should_Return_True_When_Story_Has_Been_Promoted_By_The_User()
{
    //Promote it first
    var user = new User { Id = Guid.NewGuid() };
    _story.Promote(DateTime.UtcNow, user);

    Assert.True(_story.CanDemote(user));
}

[Fact]
public void Demote_Should_Return_True_When_User_Can_Demote()
{
    //Promote it first
    var user = new User { Id = Guid.NewGuid() };
    _story.Promote(DateTime.UtcNow, user);

    Assert.True(_story.Demote(user));
}

[Fact]
public void Demote_Should_Remove_User_Vote_From_Votes()
{
    //Promote it first
    var user = new User { Id = Guid.NewGuid() };
    _story.Promote(DateTime.UtcNow, user);

    _story.Demote(user);

    Assert.True(_story.Votes.Count(v =&amp;gt; v.ByUser.Id == user.Id) == 0);
}&lt;/PRE&gt;&lt;/DIV&gt;
&lt;P&gt;And in Story:&lt;/P&gt;
&lt;DIV class=wlWriterSmartContent id=scid:812469c5-0cb0-4c63-8c15-c81123a09de7:b09e54bb-a94e-493a-8b12-1f249183acf9 style="PADDING-RIGHT: 0px; DISPLAY: inline; PADDING-LEFT: 0px; FLOAT: none; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px"&gt;&lt;PRE class=c# name="code"&gt;public bool CanDemote(User byUser)
{
    return (PostedBy.Id != byUser.Id) &amp;amp;&amp;amp; HasPromoted(byUser);
}

public bool Demote(User byUser)
{
    if (CanDemote(byUser))
    {
        Vote vote = _votes.Single(v =&amp;gt; v.ByUser.Id == byUser.Id);

        _votes.Remove(vote);

        return true;
    }

    return false;
}&lt;/PRE&gt;&lt;/DIV&gt;
&lt;H3&gt;Marking as Spam&lt;/H3&gt;
&lt;P&gt;Marking as spam is also similar to story promoting, only a new checking that is if the story is published, so we will need a new property which will indicate whether the story is published, lets create a new property PublishedAt of nullable DateTime and create an extension method:&lt;/P&gt;
&lt;DIV class=wlWriterSmartContent id=scid:812469c5-0cb0-4c63-8c15-c81123a09de7:747f52e8-1601-4abb-add3-75bed656e50a style="PADDING-RIGHT: 0px; DISPLAY: inline; PADDING-LEFT: 0px; FLOAT: none; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px"&gt;&lt;PRE class=c# name="code"&gt;public static bool IsPublished(this Story story)
{
    return (story.PublishedAt.HasValue);
}&lt;/PRE&gt;&lt;/DIV&gt;
&lt;P&gt;we also introduced a new property Timestamp for MarkAsSpam like vote, tests:&lt;/P&gt;
&lt;DIV class=wlWriterSmartContent id=scid:812469c5-0cb0-4c63-8c15-c81123a09de7:c6f713dd-4356-444b-9e28-3c0b041bacfd style="PADDING-RIGHT: 0px; DISPLAY: inline; PADDING-LEFT: 0px; FLOAT: none; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px"&gt;&lt;PRE class=c# name="code"&gt;[Fact]
public void CanMarkAsSpam_Should_Return_True_When_Story_Has_Not_Been_Published()
{
    var user = new User { Id = Guid.NewGuid() };

    Assert.True(_story.CanMarkAsSpam(user));
}

[Fact]
public void CanMarkAsSpam_Should_Return_True_When_Story_Has_Not_Been_Promoted_By_The_User()
{
    var user = new User { Id = Guid.NewGuid() };

    Assert.True(_story.CanMarkAsSpam(user));
}

[Fact]
public void CanMarkAsSpam_Should_Return_True_When_Story_Has_Not_Been_MarkedAsSpam_By_The_User()
{
    var user = new User { Id = Guid.NewGuid() };

    Assert.True(_story.CanMarkAsSpam(user));
}

[Fact]
public void MarkAsSpam_Should_Return_True_When_User_Can_Mark_As_Spam()
{
    var user = new User { Id = Guid.NewGuid() };

    Assert.True(_story.MarkAsSpam(DateTime.UtcNow, user));
}

[Fact]
public void MarkAsSpam_Should_Add_User_Marking_In_MarkAsSpams()
{
    var user = new User { Id = Guid.NewGuid() };

    _story.MarkAsSpam(DateTime.UtcNow, user);

    Assert.True(_story.MarkAsSpams.Count(m =&amp;gt; m.ByUser.Id == user.Id) &amp;gt; 0);
}&lt;/PRE&gt;&lt;/DIV&gt;
&lt;P&gt;And in Story&lt;/P&gt;
&lt;DIV class=wlWriterSmartContent id=scid:812469c5-0cb0-4c63-8c15-c81123a09de7:b70e86b4-74b1-47e7-836d-1abe5fe8dbcb style="PADDING-RIGHT: 0px; DISPLAY: inline; PADDING-LEFT: 0px; FLOAT: none; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px"&gt;&lt;PRE class=c# name="code"&gt;private readonly List&amp;lt;MarkAsSpam&amp;gt; _markAsSpams = new List&amp;lt;MarkAsSpam&amp;gt;();

public ICollection&amp;lt;MarkAsSpam&amp;gt; MarkAsSpams
{
    get
    {
        return _markAsSpams.AsReadOnly();
    }
}

public bool CanMarkAsSpam(User byUser)
{
    return !this.IsPublished() &amp;amp;&amp;amp; !HasPromoted(byUser) &amp;amp;&amp;amp; !HasMarkedAsSpam(byUser);
}

public bool MarkAsSpam(DateTime at, User byUser)
{
    if (CanMarkAsSpam(byUser))
    {
        MarkAsSpam markAsSpam = new MarkAsSpam
                                    {
                                        ByUser = byUser,
                                        ForStory = this,
                                        Timestamp = at
                                    };

        _markAsSpams.Add(markAsSpam);

        return true;
    }

    return false;
}&lt;/PRE&gt;&lt;/DIV&gt;
&lt;H3&gt;Post Comment&lt;/H3&gt;
&lt;P&gt;For comment we have to introduce few new properties, Id, Content and CreatedAt. We will skip the spam checking part for the future post, tests:&lt;/P&gt;
&lt;DIV class=wlWriterSmartContent id=scid:812469c5-0cb0-4c63-8c15-c81123a09de7:e1b71ab2-9d24-4577-9314-5e6612af6825 style="PADDING-RIGHT: 0px; DISPLAY: inline; PADDING-LEFT: 0px; FLOAT: none; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px"&gt;&lt;PRE class=c# name="code"&gt;[Fact]
public void PostComment_Should_Return_New_Comment()
{
    var user = new User{ Id = Guid.NewGuid() };

    var comment = _story.PostComment("This is a dummy content", DateTime.UtcNow, user);

    Assert.NotNull(comment);
}

[Fact]
public void PostComment_Should_Increase_Comments_Collection()
{
    var previousCount = _story.Comments.Count;

    var user = new User { Id = Guid.NewGuid() };

    _story.PostComment("This is a dummy content", DateTime.UtcNow, user);

    Assert.True(_story.Comments.Count &amp;gt; previousCount);
}&lt;/PRE&gt;&lt;/DIV&gt;
&lt;P&gt;And in Story:&lt;/P&gt;
&lt;DIV class=wlWriterSmartContent id=scid:812469c5-0cb0-4c63-8c15-c81123a09de7:ea8040d5-6b10-438c-b634-52ab6e89ce90 style="PADDING-RIGHT: 0px; DISPLAY: inline; PADDING-LEFT: 0px; FLOAT: none; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px"&gt;&lt;PRE class=c# name="code"&gt;private readonly List&amp;lt;Comment&amp;gt; _comments = new List&amp;lt;Comment&amp;gt;();

public ICollection&amp;lt;Comment&amp;gt; Comments
{
    get
    {
        return _comments.AsReadOnly();
    }
}

public Comment PostComment(string content, DateTime at, User byUser)
{
    Comment comment = new Comment
                          {
                              Id = Guid.NewGuid(),
                              ByUser = byUser,
                              ForStory = this,
                              CreatedAt = at,
                              Content = content
                          };

    _comments.Add(comment);

    return comment;
}&lt;/PRE&gt;&lt;/DIV&gt;
&lt;H3&gt;Subscribing/Unsubscribing Comments&lt;/H3&gt;
&lt;P&gt;When subscribing comment we will check if the user has already subscribed, if not we will subscribe otherwise we will ignore it, tests:&lt;/P&gt;
&lt;DIV class=wlWriterSmartContent id=scid:812469c5-0cb0-4c63-8c15-c81123a09de7:12d8afd1-9bc0-495c-85a2-4337ed992b26 style="PADDING-RIGHT: 0px; DISPLAY: inline; PADDING-LEFT: 0px; FLOAT: none; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px"&gt;&lt;PRE class=c# name="code"&gt;[Fact]
public void ContainsCommentSubscriber_Should_Return_False_When_User_Does_Not_Exist_In_CommentSubscribers_Collection()
{
    var user = new User { Id = Guid.NewGuid() };

    Assert.False(_story.ContainsCommentSubscriber(user));
}

[Fact]
public void SubscribeComment_Should_Increase_CommentSubscribers_Collection()
{
    var user = new User { Id = Guid.NewGuid() };

    var previousCount = _story.CommentSubscribers.Count;

    _story.SubscribeComment(user);

    Assert.True(_story.CommentSubscribers.Count &amp;gt; previousCount);
}

[Fact]
public void SubscribeComment_Should_Not_Increase_CommentSubscribers_Collection_When_User_Already_Exists()
{
    var user = new User { Id = Guid.NewGuid() };

    _story.SubscribeComment(user);

    var previousCount = _story.CommentSubscribers.Count;

    _story.SubscribeComment(user);

    Assert.Equal(_story.CommentSubscribers.Count, previousCount);
}

[Fact]
public void UnsubscribeComment_Should_Decrease_CommentSubscribers_Collection()
{
    var user = new User { Id = Guid.NewGuid() };
    _story.SubscribeComment(user);

    var previousCount = _story.CommentSubscribers.Count;

    _story.UnSubscribeComment(user);

    Assert.True(_story.CommentSubscribers.Count &amp;lt; previousCount);
}&lt;/PRE&gt;&lt;/DIV&gt;
&lt;P&gt;And in Story:&lt;/P&gt;
&lt;DIV class=wlWriterSmartContent id=scid:812469c5-0cb0-4c63-8c15-c81123a09de7:2e94b6fa-bcde-4397-bff7-71395456ac38 style="PADDING-RIGHT: 0px; DISPLAY: inline; PADDING-LEFT: 0px; FLOAT: none; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px"&gt;&lt;PRE class=c# name="code"&gt;private readonly List&amp;lt;User&amp;gt; _commentSubscribers = new List&amp;lt;User&amp;gt;();

public ICollection&amp;lt;User&amp;gt; CommentSubscribers
{
    get
    {
        return _commentSubscribers.AsReadOnly();
    }
}

public bool ContainsCommentSubscriber(User byUser)
{
    return _commentSubscribers.Any(cs =&amp;gt; cs.Id == byUser.Id);
}

public void SubscribeComment(User byUser)
{
    if (!ContainsCommentSubscriber(byUser))
    {
        _commentSubscribers.Add(byUser);
    }
}

public void UnSubscribeComment(User byUser)
{
    if (ContainsCommentSubscriber(byUser))
    {
        var s = _commentSubscribers.Single(cs =&amp;gt; cs.Id == byUser.Id);
        _commentSubscribers.Remove(s);
    }
}&lt;/PRE&gt;&lt;/DIV&gt;
&lt;H3&gt;Viewing Story&lt;/H3&gt;
&lt;P&gt;We need the track how many times a story has been viewed, so let us introduce another property ViewCount, Test:&lt;/P&gt;
&lt;DIV class=wlWriterSmartContent id=scid:812469c5-0cb0-4c63-8c15-c81123a09de7:9adb8b45-796b-42c0-9440-313a8ce66bdb style="PADDING-RIGHT: 0px; DISPLAY: inline; PADDING-LEFT: 0px; FLOAT: none; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px"&gt;&lt;PRE class=c# name="code"&gt;[Fact]
public void View_Should_Increase_ViewCount()
{
    var previousCount = _story.ViewCount;

    _story.View();

    Assert.True(_story.ViewCount &amp;gt; previousCount);
}&lt;/PRE&gt;&lt;/DIV&gt;
&lt;P&gt;And in Story:&lt;/P&gt;
&lt;DIV class=wlWriterSmartContent id=scid:812469c5-0cb0-4c63-8c15-c81123a09de7:078a5cf0-beed-4c7b-9717-08e05d7e2240 style="PADDING-RIGHT: 0px; DISPLAY: inline; PADDING-LEFT: 0px; FLOAT: none; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px"&gt;&lt;PRE class=c# name="code"&gt;public int ViewCount
{
    get;
    private set;
}

public void View()
{
    ViewCount += 1;
}&lt;/PRE&gt;&lt;/DIV&gt;
&lt;H3&gt;Increasing/Decreasing User Score&lt;/H3&gt;
&lt;P&gt;User score should be increased/decreased based upon the action like submitting/promoting/demoting etc. So first let us create an Enum which will hold some predefined User Actions:&lt;/P&gt;
&lt;DIV class=wlWriterSmartContent id=scid:812469c5-0cb0-4c63-8c15-c81123a09de7:bd048e8f-b8bb-46c8-a1e0-5d806a44fbb9 style="PADDING-RIGHT: 0px; DISPLAY: inline; PADDING-LEFT: 0px; FLOAT: none; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px"&gt;&lt;PRE class=c# name="code"&gt;public enum UserAction
{
    None = 0,
    AccountActivated = 1,
    StorySubmitted = 2,
    StoryViewed = 3,
    StoryPromoted = 4,
    StoryCommented = 5,
    StoryMarkedAsSpam = 6,
    SpamStorySubmitted = 7
}&lt;/PRE&gt;&lt;/DIV&gt;
&lt;P&gt;With increasing/decreasing user score we also need to have the support to know the current score and query score for a given time period, so lets add a new class which holds the date, score and user action when increasing/decreasing score, tests:&lt;/P&gt;
&lt;DIV class=wlWriterSmartContent id=scid:812469c5-0cb0-4c63-8c15-c81123a09de7:3bd99c46-928c-42fe-8fc0-5143ee002fe9 style="PADDING-RIGHT: 0px; DISPLAY: inline; PADDING-LEFT: 0px; FLOAT: none; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px"&gt;&lt;PRE class=c# name="code"&gt;[Fact]
public void CurrentScore_Should_Be_Zero_When_User_Is_Created()
{
    Assert.Equal(0, _user.CurrentScore);
}

[Fact]
public void IncreaseScoreBy_Should_Increase_CurrentScore()
{
    decimal previousScore = _user.CurrentScore;

    _user.IncreaseScoreBy(5, UserAction.StorySubmitted);

    Assert.Equal(previousScore + 5, _user.CurrentScore);
}

[Fact]
public void DecreaseScoreBy_Should_Decrease_CurrentScore()
{
    decimal previousScore = _user.CurrentScore;

    _user.DecreaseScoreBy(50, UserAction.SpamStorySubmitted);

    Assert.Equal(previousScore - 50, _user.CurrentScore);
}&lt;/PRE&gt;&lt;/DIV&gt;
&lt;P&gt;And in User:&lt;/P&gt;
&lt;DIV class=wlWriterSmartContent id=scid:812469c5-0cb0-4c63-8c15-c81123a09de7:19408e57-07f6-463f-b9bf-12684ac62f12 style="PADDING-RIGHT: 0px; DISPLAY: inline; PADDING-LEFT: 0px; FLOAT: none; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px"&gt;&lt;PRE class=c# name="code"&gt;public DateTime CreatedAt
{
    get;
    set;
}

public decimal CurrentScore
{
    get
    {
        return GetScoreBetween(CreatedAt, DateTime.UtcNow);
    }
}

private decimal GetScoreBetween(DateTime from, DateTime to)
{
    return _userScores.Where(us =&amp;gt; us.Timestamp &amp;gt;= from &amp;amp;&amp;amp; us.Timestamp &amp;lt;= to).Sum(us =&amp;gt; us.Score);
}

public void IncreaseScoreBy(decimal score, UserAction reason)
{
    AddScore(score, reason);
}

public void DecreaseScoreBy(decimal score, UserAction reason)
{
    AddScore(-score, reason);
}

private void AddScore(decimal score, UserAction reason)
{
    _userScores.Add(
                        new UserScore
                        {
                            OfUser = this,
                            Reason = reason,
                            Score = score,
                            Timestamp = DateTime.UtcNow
                        });
}&lt;/PRE&gt;&lt;/DIV&gt;
&lt;H3&gt;Tag Story&lt;/H3&gt;
&lt;P&gt;When submitting story, user should be able to specify tags for story and later on can view the stories with that specified tags, so we need to associate tag with both User and Story. Since it is a common behavior, we can create an interface which both Story and User has to implement:&lt;/P&gt;
&lt;DIV class=wlWriterSmartContent id=scid:812469c5-0cb0-4c63-8c15-c81123a09de7:a0c3af81-3dc6-444a-ad97-70a9b55fd4af style="PADDING-RIGHT: 0px; DISPLAY: inline; PADDING-LEFT: 0px; FLOAT: none; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px"&gt;&lt;PRE class=c# name="code"&gt;public interface ITagContainer
{
    ICollection&amp;lt;Tag&amp;gt; Tags
    {
        get;
    }

    void AddTag(Tag tag);

    void RemoveTag(Tag tag);

    void RemoveAllTags();

    bool ContainsTag(Tag tag);
}&lt;/PRE&gt;&lt;/DIV&gt;
&lt;P&gt;Though composition is better than inheritance, but for the time being lets keep it this way. Since both is the same I am only showing the Story, tests:&lt;/P&gt;
&lt;DIV class=wlWriterSmartContent id=scid:812469c5-0cb0-4c63-8c15-c81123a09de7:6762dd35-6a59-4a48-98d5-d4b40c7d1d50 style="PADDING-RIGHT: 0px; DISPLAY: inline; PADDING-LEFT: 0px; FLOAT: none; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px"&gt;&lt;PRE class=c# name="code"&gt;[Fact]
public void Tags_Should_Be_Empty_When_Story_Is_Created()
{
    Assert.Empty(_story.Tags);
}

[Fact]
public void Contains_Should_Return_True_When_Tag_Exists_In_Tags()
{
    var tag = new Tag
                  {
                      Id = Guid.NewGuid(),
                      Name = "Dummy Tag"
                  };

    _story.AddTag(tag);

    Assert.True(_story.ContainsTag(new Tag { Name = "Dummy Tag" }));
}

[Fact]
public void AddTag_Should_Increase_Tags_Collection()
{
    int previousCount = _story.Tags.Count;

    var tag = new Tag
                  {
                      Id = Guid.NewGuid(),
                      Name = "Dummy Tag"
                  };

    _story.AddTag(tag);

    Assert.True(_story.Tags.Count &amp;gt; previousCount);
}

[Fact]
public void AddTag_Should_Not_Increase_Tags_Collection_For_Duplicate_Tag()
{
    var tag1 = new Tag
                   {
                       Id = Guid.NewGuid(),
                       Name = "Dummy Tag"
                   };

    _story.AddTag(tag1);

    int previousCount = _story.Tags.Count;

    var tag2 = new Tag
                   {
                       Id = Guid.NewGuid(),
                       Name = "Dummy Tag"
                   };

    _story.AddTag(tag2);

    Assert.Equal(previousCount, _story.Tags.Count);
}

[Fact]
public void RemoveTag_Should_Decrease_Tags_Collection()
{
    var tag = new Tag
                  {
                      Id = Guid.NewGuid(),
                      Name = "Dummy Tag"
                  };

    _story.AddTag(tag);

    int previousCount = _story.Tags.Count;

    _story.RemoveTag(new Tag { Name = "Dummy Tag" });

    Assert.True(_story.Tags.Count &amp;lt; previousCount);
}

[Fact]
public void RemoveAllTags_Should_Make_Tags_Collection_Empty()
{
    _story.AddTag(new Tag { Id = Guid.NewGuid(), Name = "Dummy Tag1" });
    _story.AddTag(new Tag { Id = Guid.NewGuid(), Name = "Dummy Tag2" });

    _story.RemoveAllTags();

    Assert.Empty(_story.Tags);
}&lt;/PRE&gt;&lt;/DIV&gt;
&lt;P&gt;And in story:&lt;/P&gt;
&lt;DIV class=wlWriterSmartContent id=scid:812469c5-0cb0-4c63-8c15-c81123a09de7:e75a5986-4689-49c5-b664-f39e934d451c style="PADDING-RIGHT: 0px; DISPLAY: inline; PADDING-LEFT: 0px; FLOAT: none; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px"&gt;&lt;PRE class=c# name="code"&gt;private readonly List&amp;lt;Tag&amp;gt; _tags = new List&amp;lt;Tag&amp;gt;();

public ICollection&amp;lt;Tag&amp;gt; Tags
{
    get
    {
        return _tags.AsReadOnly();
    }
}

public void AddTag(Tag tag)
{
    if (!ContainsTag(tag))
    {
        _tags.Add(tag);
    }
}

public void RemoveTag(Tag tag)
{
    Tag sameNameTag = _tags.SingleOrDefault(t =&amp;gt; t.Name == tag.Name);

    if (sameNameTag != null)
    {
        _tags.Remove(sameNameTag);
    }
}

public void RemoveAllTags()
{
    _tags.Clear();
}

public bool ContainsTag(Tag tag)
{
    return _tags.Any(t =&amp;gt; t.Name == tag.Name);
}&lt;/PRE&gt;&lt;/DIV&gt;
&lt;H3&gt;Skipping few functionalities&lt;/H3&gt;
&lt;P&gt;We will skip story submit, edit, delete, confirm as spam, view story list for future post.&lt;/P&gt;
&lt;H3&gt;Changing Role of User&lt;/H3&gt;
&lt;P&gt;Admin should be able to change role of User. So we need to introduce another property in User. We will have some predefined role and the functionalities for each role should be static. First, let create an Enum&lt;/P&gt;
&lt;DIV class=wlWriterSmartContent id=scid:812469c5-0cb0-4c63-8c15-c81123a09de7:6c946252-b145-421b-a29c-3afbcdeb8b9c style="PADDING-RIGHT: 0px; DISPLAY: inline; PADDING-LEFT: 0px; FLOAT: none; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px"&gt;&lt;PRE class=c# name="code"&gt;public enum Roles
{
    User = 0,
    Bot = 1,
    Moderator = 2,
    Administrator = 4
}&lt;/PRE&gt;&lt;/DIV&gt;
&lt;P&gt;Next, the user will have a property Role and a method to change the role, we will skip the checking whether the caller is an admin for future when changing the role, test:&lt;/P&gt;
&lt;DIV class=wlWriterSmartContent id=scid:812469c5-0cb0-4c63-8c15-c81123a09de7:45516966-6bed-4fd9-9b8e-c8a932ea4a0b style="PADDING-RIGHT: 0px; DISPLAY: inline; PADDING-LEFT: 0px; FLOAT: none; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px"&gt;&lt;PRE class=c# name="code"&gt;[Fact]
public void ChangeRole_Should_Update_Role()
{
    _user.ChangeRole(Roles.Administrator);

    Assert.Equal(Roles.Administrator, _user.Role);
}&lt;/PRE&gt;&lt;/DIV&gt;
&lt;P&gt;and in User:&lt;/P&gt;
&lt;DIV class=wlWriterSmartContent id=scid:812469c5-0cb0-4c63-8c15-c81123a09de7:525b2117-e0b2-4b88-8731-529a852d88af style="PADDING-RIGHT: 0px; DISPLAY: inline; PADDING-LEFT: 0px; FLOAT: none; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px"&gt;&lt;PRE class=c# name="code"&gt;public Roles Role
{
    get;
    private set;
}

public void ChangeRole(Roles role)
{
    Role = role;
}&lt;/PRE&gt;&lt;/DIV&gt;
&lt;H3&gt;Lock/Unlock User&lt;/H3&gt;
&lt;P&gt;Admin should be able to lock/unlock user. It should be same as Role, we will again skip the admin calling checking for future, tests:&lt;/P&gt;
&lt;DIV class=wlWriterSmartContent id=scid:812469c5-0cb0-4c63-8c15-c81123a09de7:9514651b-a54e-46ce-a52d-f5dd185350bb style="PADDING-RIGHT: 0px; DISPLAY: inline; PADDING-LEFT: 0px; FLOAT: none; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px"&gt;&lt;PRE class=c# name="code"&gt;[Fact]
public void Lock_Should_Update_IsLocked()
{
    _user.Lock();

    Assert.True(_user.IsLocked);
}

[Fact]
public void Unlock_Should_Update_IsLocked()
{
    _user.Unlock();

    Assert.False(_user.IsLocked);
}&lt;/PRE&gt;&lt;/DIV&gt;
&lt;P&gt;And in User:&lt;/P&gt;
&lt;DIV class=wlWriterSmartContent id=scid:812469c5-0cb0-4c63-8c15-c81123a09de7:7bcc10a5-d82f-4a47-913c-9e314dd766b1 style="PADDING-RIGHT: 0px; DISPLAY: inline; PADDING-LEFT: 0px; FLOAT: none; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px"&gt;&lt;PRE class=c# name="code"&gt;public bool IsLocked
{
    get;
    private set;
}

public void Lock()
{
    IsLocked = true;
}

public void Unlock()
{
    IsLocked = false;
}&lt;/PRE&gt;&lt;/DIV&gt;
&lt;H4&gt;Approving Story&lt;/H4&gt;
&lt;P&gt;User can Mark story as spam if they think the story is not relevant, also our spam checkers can block a story from appearing if they detect it as spam. But the Moderator should be the final judge for confirming a story as spam, if the Moderator finds a story is not spam, s/he will approve the story which makes sure the story appearance.&amp;nbsp; For marking the Story as approved we will introduce a new Property ApprovedAt, same as Story published, tests:&lt;/P&gt;
&lt;DIV class=wlWriterSmartContent id=scid:812469c5-0cb0-4c63-8c15-c81123a09de7:be4317a6-c91a-4f06-abfe-0079f62ef1ed style="PADDING-RIGHT: 0px; DISPLAY: inline; PADDING-LEFT: 0px; FLOAT: none; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px"&gt;&lt;PRE class=c# name="code"&gt;[Fact]
public void Approve_Should_Update_ApprovedAt()
{
    var now = DateTime.UtcNow;

    _story.Approve(now);

    Assert.Equal(now, _story.ApprovedAt);
}&lt;/PRE&gt;&lt;/DIV&gt;
&lt;P&gt;And in Story:&lt;/P&gt;
&lt;DIV class=wlWriterSmartContent id=scid:812469c5-0cb0-4c63-8c15-c81123a09de7:75c5eec6-5ee2-48c2-81c5-a76101f9401f style="PADDING-RIGHT: 0px; DISPLAY: inline; PADDING-LEFT: 0px; FLOAT: none; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px"&gt;&lt;PRE class=c# name="code"&gt;public DateTime? ApprovedAt
{
    get;
    private set;
}

public void Approve(DateTime at)
{
    ApprovedAt = at;
}&lt;/PRE&gt;&lt;/DIV&gt;
&lt;P&gt;Note that we are again skipping the checking if the caller is Moderator in Approve method for future.&lt;/P&gt;
&lt;H3&gt;Publishing Story &lt;/H3&gt;
&lt;P&gt;Story publish is a process where popular stories appears in the front page, certainly there should be different strategies for calculating the popularity of story, which we will again it skip for future post, for the time being we will only focus once a story is qualified to publish how do we mark it. As mentioned in the Marking as Spam section that once a Story is published we will update its PublishedAt property, but updating only the PublishedAt does not serves our purpose as we also have to know the Rank to order it in the list. Tests:&lt;/P&gt;
&lt;DIV class=wlWriterSmartContent id=scid:812469c5-0cb0-4c63-8c15-c81123a09de7:9e99ca31-2646-45c2-a5d6-8c4ed0c05db2 style="PADDING-RIGHT: 0px; DISPLAY: inline; PADDING-LEFT: 0px; FLOAT: none; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px"&gt;&lt;PRE class=c# name="code"&gt;[Fact]
public void Publish_Should_Update_PublishedAt()
{
    var now = DateTime.UtcNow;

    _story.Publish(now, 1);

    Assert.Equal(now, _story.PublishedAt);
}

[Fact]
public void Publish_Should_Update_Rank()
{
    _story.Publish(DateTime.UtcNow, 1);

    Assert.Equal(1, _story.Rank);
}&lt;/PRE&gt;&lt;/DIV&gt;
&lt;P&gt;And in story:&lt;/P&gt;
&lt;DIV class=wlWriterSmartContent id=scid:812469c5-0cb0-4c63-8c15-c81123a09de7:09f62218-55c6-4289-b378-27525e2bb982 style="PADDING-RIGHT: 0px; DISPLAY: inline; PADDING-LEFT: 0px; FLOAT: none; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px"&gt;&lt;PRE class=c# name="code"&gt;public DateTime? PublishedAt
{
    get;
    private set;
}

public int? Rank
{
    get;
    private set;
}

public void Publish(DateTime at, int rank)
{
    PublishedAt = at;
    Rank = rank;
}&lt;/PRE&gt;&lt;/DIV&gt;
&lt;P&gt;The following is final object model of this part that we have done so far:&lt;/P&gt;
&lt;P&gt;&lt;A href="http://weblogs.asp.net/blogs/rashid/DomainObjects_4B651F0F.png" mce_href="http://weblogs.asp.net/blogs/rashid/DomainObjects_4B651F0F.png"&gt;&lt;IMG title=DomainObjects style="BORDER-TOP-WIDTH: 0px; DISPLAY: inline; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height=856 alt=DomainObjects src="http://weblogs.asp.net/blogs/rashid/DomainObjects_thumb_7D68D167.png" width=790 border=0 mce_src="http://weblogs.asp.net/blogs/rashid/DomainObjects_thumb_7D68D167.png"&gt;&lt;/A&gt; &lt;/P&gt;
&lt;P&gt;You can also download the complete code from the bottom of this post.&lt;/P&gt;
&lt;P&gt;There are few points in the above which I like to highlight&lt;/P&gt;
&lt;P mce_keep="true"&gt;&amp;nbsp;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;Note that in above we are only focusing on the Domain Logic, we did not mentioned anything about database, UI or any other infrastructural stuffs. &lt;/LI&gt;
&lt;LI&gt;Almost all of the code blocks are 3/4 lines long which is easy to understand and unit test. &lt;/LI&gt;
&lt;LI&gt;There are certain functionalities in the above can be also achieved by using only properties instead of using method, for example in the User we can have IsLocked property with both getter and setter but instead we have created Lock, Unlock method and readonly property IsLocked which I think makes it more explicit and also expressive. &lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;In the next post, we will check how to map this domain model into the database and create repository with LinqToSql with 100% code coverage.&lt;/P&gt;
&lt;P&gt;Stay tuned!!!&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;Download&lt;/STRONG&gt;: &lt;A href="http://weblogs.asp.net/blogs/rashid/KiGG/Kigg.DomainModel.zip" target=_blank mce_href="http://weblogs.asp.net/blogs/rashid/KiGG/Kigg.DomainModel.zip"&gt;Source Code&lt;/A&gt;&lt;/P&gt;
&lt;DIV class=wlWriterHeaderFooter style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px"&gt;
&lt;DIV class=shoutIt&gt;&lt;A href="http://dotnetshoutout.com/Submit?url=http%3a%2f%2fweblogs.asp.net%2frashid%2farchive%2f2009%2f02%2f12%2fdomain-model-developing-kigg-v2-0-part-1.aspx&amp;amp;title=Domain+Model+(Developing+KiGG+v2.0+Part+1)" rev=vote-for mce_href="http://dotnetshoutout.com/Submit?url=http%3a%2f%2fweblogs.asp.net%2frashid%2farchive%2f2009%2f02%2f12%2fdomain-model-developing-kigg-v2-0-part-1.aspx&amp;amp;title=Domain+Model+(Developing+KiGG+v2.0+Part+1)"&gt;&lt;IMG style="BORDER-RIGHT: 0px; BORDER-TOP: 0px; BORDER-LEFT: 0px; BORDER-BOTTOM: 0px" alt="Shout it" src="http://dotnetshoutout.com/image.axd?url=http://weblogs.asp.net/rashid/archive/2009/02/12/domain-model-developing-kigg-v2-0-part-1.aspx" mce_src="http://dotnetshoutout.com/image.axd?url=http://weblogs.asp.net/rashid/archive/2009/02/12/domain-model-developing-kigg-v2-0-part-1.aspx"&gt;&lt;/A&gt;&lt;/DIV&gt;&lt;/DIV&gt;&lt;img src="http://weblogs.asp.net/aggbug.aspx?PostID=6898096" width="1" height="1"&gt;</description><category domain="http://weblogs.asp.net/rashid/archive/tags/Asp.net/default.aspx">Asp.net</category><category domain="http://weblogs.asp.net/rashid/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://weblogs.asp.net/rashid/archive/tags/MVC/default.aspx">MVC</category><category domain="http://weblogs.asp.net/rashid/archive/tags/DotNetShoutout/default.aspx">DotNetShoutout</category><category domain="http://weblogs.asp.net/rashid/archive/tags/DDD/default.aspx">DDD</category><category domain="http://weblogs.asp.net/rashid/archive/tags/TDD/default.aspx">TDD</category><category domain="http://weblogs.asp.net/rashid/archive/tags/ASPNETMVC/default.aspx">ASPNETMVC</category><category domain="http://weblogs.asp.net/rashid/archive/tags/KiGG/default.aspx">KiGG</category></item><item><title>Introducing DotNetShoutout.com</title><link>http://weblogs.asp.net/rashid/archive/2009/01/20/introducing-dotnetshoutout-com.aspx</link><pubDate>Tue, 20 Jan 2009 07:15:00 GMT</pubDate><guid isPermaLink="false">c06e2b9d-981a-45b4-a55f-ab0d8bbfdc1c:6846151</guid><dc:creator>kazimanzurrashid</dc:creator><slash:comments>34</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/rashid/rsscomments.aspx?PostID=6846151</wfw:commentRss><wfw:comment xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/rashid/commentapi.aspx?PostID=6846151</wfw:comment><comments>http://weblogs.asp.net/rashid/archive/2009/01/20/introducing-dotnetshoutout-com.aspx#comments</comments><description>&lt;p&gt;&lt;a href="http://dotnetshoutout.com/" mce_href="http://dotnetshoutout.com/" target="_blank"&gt;DotNetShoutout&lt;/a&gt; is a Web 2.0 style social news site for .NET developers running on our open source project &lt;a href="http://www.codeplex.com/Kigg" mce_href="http://www.codeplex.com/Kigg" target="_blank"&gt;KiGG&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;&lt;b&gt;Background&lt;/b&gt;&lt;/p&gt; KiGG was started as a fun project to get familiar with the new ASP.NET MVC framework and with no exception I started to hear terms like DDD, BDD, SOLID, DRY, Law of Demeter, Testability etc etc which brings me a new world of learning. I found, following alt.net community is the best way to lean these stuffs, I followed them (and still doing) ruthlessly over the blogs, twitter, podcast, discussion group etc. The &lt;a href="http://www.codeplex.com/Kigg/Release/ProjectReleases.aspx?ReleaseId=21737" mce_href="http://www.codeplex.com/Kigg/Release/ProjectReleases.aspx?ReleaseId=21737" target="_blank"&gt;latest release&lt;/a&gt; of KiGG is the outcome whatever I have learned from them in these days.   &lt;p&gt;&lt;b&gt;Goal&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;My goal was to implement a very loosely coupled application with MS supported technologies following the Domain Driven Design. It is developed with:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;ASP.NET MVC Beta &lt;/li&gt;    &lt;li&gt;Linq To SQL &lt;/li&gt;    &lt;li&gt;MS Patterns &amp;amp; Practices – Enterprise Library (Logging &amp;amp; Caching) &lt;/li&gt;    &lt;li&gt;MS Patterns &amp;amp; Practices – Unity &lt;/li&gt;    &lt;li&gt;jQuery &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Unit Testing:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Moq &lt;/li&gt;    &lt;li&gt;xUnit.net &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Other Open Source Projects:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;DotNetOpenId &lt;/li&gt;    &lt;li&gt;HtmlAgilityPack &lt;/li&gt;    &lt;li&gt;jQuery UI &lt;/li&gt;    &lt;li&gt;MarkItUp Editor &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;&lt;b&gt;What’s new in v2.0&lt;/b&gt;&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;OpenID integration. &lt;/li&gt;    &lt;li&gt;Web page thumbnail. &lt;/li&gt;    &lt;li&gt;RSS/Atom. &lt;/li&gt;    &lt;li&gt;Captcha. &lt;/li&gt;    &lt;li&gt;Rich Story/Comment editor via Markdown. &lt;/li&gt;    &lt;li&gt;Both internal and external spam protection (Akismet, TypePad and Defensio) . &lt;/li&gt;    &lt;li&gt;Url shrinking. &lt;/li&gt;    &lt;li&gt;Sitemap including standard, mobile (Google only), news (Google only). &lt;/li&gt;    &lt;li&gt;Open Search. &lt;/li&gt;    &lt;li&gt;Mircroformats hAtom, hReview, hVote, xFolk etc. &lt;/li&gt;    &lt;li&gt;Karma point system for Users. &lt;/li&gt;    &lt;li&gt;Easy sharing with MSDN social, delicious, facebook and twitter. &lt;/li&gt;    &lt;li&gt;Vote Counter with customization (Check the bottom of this post). &lt;/li&gt;    &lt;li&gt;Live Writer Plug-in. &lt;/li&gt;    &lt;li&gt;BlogEngine.NET Extension. &lt;/li&gt;    &lt;li&gt;Both Community Server ICS Module and Graffiti Chalk. &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;&lt;b&gt;Why DotNetShoutout.com&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&lt;a href="http://dotnetshoutout.com" mce_href="http://dotnetshoutout.com" target="_blank"&gt;DotNetShoutout&lt;/a&gt; is like &lt;a href="http://en.wikipedia.org/wiki/Eating_one%27s_own_dog_food" mce_href="http://en.wikipedia.org/wiki/Eating_one%27s_own_dog_food" target="_blank"&gt;eating one’s own dog food&lt;/a&gt;. Since, it has all these nice features and the controversies that are around with our primary source of .NET story, &lt;a href="http://dotnetshoutout.com/about" mce_href="http://dotnetshoutout.com/about" target="_blank"&gt;we&lt;/a&gt; decided why not run a site for our own community that solves these issues and keep you informed with the latest happening of .NET world. So dear reader, I am inviting you in the public beta release of &lt;a href="http://dotnetshoutout.com/" mce_href="http://dotnetshoutout.com/" target="_blank"&gt;DotNetShoutout.com&lt;/a&gt;. Please, tell us what you like, what you do not like, what you think can be improved or added. We are still working on some of the cool features which will be available in 1/2 weeks.&lt;/p&gt;  &lt;p&gt;And I will be also blogging about the technical side of this project in coming days.&lt;/p&gt;  &lt;p&gt;To end this post, I just to share with you that I have been again awarded as MVP for year 2009.&lt;/p&gt;  &lt;p&gt;Long Live .NET!!!&lt;/p&gt;&lt;div class="wlWriterHeaderFooter" style="margin: 0px; padding: 0px;"&gt;&lt;div class="shoutIt"&gt;&lt;a href="http://dotnetshoutout.com/Submit?url=http%3a%2f%2fweblogs.asp.net%2frashid%2farchive%2f2009%2f01%2f20%2fintroducing-dotnetshoutout-com.aspx&amp;amp;title=Introducing+DotNetShoutout.com" rev="vote-for" mce_href="http://dotnetshoutout.com/Submit?url=http%3a%2f%2fweblogs.asp.net%2frashid%2farchive%2f2009%2f01%2f20%2fintroducing-dotnetshoutout-com.aspx&amp;amp;title=Introducing+DotNetShoutout.com"&gt;&lt;img src="http://dotnetshoutout.com/image.axd?url=http://weblogs.asp.net/rashid/archive/2009/01/20/introducing-dotnetshoutout-com.aspx" alt="Shout it" mce_src="http://dotnetshoutout.com/image.axd?url=http://weblogs.asp.net/rashid/archive/2009/01/20/introducing-dotnetshoutout-com.aspx" style="border: 0px none ;"&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;img src="http://weblogs.asp.net/aggbug.aspx?PostID=6846151" width="1" height="1"&gt;</description><category domain="http://weblogs.asp.net/rashid/archive/tags/Asp.net/default.aspx">Asp.net</category><category domain="http://weblogs.asp.net/rashid/archive/tags/Community+News/default.aspx">Community News</category><category domain="http://weblogs.asp.net/rashid/archive/tags/MVC/default.aspx">MVC</category><category domain="http://weblogs.asp.net/rashid/archive/tags/DotNetShoutout/default.aspx">DotNetShoutout</category><category domain="http://weblogs.asp.net/rashid/archive/tags/DDD/default.aspx">DDD</category><category domain="http://weblogs.asp.net/rashid/archive/tags/TDD/default.aspx">TDD</category><category domain="http://weblogs.asp.net/rashid/archive/tags/ASPNETMVC/default.aspx">ASPNETMVC</category></item></channel></rss>