Case Study: Comparing ASP.NET Web Forms and MVC Implementations

Introduction

Apparently, I am the last ASP.NET Web Forms developer in the whole World, which makes me kind of sad… anyway, after much crying, I decided to try out something: a comparison of Web Forms and MVC to solve a real life problem! Now, I only needed a problem to solve… Then, a colleague of mine came questioning me about captchas, so I thought, why not use it as my example? And here we are!

Everyone is familiar with captchas. They are a way to prevent the automated use of web forms, so as to make sure that it is indeed an human that is providing the information. There are lots of implementations out there, and I don’t claim that mine is better or worse, I just implemented it in a couple of hours as an exercise.

Core Library

I designed a couple of classes, framework-agnostic, to hold my abstractions and sample implementations. These are:

Common

A succinct description is in order.

CaptchaSecretPersister

The contract for storing a secret key. Defines two methods, Store and Retrieve.

SessionCaptchaSecretPersister

An implementation of CaptchaSecretPersister that stores the secret key in the ASP.NET session.

CaptchaImageTransformer

The contract for transforming an image. Only has a single method, Transform.

NullCaptchaImageTransformer

Inherits from CaptchaImageTransformer, but doesn’t do anything.

CaptchaSecretGenerator

The contract for generating a new secret key (GetSecret).

RandomCaptchaSecretGenerator

An implementation of CaptchaSecretGenerator that generates a series of random letters.

CaptchaImageProducer

Base class for producing an image with a given secret key, dimensions, colors and an optional image transformer. Only has a single method, GetImage.

GdiCaptchaImageProducer

An implementation of CaptchaImageProducer that uses GDI+.

CaptchaSecretValidator

Base contract for secret key validation. Only defines a method, Validate.

CaseInsensitiveCaptchaSecretValidator

A case-insensitive string comparison implementation of CaptchaSecretValidator.

And here is their code:

public abstract class CaptchaSecretPersister

{

    public abstract void Store(String secret);

 

    public abstract String Retrieve();

}

 

public sealed class SessionCaptchaSecretPersister : CaptchaSecretPersister

{

    public static readonly CaptchaSecretPersister Instance = new SessionCaptchaSecretPersister();

 

    internal const String Key = "Captcha";

 

    public override void Store(String secret)

    {

        HttpContext.Current.Session[Key] = secret;

    }

 

    public override String Retrieve()

    {

        return HttpContext.Current.Session[Key] as String;

    }

}

 

public abstract class CaptchaImageTransformer

{

    public abstract void Transform(Graphics g);

}

 

public sealed class NullCaptchaImageTransformer : CaptchaImageTransformer

{

    public static readonly CaptchaImageTransformer Instance = new NullCaptchaImageTransformer();

 

    public override void Transform(Graphics g)

    {

        //do nothing

    }

}

 

public abstract class CaptchaSecretGenerator

{

    public abstract String GetSecret(UInt32 length);

}

 

public sealed class RandomCaptchaSecretGenerator : CaptchaSecretGenerator

{

    public static readonly CaptchaSecretGenerator Instance = new RandomCaptchaSecretGenerator();

 

    public override String GetSecret(UInt32 length)

    {

        var builder = new StringBuilder();

        var rand = new Random();

 

        for (var i = 0; i < length; i++)

        {

            var ch = (Char)('A' + rand.Next(26));

            builder.Append(ch);

        }

 

        return builder.ToString();

    }

}

 

public abstract class CaptchaImageProducer

{

    public abstract Image GetImage(Int32 width, Int32 height, String secret, Color foreColor, Color backColor, CaptchaImageTransformer transformer);

}

 

public sealed class GdiCaptchaImageProducer : CaptchaImageProducer

{

    public static readonly CaptchaImageProducer Instance = new GdiCaptchaImageProducer();

 

    public override Image GetImage(Int32 width, Int32 height, String secret, Color foreColor, Color backColor, CaptchaImageTransformer transformer)

    {

        var img = new Bitmap(width, height, PixelFormat.Format32bppArgb);

 

        using (var graphics = Graphics.FromImage(img))

        using (var font = new Font(FontFamily.GenericSansSerif, 10F))

        using (var color = new SolidBrush(foreColor))

        {

            graphics.TextRenderingHint = TextRenderingHint.AntiAlias;

            graphics.Clear(backColor);

            graphics.DrawString(secret, font, color, 0F, 0F);

 

            if (transformer != null)

            {

                transformer.Transform(graphics);

            }

 

            return img;

        }

    }

}

 

public abstract class CaptchaSecretValidator

{

    public abstract Boolean Validate(String storedSecret, String secret);

}

 

public sealed class CaseInsensitiveCaptchaSecretValidator : CaptchaSecretValidator

{

    public static readonly CaptchaSecretValidator Instance = new CaseInsensitiveCaptchaSecretValidator();

 

    public override Boolean Validate(String storedSecret, String secret)

    {

        return String.Equals(storedSecret, secret, StringComparison.OrdinalIgnoreCase);

    }

}

Noteworthy:

  • Base classes are always abstract;
  • Actual implementations are sealed, stateless, and therefore define a static read only field, to avoid multiple instantiations.

Both implementations, Web Forms and MVC, will use these classes.

Web Forms

So let’s start playing. Web Forms has the concept of validators. A validator must implement interface IValidator, and a BaseValidator class exists to make the task easier. When a form is submitted by a control that triggers validation, such as Button, all registered validators (Page.Validators) of the same validation group (ValidationGroup) as the trigger control are fired (Validate is called). The page will be considered valid if all validators have their IsValid property set to true. Knowing this, I created a custom control to display the captcha image and perform its validation:

public sealed class CaptchaImage : WebControl, IValidator

{

    public CaptchaImage() : base(HtmlTextWriterTag.Img)

