AdRotator for ASP.NET MVC

I was looking  for an Ad Rotator for DotNetShoutout, 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.

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<Ad> matchedAds = ads.Where(ad => string.Compare(ad.Keyword, keywordFilter, StringComparison.InvariantCultureIgnoreCase) == 0)
                                      .OrderBy(ad => ad.Impressions)
                                      .ToList();

            if (matchedAds.Count > 0)
            {
                int max = matchedAds.Sum(ad => ad.Impressions);
                int random = new Random().Next(max + 1);
                int runningTotal = 0;

                foreach(Ad ad in matchedAds)
                {
                    runningTotal += ad.Impressions;

                    if (random <= runningTotal)
                    {
                        targetAd = ad;
                        break;
                    }
                }

                if (targetAd == null)
                {
                    targetAd = matchedAds.Last();
                }
            }

            return targetAd;
        }

        private static string GenerateHtml(Ad ad)
        {
            Action<TagBuilder, object> merge = (builder, values) =>
                                               {
                                                   if (values != null)
                                                   {
                                                       builder.MergeAttributes(new RouteValueDictionary(values));
                                                   }
                                               };

            Action<TagBuilder, string, string> mergeIfNotBlank =    (builder, name, value) =>
                                                                    {
                                                                         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();
        }
    }
}

And in the view I can use it like the following:

<%= 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 }
             )%>

It will randomly rotate based upon the impression value like the original control.  Though keyword filtering is not required for DotNetShoutout, but I have implemented it, maybe you can find it useful.

Shout it

4 Comments

  • Did you submit this to KiGG project?
    Really good job, and good implementation

  • @mosessaur: Not yet, we do it soon.

  • Nice blog...

  • Good post! I would suggest using 3 extension methods in the GenerateHtml method, instead of the actions. Will improve readability, and they can be reused by other controls:

    private static string GenerateHtml(Ad ad)
    {
    TagBuilder imageBuilder = new TagBuilder("img");

    imageBuilder.MergeAttributes(ad.ImageAttributes); // 1
    imageBuilder.MergeAttributeIfNotBlank("src", ad.ImageUrl); // 2
    imageBuilder.MergeAttributeWithDefaultIfBlank("alt", ad.AlternateText, ""); // 3

    TagBuilder linkBuilder = new TagBuilder("a");

    linkBuilder.MergeAttributes(ad.LinkAttributes);
    linkBuilder.MergeAttributeIfNotBlank("href", ad.NavigateUrl);
    linkBuilder.MergeAttributeIfNotBlank("target", ad.Target);

    linkBuilder.InnerHtml = imageBuilder.ToString(TagRenderMode.SelfClosing);

    return linkBuilder.ToString();
    }

Comments have been disabled for this content.