    {

        this.CaptchaSecretGenerator = RandomCaptchaSecretGenerator.Instance;

        this.CaptchaImageTransformer = NullCaptchaImageTransformer.Instance;

        this.CaptchaImageProducer = GdiCaptchaImageProducer.Instance;

        this.CaptchaSecretPersister = SessionCaptchaSecretPersister.Instance;

        this.CaptchaSecretValidator = CaseInsensitiveCaptchaSecretValidator.Instance;

 

        this.CaptchaLength = 4;

        this.CaptchaFormat = ImageFormat.Png;

 

        this.ForeColor = Color.Black;

        this.BackColor = Color.Transparent;

        this.AlternateText = String.Empty;

        this.ControlToSetError = String.Empty;

        this.ControlToValidate = String.Empty;

 

        (this as IValidator).IsValid = true;

    }

 

    public CaptchaSecretPersister CaptchaSecretPersister { get; set; }

 

    public CaptchaSecretGenerator CaptchaSecretGenerator { get; set; }

 

    public CaptchaImageTransformer CaptchaImageTransformer { get; set; }

 

    public CaptchaImageProducer CaptchaImageProducer { get; set; }

 

    public CaptchaSecretValidator CaptchaSecretValidator { get; set; }

 

    [DefaultValue("")]

    public String AlternateText { get; set; }

 

    [DefaultValue(4)]

    public UInt32 CaptchaLength { get; set; }

 

    [DefaultValue(typeof(ImageFormat), "Png")]

    public ImageFormat CaptchaFormat { get; set; }

 

    [DefaultValue("")]

    [IDReferenceProperty]

    [TypeConverter(typeof(ControlIDConverter))]

    public String ControlToValidate { get; set; }

 

    [DefaultValue("")]

    [IDReferenceProperty]

    [TypeConverter(typeof(ControlIDConverter))]

    public String ControlToSetError { get; set; }

 

    [DefaultValue("")]

    public String ErrorMessage { get; set; }

 

 

    public event EventHandler ValidationSuccess;

    public event EventHandler ValidationFailure;

    public event EventHandler ValidationComplete;

 

    private void OnValidationSuccess()

    {

        var handler = this.ValidationSuccess;

 

        if (handler != null)

        {

            handler(this, EventArgs.Empty);

        }

    }

 

    private void OnValidationComplete()

    {

        var handler = this.ValidationComplete;

 

        if (handler != null)

        {

            handler(this, EventArgs.Empty);

        }

    }

 

    private void OnValidationFailure()

    {

        var handler = this.ValidationFailure;

 

        if (handler != null)

        {

            handler(this, EventArgs.Empty);

        }

    }

    

    private ITextControl FindTextControl(String id)

    {

        return this.NamingContainer.FindControl(id) as ITextControl;

    }

 

    protected override void OnInit(EventArgs e)

    {

        if (this.Enabled == true)

        {

            this.Page.Validators.Add(this);

        }

 

        base.OnInit(e);

    }

 

    protected override void Render(HtmlTextWriter writer)

    {

        var secret = this.CaptchaSecretGenerator.GetSecret(this.CaptchaLength);

 

        this.CaptchaPersister.Store(secret);

 

        using (var img = this.CaptchaImageProducer.GetImage((Int32)this.Width.Value, (Int32)this.Height.Value, secret, this.ForeColor, this.BackColor, this.CaptchaImageTransformer))

        using (var stream = new MemoryStream())

        {

            img.Save(stream, this.CaptchaFormat);

 

            this.Attributes[HtmlTextWriterAttribute.Src.ToString().ToLower()] = String.Format("data:image/{0};base64,{1}", this.CaptchaFormat.ToString().ToLower(), Convert.ToBase64String(stream.ToArray()));

            this.Attributes[HtmlTextWriterAttribute.Alt.ToString().ToLower()] = this.AlternateText;

        }

 

        base.Render(writer);

 

        var val = this as IValidator;

 

        if (val.IsValid == false)

        {

            var errorControl = this.FindTextControl(this.ControlToSetError);

 

            if (errorControl != null)

            {

                errorControl.Text = this.ErrorMessage;

            }

            else

            {

                writer.Write(this.ErrorMessage);

            }

        }

    }

 

    Boolean IValidator.IsValid { get; set; }

 

    void IValidator.Validate()

    {

        var val = this as IValidator;

        val.IsValid = true;

 

        var secretControl = this.FindTextControl(this.ControlToValidate);

        

        if (secretControl != null)

        {

            var storedSecret = this.CaptchaSecretPersister.Retrieve();

 

            val.IsValid = this.CaptchaSecretValidator.Validate(storedSecret, secretControl.Text);

 

            if (val.IsValid == true)

            {

                this.OnValidationSuccess();

            }

            else

            {

                this.OnValidationFailure();

            }

 

            this.OnValidationComplete();

        }

    }

}

The CaptchaImage class inherits from WebControl, so that we can leverage some of its properties (ForeColor, BackColor), and defines a containing tag of IMG.

In its constructor, all relevant properties are instantiated to sensible defaults, these include a number of properties for the core library classes (CaptchaSecretPersister, CaptchaSecretGenerator, CaptchaImageProducer, CaptchaImageTransformer and CaptchaSecretValidator). Other important properties are:

  • ErrorMessage: the message to display in case of a validation error;
  • AlternateText: the image’s ALT text;
  • CaptchaLength: the desired length of the secret key; the default is 4;
  • CaptchaFormat: the image format of choice, where the default is ImageFormat.Png;
  • ControlToValidate: the required ID of a server-side control containing text (must implement ITextControl);
  • ControlToSetError: an optional ID for a control that can take text (must implement ITextControl too), which will be used to set the ErrorMessage value.

The IsValid property, from IValidator, is implemented privately because most of the time we do not want to mess with it. Another inherited property, Enabled, can be used to turn off validation.

Then we have three events:

  • ValidationSuccess: raised when the validation occurs and is successful;
  • ValidationFailure: raised when a failed validation occurs;
  • ValidationComplete: always raised when a validation is performed, regardless of its outcome.

When the control loads, OnInit is called, and it registers itself with the Validators collection, this is a required step. In Render, it generates a secret key, stores it, produces an image from it and renders it as an inline image using the Data URI format. It is the same approach that I talked about a number of times before. When the control receives a validation request, the Validate method is called, and the provided key is validated against the stored one.

A typical usage would be like this:

<web:CaptchaImage runat="server" ID="captcha" Width="50px" Height="25px" BackColor="Blue" AlternateText="Captcha riddle" ControlToValidate="text" ErrorMessage="Invalid captcha" OnValidationSuccess="OnSuccess" OnValidationFailure="OnFailure" OnValidationComplete="OnComplete" />

Whereas the registered event handlers might be:

protected void OnSuccess(Object sender, EventArgs e)

{

    this.captcha.Visible = false;

}

 

protected void OnFailure(Object sender, EventArgs e)

{

}

 

protected void OnComplete(Object sender, EventArgs e)

{

}

Easy, don’t you think? Now, let’s move on to MVC!

MVC

In MVC we don’t really have controls, because of the separation of concerns and all that. The closest we have at the moment are extension methods, let’s have a look at the Captcha method:

public static IHtmlString Captcha(this HtmlHelper html, Int32 width, Int32 height, UInt32 length = 4, String alternateText = "", ImageFormat captchaFormat = null, Color? foreColor = null, Color? backColor = null)

{

    var captchaSecretGenerator = DependencyResolver.Current.GetService<CaptchaSecretGenerator>();

    var captchaSecretPersister = DependencyResolver.Current.GetService<CaptchaSecretPersister>();

    var captchaImageProducer = DependencyResolver.Current.GetService<CaptchaImageProducer>();

    var captchaImageTransformer = DependencyResolver.Current.GetService<CaptchaImageTransformer>();

 

    var secret = captchaSecretGenerator.GetSecret(length);

 

    captchaSecretPersister.Store(secret);

 

    using (var img = captchaImageProducer.GetImage(width, height, secret, foreColor ?? Color.Black, backColor ?? Color.Transparent, captchaImageTransformer))

    using (var stream = new MemoryStream())

    {

        img.Save(stream, captchaFormat ?? ImageFormat.Png);

 

        var tag = String.Format("<img src=\"data:image/{0};base64,{1}\" width=\"{1}\" height=\"{2}\" alt=\"{3}\"/>", (captchaFormat ?? ImageFormat.Png).ToString().ToLower(), Convert.ToBase64String(stream.ToArray()), width, height, alternateText);

 

        return new HtmlString(tag);

    }

}

It is unpractical to pass all components, so we are relying on MVC’s built-in dependency injection framework. I used Microsoft Unity as my Inversion of Control (IoC) container, and to make it integrate with MVC, I installed the Unity.Mvc NuGet package:

image

Then I had to register the actual implementations for my services, this is usually done in the App_Start\UnityConfig.cs file’s RegisterTypes method:

container.RegisterInstance<CaptchaSecretGenerator>(RandomCaptchaSecretGenerator.Instance);

container.RegisterInstance<CaptchaImageProducer>(GdiCaptchaImageProducer.Instance);

container.RegisterInstance<CaptchaSecretPersister>(SessionCaptchaSecretPersister.Instance);

container.RegisterInstance<CaptchaImageTransformer>(NullCaptchaImageTransformer.Instance);

container.RegisterInstance<CaptchaSecretValidator>(CaseInsensitiveCaptchaSecretValidator.Instance);

The code from Unity.Mvc automatically hooks Unity with the DependencyResolver class, so we don’t have to do it ourselves. For an example implementation, I once wrote a post on it that you can check out, if you are curious.

Now, we need two things: a view and a controller. Let’s look at the view first (simplified):

@Html.Captcha(50, 25)

@using (Html.BeginForm("Validate", "Home"))

{

    @Html.TextBox("secret")

    <button>Validate</button>

}

As you can see, the form will post to the Validate method of an HomeController. The output of the Captcha method doesn’t have to be inside the form, because it merely renders an IMG tag.

As for the controller, the Validate method is pretty straightforward:

public ActionResult Validate(String secret)

{

    var captchaPersister = DependencyResolver.Current.GetService<CaptchaSecretPersister>();

    var captchaSecretValidator = DependencyResolver.Current.GetService<CaptchaSecretValidator>();

 

    var storedSecret = captchaPersister.Retrieve();

    var isValid = captchaSecretValidator.Validate(storedSecret, secret);

 

    if (isValid == true)

    {

        return this.View("Success");

    }

    else

    {

        return this.View("Failure");

    }

}

It tries to obtain the services from the DependencyResolver, validates the supplied secret key and then returns the proper view, accordingly.

Conclusion

So, which implementation do you prefer? The Web Forms one, unsurprisingly, only needs a single class, CaptchaImage, and, of course, a host page; the assembly containing it can be referenced by other projects and used very easily. As for the MVC version, we need to have the Captcha extension method, the IoC bootstrapping/registration code, a view and a controller with a Validate method similar to the one presented. The extension method and the controller may come from another referenced assembly, it needs some work, but can definitely be done.

Of course, this is a sample implementation, things can be done in a myriad of alternative ways. I’d like to hear from you about the problems with this particular implementation, and alternative ways to do it. By the way, feel free to use and modify the code anyway you want to, I will soon make it available in my GitHub account.

Farewell, ASP.NET Web Forms, We Hardly Knew Ye

ASP.NET Web Forms, the venerable web framework that Microsoft shipped with the .NET framework almost 15 years ago and we all hate love, is going away. Yes, it’s official: ASP.NET 5 will not include Web Forms, only MVC 6. ASP.NET 4.6, however, will still include Web Forms, including some updates, but the future appears to be all MVC (and OWIN, for that matter). Having spend a lot of my professional life working with Web Forms, I feel kind of sad. Yes, I do acknowledge event-hell, viewstate-hell, etc, but I find it easy to get around this once you know a thing or two. But what I really like about Web Forms is the reuse capacity that server-side controls offer: just add a reference to an assembly containing controls, register them in Web.config or in the page, and you’re done. A control encapsulates both client and server-side logic, so it is the perfect reusability mechanism for the web, in my opinion, and I wrote tens of posts on it. MVC now offers somewhat similar mechanisms, in the form of view components and tag helpers, but before that, there was really no reuse mechanism – partial views cannot be referenced from an assembly and helper methods cannot be extended. Don’t get me wrong: I like MVC, but I think that Web Forms was complementary. Taking in consideration the extensibility mechanisms offered by MVC, I can imagine that someone will even implement Web Forms on top of it! Winking smile

Apart from Web Forms, ASP.NET 5 seems promising! I published recently an article on it for the portuguese magazine Programar, which you can find here. I may translate it to english and add a thing or two in the next couple of days, so stay tuned!

Defining ASP.NET Update Panel Template Contents Dynamically

The ASP.NET UpdatePanel was introduced with the ASP.NET 2.0 AJAX Extensions almost a century ago (kidding, but almost feels like it!Winking smile). It allows us to have AJAX-style effects (partial page loads) with very little effort.

The UpdatePanel has a property, ContentTemplate, which is, well, a template, which means it can only be set through markup – not so good for dynamic contents -, or by implementing our own ITemplate class – slightly more work, but this can be reused in other scenarios where an ITemplate is required:

public class Template : Control, ITemplate

{

    public void InstantiateIn(Control container)

    {

        var controls = this.Controls.OfType<Control>().ToList();

 

        for (var i = 0; i < controls.Count; i++)

        {

            var control = controls[i];

            container.Controls.Add(control);

        }

    }

}

 

protected override void OnLoad(EventArgs e)

{

    var tb = new TextBox { ID = "time" };

    var timer = new Timer { Interval = 1000, Enabled = true };

    timer.Tick += timer_Tick;

 

    var tmp = new Template();

    tmp.Controls.Add(tb);

    tmp.Controls.Add(timer);

 

    var up = new UpdatePanel();

    up.ContentTemplate = tmp;

 

    this.Form.Controls.Add(up);

 

    base.OnLoad(e);

}

 

void timer_Tick(object sender, EventArgs e)

{

    var time = (sender as Control).NamingContainer.FindControl("time") as TextBox;

    time.Text = DateTime.Now.ToString();

}

There is also a property, ContentTemplateContainer, which is a control to which you can add your controls, so that they are added to the UpdatePanel. This is exactly what we need:

protected override OnLoad(EventArgs e)

{

    var tb = new TextBox { ID = "time" };

    var timer = new Timer { Interval = 1000, Enabled = true };

    timer.Tick += this.timer_Tick;

    

    var up = new UpdatePanel();

    up.ContentTemplateContainer.Controls.Add(tb);

    up.ContentTemplateContainer.Controls.Add(timer);

    

    this.Form.Controls.Add(up);

    

    base.OnLoad(e);

}

SharePoint XSLT Web Part

After my previous post on XSLT processing, what else could follow? Of course, an XSLT web part for SharePoint! Smile

Here I want to solve a couple of problems:

  • Allow the usage of XSLT 2.0;
  • Have a more flexible parameter passing mechanism than <ParameterBindings>;
  • Make the XSLT extension mechanism (parameters, functions) more usable.

Similar to XsltListViewWebPart and the others, this web part will query SharePoint and return the results processed by a XSLT style sheet. I am going to built on top of the classes introduced in the last post. Here is the SPCustomXsltWebPart (please, do give it a better name…):

public enum XsltVersion

{

    Xslt1 = 1,

    Xslt2 = 2

}

 

public class SPCustomXsltWebPart : WebPart, IWebPartTable

{

    private static readonly Regex parametersRegex = new Regex(@"@(\w+)\b", RegexOptions.IgnoreCase);

 

    [NonSerialized]

    private DataTable table;

    [NonSerialized]

    private IOrderedDictionary parameters;

 

    public SPCustomXsltWebPart()

    {

        this.AddDefaultExtensions = true;

        this.RowLimit = Int32.MaxValue;

        this.Parameters = new ParameterCollection();

        this.XsltVersion = XsltVersion.Xslt1;

    }

 

    [Category("XSLT")]

    [Personalizable(PersonalizationScope.Shared)]

    [WebBrowsable(true)]

    [WebDisplayName("XSL Version")]

    [WebDescription("The XSLT version")]

    [DefaultValue(XsltVersion.Xslt1)]

    public XsltVersion XsltVersion { get; set; }

 

    [Category("XSLT")]

    [Personalizable(PersonalizationScope.Shared)]

    [WebBrowsable(true)]

    [WebDisplayName("XSL Link")]

    [WebDescription("The URL of a file containing XSLT")]

    [DefaultValue("")]

    public String XslLink { get; set; }

 

    [Category("XSLT")]

    [Personalizable(PersonalizationScope.Shared)]

    [WebBrowsable(true)]

    [WebDisplayName("XSL")]

    [WebDescription("The XSLT content")]

    [DefaultValue("")]

    [PersistenceMode(PersistenceMode.InnerProperty)]

    public String Xsl { get; set; }

 

    [Category("Query")]

    [Personalizable(PersonalizationScope.Shared)]

    [WebBrowsable(true)]

    [WebDisplayName("Query")]

    [WebDescription("The CAML query")]

    [DefaultValue("")]

    [PersistenceMode(PersistenceMode.InnerProperty)]

    public String Query { get; set; }

 

    [Category("Query")]

    [Personalizable(PersonalizationScope.Shared)]

    [WebBrowsable(true)]

    [WebDisplayName("Row Limit")]

    [WebDescription("The row limit")]

    [DefaultValue(Int32.MaxValue)]

    public UInt32 RowLimit { get; set; }

 

    [Category("Query")]

    [Personalizable(PersonalizationScope.Shared)]

    [WebBrowsable(true)]

    [WebDisplayName("Lists")]

    [WebDescription("The target lists")]

    [DefaultValue("")]

    public String Lists { get; set; }

 

    [Category("Query")]

    [Personalizable(PersonalizationScope.Shared)]

    [WebBrowsable(true)]

    [WebDisplayName("Webs")]

    [WebDescription("The target webs")]

    [DefaultValue("")]

    public String Webs { get; set; }

 

    [Category("Query")]

    [Personalizable(PersonalizationScope.Shared)]

    [WebBrowsable(true)]

    [WebDisplayName("View Fields")]

    [WebDescription("The view fields")]

    [DefaultValue("")]

    public String ViewFields { get; set; }

 

    [Category("Query")]

    [Personalizable(PersonalizationScope.Shared)]

    [WebBrowsable(true)]

    [WebDisplayName("Query Throttle Mode")]

    [WebDescription("The query throttle mode")]

    [DefaultValue(SPQueryThrottleOption.Default)]

    public SPQueryThrottleOption QueryThrottleMode { get; set; }

 

    [Category("General")]

    [Personalizable(PersonalizationScope.Shared)]

    [WebBrowsable(true)]

    [WebDisplayName("Add Default Extensions")]

    [WebDescription("Adds the default extensions")]

    [DefaultValue(true)]

    public Boolean AddDefaultExtensions { get; set; }

 

    [PersistenceModeAttribute(PersistenceMode.InnerProperty)]

    public ParameterCollection Parameters { get; private set; }

 

    public event EventHandler<XsltExtensionEventArgs> XsltExtension;

 

    protected XsltProvider XsltProvider

    {

        get

        {

            return this.XsltVersion == XsltVersion.Xslt1 ? DefaultXsltProvider.Instance : SaxonXsltProvider.Instance;

        }

    }

    protected virtual void OnXsltExtension(XsltExtensionEventArgs e)

    {

        var handler = this.XsltExtension;

 

        if (handler != null)

        {

            handler(this, e);

        }

    }

 

    protected override void CreateChildControls()

    {

        var xml = this.GetXml();

        var html = this.Render(xml);

        var literal = new LiteralControl(html);

 

        this.Controls.Add(literal);

 

        base.CreateChildControls();

    }

 

    private String GetXslt()

    {

        var xslt = String.Empty;

 

        if (String.IsNullOrWhiteSpace(this.Xsl) == false)

        {

            xslt = this.Xsl;

        }

        else if (String.IsNullOrWhiteSpace(this.XslLink) == false)

        {

            var doc = new XmlDocument();

            doc.Load(this.XslLink);

 

            xslt = doc.InnerXml;

        }

 

        return xslt;

    }

 

    private DataTable GetTable()

    {

        if (this.table == null)

        {

            var query = new SPSiteDataQuery();

            query.Query = this.ApplyParameters(this.Query);

            query.QueryThrottleMode = this.QueryThrottleMode;

            query.RowLimit = this.RowLimit;

            query.Lists = this.Lists;

            query.Webs = this.Webs;

            query.ViewFields = this.ViewFields;

 

            this.table = SPContext.Current.Site.RootWeb.GetSiteData(query);

            this.table.TableName = "Row";

 

            foreach (var column in this.table.Columns.OfType<DataColumn>())

            {

                column.ColumnMapping = MappingType.Attribute;

            }

        }

 

        return this.table;

    }

 

    private String ApplyParameters(String value)

    {

        var parameters = this.GetParameters();

 

        value = parametersRegex.Replace(value, x => this.GetFormattedValue(parameters[x.Value.Substring(1)]));

 

        return value;

    }

 

    private String GetFormattedValue(Object value)

    {

        if (value == null)

        {

            return String.Empty;

        }

 

        if (value is Enum)

        {

            return ((Int32)value).ToString();

        }

 

        if (value is DateTime)

        {

            return SPUtility.CreateISO8601DateTimeFromSystemDateTime((DateTime)value);

        }

 

        if (value is IFormattable)

        {

            return (value as IFormattable).ToString(String.Empty, CultureInfo.InvariantCulture);

        }

 

        return value.ToString();

    }

 

    private IOrderedDictionary GetParameters()

    {

        if (this.parameters == null)

        {

            this.parameters = this.Parameters.GetValues(this.Context, this);

        }

 

        return this.parameters;

    }

 

    private String GetXml()

    {

        var sb = new StringBuilder();

        var table = this.GetTable();

 

        using (var writer = new StringWriter(sb))

        {

            table.WriteXml(writer);

        }

 

        sb

            .Replace("<DocumentElement>", "<dsQueryResponse RowLimit='" + this.RowLimit + "'><Rows>")

            .Replace("</DocumentElement>", "</Rows></dsQueryResponse>");

 

        return sb.ToString();

    }

 

    private String ApplyXslt(String xml, String xslt, XsltExtensionEventArgs args)

    {

        return this.XsltProvider.Transform(xml, xslt, args);

    }

 

    private String Render(String xml)

    {

        if (String.IsNullOrWhiteSpace(xml) == true)

        {

            return String.Empty;

        }

 

        var xslt = this.GetXslt();

 

        if (String.IsNullOrWhiteSpace(xslt) == true)

        {

            return String.Empty;

        }

 

        var extensions = new XsltExtensionEventArgs();

 

        this.OnXsltExtension(extensions);

 

        if (this.AddDefaultExtensions == true)

        {

            var defaultExtensions = Activator.CreateInstance(typeof(Microsoft.SharePoint.WebPartPages.DataFormWebPart).Assembly.GetType("Microsoft.SharePoint.WebPartPages.DataFormDdwRuntime"));

            extensions.AddExtension("http://schemas.microsoft.com/WebParts/v2/DataView/runtime", defaultExtensions);

        }

 

        foreach (var ext in extensions.Extensions)

        {

            extensions.AddExtension(ext.Key, ext.Value);

        }

 

        var parameters = this.GetParameters();

 

        foreach (var key in parameters.Keys.OfType<String>())

        {

            extensions.AddParameter(key, String.Empty, parameters[key].ToString());

        }

 

        foreach (var param in extensions.Parameters)

        {

            extensions.AddParameter(param.Name, param.NamespaceUri, param.Parameter.ToString());

        }

 

        return this.ApplyXslt(xml, xslt, extensions);

    }

 

    void IWebPartTable.GetTableData(TableCallback callback)

    {

        callback(this.GetTable().DefaultView);

    }

 

    PropertyDescriptorCollection IWebPartTable.Schema

    {

        get { return TypeDescriptor.GetProperties(this.GetTable().DefaultView); }

    }

}

This class extends the basic WebPart class and adds a couple of properties:

  • XsltVersion: the XSLT version to use, which will result in either my DefaultXsltProvider or the SaxonXsltProvider being used;
  • XslLink: the URL of a file containing XSLT;
  • Xsl: in case you prefer to have the XSLT inline;
  • Query: a CAML query;
  • Webs: the webs to query;
  • Lists: the lists to query;
  • ViewFields: the fields to return;
  • RowLimit: maximum number of rows to return;
  • QueryThrottleMode: the query throttle mode;
  • AddDefaultExtensions: whether to add the default extension functions and parameters;
  • Parameters: a standard collection of ASP.NET parameter controls.

The web part uses SPSiteDataQuery to execute a CAML query. Before the query is executed, any parameters it may have, in the form @ParameterName,  are replaced by actual values evaluated from the Parameters collection. This gives some flexibility to the queries, because, not only ASP.NET includes parameters for all the common sources, it’s very easy to add new ones. The web part knows how to format strings, enumerations, DateTime objects and in general any object implementing IFormattable; if you wish, you can extend it to support other types, but I don’t think it will be necessary.

An example usage:

<web:SPCustomXsltWebPart runat="server" XslLink="~/Style.xslt">

    <Query>

        <Where><Eq><FieldRef Name='Id'/><Value Type='Number'>@Id</Value></Eq></Where>

    </Query>

    <Parameters>

        <asp:QueryStringParameter Name="Id" QueryStringField="Id" Type="Int32" />

    </Parameters>

</web:SPCustomXsltWebPart>

Notice that one of the Xsl or the XslLink properties must be set, and the same goes for the Query.

Hope you find this useful, and let me know how it works!

     

    Visual Studio Tips 2

    Update: see the third post here.

    OK, continuing with my previous post, here are some more Visual Studio tips, this time, with a lot more pictures! Winking smile

    1. Start several projects simultaneously: when it comes to starting a project in a solution, there are three options:
      1. Start a single, fixed, project;
      2. Start the project where the focus is currently on;
      3. Start multiple projects simultaneously.

      Right click on the solution – PropertiesCommon Projects Startup Project and select Multiple startup projects:

      image

    2. Break on exceptions: Visual Studio can break automatically on first-chance exceptions (ones that may be caught afterwards). This is useful, because if you have catch blocks, you might not even notice them. First-chance exception breaking can be defined on a class per class basis. First, click on DebugExceptions:image

      Then, check the ones that you are interested in:

      image

    3. Conditional breakpoints: you can enable breakpoints based on some condition on the code. First, create a breakpoint, right click in it, then click on Condition:image

      Now you can add any condition you want, either based on local or global variables and methods – watch out for any possible side effects!

      image

      Another option is to only break when the hit count – the number of times the program has processed the instruction – has reached some value. Click on Hit Count and select the right condition, break always is the default:

      image

      Yet another option is to add a filter. This is a limited condition, which can only take a couple of variables. Activate it by clicking on Filter:

      image

    4. Tracepoints: in the Ultimate edition of Visual Studio (sorry, folks that to not have it!) you can send output to the trace window without actually writing any code. How many times did you add a Trace.WriteLine call, or something similar, for this purpose? Create a breakpoint, then click When Hit:image

      Now, add an output string, which can take variables, parameters or properties, plus some special tokens:

      image

      image

    5. Showing or hiding items from Output window: while debugging your application, the Output windows tends to be cluttered with all sorts of messages. Well, you can hide some of them. Just right click on the Output window and check out those that you want to hide:image
    6. Add links: in a project, you can add physical files, which are copied to the project’s folder, or links, which are kept in their original locations. Select AddExisting Item, navigate to the folder containing the file you want to add, select it, but instead of clicking Add, select Add As Link from the drop down:image
    7. Save text files with different encoding: due to several reasons, you might need to save a text file with a particular encoding. When about to save the file, click the drop down in the Save button and select Save with Encoding:image

      Then select the encoding and line ending you want:

      image

    8. Preview Web.config transformations: ASP.NET Web.config Transformations were introduced in Visual Studio 2010 and allow us to perform transformations on the Web.config file based on the current configuration – Debug, Release, etc, upon deployment. The problem is, most of you don’t know it’s possible to view the result file without actually deploying the application, but it is! Right click on a transformation file, like Web.Release.config, and select Preview Transform:image
    9. Generate ASP.NET designer files: when your ASP.NET web forms designer files – like Default.Designer.aspx.cs – become corrupted, for whatever reason, it’s better to remove them and have them generated by Visual Studio. Right click on the markup file – .aspx, .master or .ascx – and click on Project – Convert to Web Application (weird choice of name, I know):image
    10. Debug MSBuild scripts: I left the best for last! It’s possible to debug .csproj build tasks, which are basically MSBuild tasks. I won’t cover it all here, but instead make sure you read this post by Dan Moseleyhttp://blogs.msdn.com/b/visualstudio/archive/2010/07/06/debugging-msbuild-script-with-visual-studio.aspx.

    I will be back! Winking smile

    ASP.NET Web Forms Extensibility: Model Binding Value Providers

    ASP.NET 4.5 introduced model binding: basically, it is a way for databound controls – Repeater, GridView, ListView, etc – to be fed, not from a datasource control – ObjectDataSource, EntityDataSource, SqlDataSource, etc -, but from a method in the page. This method needs to return a collection, and may have parameters. The problem is: how these parameters get their values? The answer is: through a model binding value provider.

    A model binding value provider is a class that implements IValueProvider, and normally is injected through a ValueProviderSourceAttribute-derived attribute. ASP.NET includes some implementations:

    If we want, say, to return a value from the Common Service Locator, it’s pretty easy. First, an attribute:

    [Serializable]

    [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]

    public sealed class ServiceLocatorAttribute : ValueProviderSourceAttribute

    {

        private readonly Type serviceType;

        private readonly String key;

    
    

        public ServiceLocatorAttribute(Type serviceType, String key = null)

        {

            this.serviceType = serviceType;

            this.key = key;

        }

    
    

        public override IValueProvider GetValueProvider(ModelBindingExecutionContext modelBindingExecutionContext)

        {

            return new ServiceLocatorValueProvider(this.serviceType, this.key);

        }

    }

    And now the actual value provider:

    public sealed class ServiceLocatorValueProvider : IValueProvider

    {

        private readonly Type serviceType;

        private readonly String key;

    
    

        public ServiceLocatorValueProvider(Type serviceType, String key)

        {

            this.serviceType = serviceType;

            this.key = key;

        }

    
    

        public Boolean ContainsPrefix(String prefix)

        {

            return true;

        }

    
    

        public ValueProviderResult GetValue(String key)

        {

            return new ValueProviderResult(ServiceLocator.Current.GetInstance(this.serviceType, this.key), null, CultureInfo.CurrentCulture);

        }

    }

    You can even have the ServiceLocatorAttribute implement the IValueProvider interface, I just separated it because conceptually they are different things.

    Finally, here’s how we would use it:

    public IQueryable<SomeEntity> GetItems([ServiceLocator(typeof(MyComponent), "SomeKey")] MyComponent cmp)

    {

        //do something

    }

    Pretty sleek, don’t you think? Winking smile

    ASP.NET Web Forms Extensibility: Control Builder Interceptors

    After my previous post on Control Builders, what could possibly come next? Of course, Control Builder Interceptors! Not much documentation on this one, which is a shame, because it is an even more powerful feature that was recently introduced in ASP.NET 4.5.

    A Control Builder Interceptor inherits from, unsurprisingly, ControlBuilderInterceptor. This is configured for the whole application, in the Web.config file, in the compilation section, by a controlBuilderInterceptorType (sorry, no link, since the ASP.NET 4.5 documentation is not online) attribute:

    <compilation targetFramework="4.5" controlBuilderInterceptorType="MyNamespace.MyControlBuilderInterceptor, MyAssembly" />

    Similarly to Control Builders, a Control Builder Interceptor allows us to:

    Granted, less than Control Builders, but the point here is that this is fired for all markup-declared controls, not just those that have a specific Control Builder applied to. With that in mind, we can write code like this:

    public class MyControlBuilderInterceptor : ControlBuilderInterceptor

    {

        //raised for every control on markup

        public static event Action<ControlInterceptedEventArgs> ControlIntercepted;

     

        public override void OnProcessGeneratedCode(ControlBuilder controlBuilder, CodeCompileUnit codeCompileUnit, CodeTypeDeclaration baseType, CodeTypeDeclaration derivedType, CodeMemberMethod buildMethod, CodeMemberMethod dataBindingMethod, IDictionary additionalState)

        {

            var controlDeclaration = buildMethod.Statements[0] as CodeVariableDeclarationStatement;

     

            if (controlDeclaration != null)

            {

                var controlName = controlDeclaration.Name;

     

                buildMethod.Statements.Insert(buildMethod.Statements.Count - 1, new CodeSnippetStatement(String.Concat(this.GetType().FullName, ".Intercept(@", controlName, ");")));

            }

     

            base.OnProcessGeneratedCode(controlBuilder, codeCompileUnit, baseType, derivedType, buildMethod, dataBindingMethod, additionalState);

        }

     

        public override void PreControlBuilderInit(ControlBuilder controlBuilder, TemplateParser parser, ControlBuilder parentBuilder, Type type, String tagName, String id, IDictionary attributes, IDictionary additionalState)

        {

            if ((attributes != null) && (attributes.Contains("Text") == true))

            {

                //make property value uppercase

                attributes["Text"] = (attributes["Text"] as String).ToUpper();

            }

     

            base.PreControlBuilderInit(controlBuilder, parser, parentBuilder, type, tagName, id, attributes, additionalState);

        }

     

        public static void Intercept(Control instance)

        {

            var handler = ControlIntercepted;

     

            if (handler != null)

            {

                handler(new ControlInterceptedEventArgs(instance));

            }

        }

    }

    And there you have it. By adding an event handler to MyControlBuilderInterceptor.ControlIntercepted, we can analyze and change the properties of every control:

    [Serializable]

    public sealed class ControlInterceptedEventArgs : EventArgs

    {

        public ControlInterceptedEventArgs(Control control)

        {

            this.Control = control;

        }

     

        public Control Control { get; private set; }

    }

     

    MyControlBuilderInterceptor.ControlIntercepted += e =>

    {

        var myControl = e.Control as MyControl;

        

        if (myControl != null)

        {

            myControl.Text = myControl.Text.ToUpper();

        }

    };

    Stay tuned for more extensibility points of your favorite framework!

    ASP.NET Web Forms Extensibility: Control Builders

    One of the most often ignored extensibility point in Web Forms is the Control Builder. Control Builders are subclasses of ControlBuilder (or other more specialized, such as FileLevelPageControlBuilder, for pages, or FileLevelMasterPageControlBuilder, for master pages) that can be specified per class. It controls some aspects of a control instance:

    It also allows overriding a couple of things:

    • The parameters specified in the markup (Init);
    • What to do when the control builder is added to a parent control builder (OnAppendToParentBuilder);
    • Modify the code that will be generated in the code-behind class that is produced by ASP.NET or the code that will be used to instantiate the control (ProcessGeneratedCode);
    • Change the tag’s inner textual content (SetTagInnerText);
    • Etc.

    This is a powerful mechanism, which has even been used to allow generic control classes. We apply a control builder through a ControlBuilderAttribute (for regular controls) or FileLevelControlBuilderAttribute for pages, master pages or user controls.

    I won’t go into many details, but instead I will focus on the Init and ProcessGeneratedCode methods.

    Init let’s us do things such as:

    public override void Init(TemplateParser parser, ControlBuilder parentBuilder, Type type, String tagName, String id, IDictionary attribs)

    {

        if (type == typeof(SomeBaseControl)

        {

            //replace the control's type for another one

            type = typeof(SomeDerivedControl);

     

            //convert an hypothetical Text property value to upper case

            attribs["Text"] = (attribs["Text"] as String).ToUpper();

        }

     

        base.Init(parser, parentBuilder, type, tagName, id, attribs);

    }

    And ProcessGeneratedCode, messing with the generated page class:

    public override void ProcessGeneratedCode(CodeCompileUnit codeCompileUnit, CodeTypeDeclaration baseType, CodeTypeDeclaration derivedType, CodeMemberMethod buildMethod, CodeMemberMethod dataBindingMethod)

    {

        //add some interface to the generated page class

        derivedType.BaseTypes.Add(typeof(ISomeInterface));

     

        //add a property implementation to the generated page class

        var prop = new CodeMemberProperty();

        prop.Attributes = MemberAttributes.Public;

        prop.Name = "SomeProperty";

        prop.Type = new CodeTypeReference(typeof(String));    

        prop.GetStatements.Add(new CodeMethodReturnStatement(new CodePrimitiveExpression("Hello, World, from a generated property!")));

        

        derivedType.Members.Add(prop);

     

        base.ProcessGeneratedCode(codeCompileUnit, baseType, derivedType, buildMethod, dataBindingMethod);

    }

    But also something MUCH more fun! Imagine you are using an IoC container – I will use Unity, but you can use whatever you want. We might have something like this in Application_Start (or whatever method spawned from it);

    var unity = new UnityContainer();

    unity.RegisterInstance<MyControl>(new MyControl { Text = "Bla bla" });

    ServiceLocator.SetLocatorProvider(() => new UnityServiceLocator(unity));

    Notice I am using the Common Service Locator to abstract the IoC container and to make the code independent of it. Here, I am assigning a static instance to the MyControl type, in essence, a singleton.

    Now, we can change our control builder so as to have the control build method return this instance:

    public override void ProcessGeneratedCode(CodeCompileUnit codeCompileUnit, CodeTypeDeclaration baseType, CodeTypeDeclaration derivedType, CodeMemberMethod buildMethod, CodeMemberMethod dataBindingMethod)

    {

        //return ServiceLocator.Current.GetInstance(typeof(MyControl));

        var type = Type.GetType((buildMethod.Statements[0] as CodeVariableDeclarationStatement).Type.BaseType);

        var currentProperty = new CodePropertyReferenceExpression(new CodeTypeReferenceExpression(typeof (ServiceLocator)), "Current");

        var getInstance = new CodeMethodInvokeExpression(currentProperty, "GetInstance", new CodeTypeOfExpression(type));

        var @cast = new CodeCastExpression(type, getInstance);

        var @return = new CodeMethodReturnStatement(@cast);

     

        buildMethod.Statements.Clear();

        buildMethod.Statements.Add(@return);

     

        base.ProcessGeneratedCode(codeCompileUnit, baseType, derivedType, buildMethod, dataBindingMethod);

    }

    In case you didn’t notice, what this does is, every time the MyControl control is instantiated in a page, for every request, ASP.NET will always return the same instance!

    Now, I am not saying that you SHOULD do this, but only that you CAN do this! Winking smile

    Take care out there…

    Conditional Content in SharePoint Markup

    In SharePoint, there are several web parts that allow us to have different contents depending on some conditions:

    • LoginView (ASP.NET): allows the definition of templates per authenticated or anonymous user:
    <asp:LoginView runat="server">

        <AnonymousTemplate>

            <!-- anonymous content -->

        </AnonymousTemplate>

        <LoggedInTemplate>

            <!-- authenticated content -->

        </LoggedInTemplate>

        <RoleGroups>

            <asp:RoleGroup Roles="Admin">

                <ContentTemplate>

                    <!-- admin content -->

                </ContentTemplate>

            </asp:RoleGroup>

        </RoleGroups>

    </asp:LoginView>

    <SharePoint:SPSecurityTrimmedControl runat="server" PermissionMode="All" PermissionContext="CurrentSite" Permissions="ManageWeb">

        <!-- secure content -->

    </SharePoint:SPSecurityTrimmedControl>

    • EditModePanel: for displaying contents in a web part page depending on its edit mode:
    <PublishingWebControls:EditModePanel runat="server" PageDisplayMode="Edit">

        <!-- edit content -->

    </PublishingWebControls:EditModePanel>

    <SharePoint:UIVersionedContent runat="server" UIVersion="4">

        <ContentTemplate>

            <!-- content for SharePoint 2010 -->

            <!-- no code is run if does not match UIVersion -->

        </ContentTemplate>

    </SharePoint:UIVersionedContent>

     

     

    <SharePoint:VersionedPlaceholder runat="server" UIVersion="4">

        <!-- content for SharePoint 2010 -->

        <!-- code is always run but not rendered if does not match UIVersion -->

    </SharePoint:VersionedPlaceholder>

    • AuthoringContainer: displays content depending on whether the current user has write or read rights on the current page or if it has an associated list item:
    <PublishingWebControls:AuthoringContainer runat="server" DisplayAudience="ReadersOnly">

        <!-- content for readers -->

    </PublishingWebControls:AuthoringContainer>

    <PublishingWebControls:DeviceChannelPanel runat="server" IncludedChannels="iPhone">

        <!-- content for iPhones -->

    </PublishingWebControls:DeviceChannelPanel>

    • DataViewWebPart: allows the passing of parameters and the usage of XSL for rendering logic;

    I imagine you are now rolling your eyes: DataViewWebPart? how come? Well, because it doesn’t need to point to a specific list or view (unlike XsltListViewWebPart), it is very useful for markup-based customizations that will only depend on parameters:

       1: <WebPartPages:DataFormWebPart runat="server" Title="Conditional Content">

       2:     <ParameterBindings>

       3:         <ParameterBinding Name="MyParameter" Location="QueryString(MyParameter)"/>

       4:     </ParameterBindings>

       5:     <XSL>

       6:         <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl asp" xmlns:asp="System.Web.UI.WebControls">                                                  

       7:             <xsl:param name="MyParameter"/>                                                                                                                                        

       8:             <xsl:template match="/">

       9:                 <asp:Label runat="server" Text="Some content, just to show ASP.NET controls inside a SharePoint DataFormWebPart"/>

      10:                 <xsl:choose>

      11:                     <xsl:when test="$MyParameter=''">

      12:                         No parameter...

      13:                     </xsl:when>

      14:                     <xsl:otherwise>

      15:                         Allright! <xsl:value-of select="$MyParameter"/>

      16:                     </xsl:otherwise>

      17:                 </xsl:choose>                                                                                                

      18:             </xsl:template>

      19:         </xsl:stylesheet>

      20:     </XSL>                                        

      21: </WebPartPages:DataFormWebPart>

    You can use this technique for:

    • Including scripts and stylesheets;
    • Including server-side controls.

    It’s just a matter of rolling out some XSL to the rescue!

    You may be already familiar with the available parameters, but you can find the full list here: http://msdn.microsoft.com/en-us/library/office/ff630170(v=office.15).aspx.

    Another SharePoint Designer-only solution that may come in handy! 😉