Unit Testing .NET Core

Introduction

With the recent arrival of .NET Core, some things that we were used to having are no longer available. This includes unit tests – plus Visual Studio integration – and mocking frameworks. Fortunately, they are now becoming available, even if, in some cases, in pre-release form.

In this post, I will cover:

  • Unit testing frameworks
  • Mocking libraries
  • Validation libraries

I won’t explore everything that exists, just present a simple setup that works well for .NET Core.

Unit Testing

So, you want to do unit testing? There are a couple of frameworks for that purpose that work with .NET Core, which include:

MS Test is Microsoft’s own unit testing library, I’d say a much hated one because of its historical tie to Visual Studio. This was the last one that was announced for .NET Core and it’s still not in release form.

They more or less work the same way, so let’s see how to use xUnit, my favorite one So, you need to add two Nuget packages:

  • xunit: this is the actual library that you will be using
  • dotnet-test-xunit: this is the runner and integration with Visual Studio

I sometimes have a base class for tests, which takes care of loading configuration and initializing stuff, but let’s leave that aside. A unit test class for xUnit looks like this:

using Xunit;

public class MyTests
{
[Fact]
public void Test()
{
}
}

Now, you need to configure the runner so that you can run tests in Visual Studio. Using project.json, it goes like this:

{
"version": "1.0.0-*",
"testRunner": "xunit",
"buildOptions": {
"emitEntryPoint": false,
"copyToOutput": ["appsettings.json"]
},

"dependencies": {
"dotnet-test-xunit": "2.2.0-preview2-build1029",
"Microsoft.Extensions.Configuration": "1.0.0",
"Microsoft.Extensions.Configuration.Json": "1.0.0",
"Microsoft.Extensions.PlatformAbstractions": "1.0.0",
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.0.0"
},
"xunit": "2.2.0-beta2-build3300"
},

"frameworks": {
"netcoreapp1.0": {
"imports": "dnxcore50"
}
}
}

Notice that I am copying to the output folder the appsettings.json file, I am not going to talk about it, but let’s just say that it has some configuration that my tests will use, and it needs to be on the same file as the unit tests assembly.

With the Visual Studio integration working, we get this:

image

So we can run and debug our tests directly from here. Nice to have coherent behavior for all unit test frameworks!

Mocking

As for mocking, there are also some mocking frameworks that work with .NET Core:

I’ll pick Moq for this exercise. Just add the Moq package to your project. Here’s how to do simple mocking of an interface:

using Xunit;
using Moq;

[Fact]
public void Test()
{
var mock = new Mock<IService>();
mock
.Setup(x => x.Get())
.Returns("Hi there!");

var svc = mock.Object;

var result = svc.Get();

Assert.NotNull(result);

Assert.Equal("Hi there!", result);
}

Exactly the same as you’d do in a classic .NET unit test using mocking.

Validation

As for validations, I know of two libraries that work with .NET Core:

I’ll pick FluentAssertions. Here’s a simple example, for the same test:

using FluentAssertions;
using Moq;
using Xunit;

[Fact]
public void Test()
{
var mock = new Mock<IService>();
mock
.Setup(x => x.Get())
.Returns("Hi there!");

var svc = mock.Object;

var result = svc.Get();

result
.Should()
.BeOfType<string>()
.And
.Be("Hi there!");
}

Again, this should be familiar to everyone.

Conclusion

So, it’s no longer unit tests holding us back from .NET Core! Most of what is done today in classic .NET can be done in Core by now. There are still some more complex libraries, for interception, mapping, serialization, etc, that are not quite there yet, but I expect these to come with time.

Fluent Validation in JavaScript

A recent discussion with my colleagues about fluent validation in JavaScript made me realize that I didn’t know of any such library. I take it for granted that some may exist, but I have never actually used one. To be clear, I mean a validation library that I can use in unit tests, for asserting conditions. Because I had a free Saturday morning, I decided to write my own!

Nothing fancy, just a couple of methods for dealing with common validations. It can be used in both browsers and in Node.js apps. Several validations can be chained and it any fails, an error is thrown. It goes like this:

/**

 * Builds a new Validation object

 * @param {object} obj The object to validate

 */

function Validation(obj) {    

    this.obj = obj;

    this.negated = false;

    this.reporting(null);

}

 

/**

 * Try to find the first argument of a given type

 * @param {array} args An array of arguments

 * @param {string} type The type to find

 * @returns {object} The first argument that matches the given type, or undefined

 */

function findFirstOfType(args, type) {

    for (var i = 0; i < args.length; ++i) {

        if (typeof args[i] === type) {

            return args[i];

        }

    }

    return undefined;

}

 

/**

 * Either returns the first argument as an array or the arguments implicit parameter

 * @param {array} args A possibly array parameter

 * @param {array} array An array of arguments

 * @returns {array} An array of arguments

 */

function getArguments(args, array) {

    var newArray = args;

    

    if (!(args instanceof Array))

    {

        newArray = [ args ];

        for (var i = 1; i < array.length; ++i) {

            newArray.push(array[i]);

        }

    }

    

    return newArray;

}

 

/**

 * Throws an exception in case of an error

 * @param {boolean} error An error flag

 * @param {string} msg An error message

 */

Validation.prototype.assert = function(error, msg) {

    if (error != this.negated) {

        this.report(msg);

    }

};

 

/**

 * Changes the reporting function in case of a validation error. The default is to throw an Error

 * @param {function} fn A reporting function

 * @returns {object} The Validation object

 */

Validation.prototype.reporting = function(fn) {

    if ((!fn) || (typeof fn !== 'function')) {

        fn = function(msg) {

            throw new Error(msg);

        };

    }

    

    this.report = fn;

    return this;

};

 

/**

 * Uses an external validation function

 * @param {function} fn A validation function

 * @param {string} msg An optional error message

 */

Validation.prototype.isValid = function(fn, msg) {

    var self = this;

    msg = msg || 'Validation failed: custom validation function';

    var error = (fn(self.obj) !== true);

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is one of a set of passed values

 * @param {array} args An optional array of arguments

 */

Validation.prototype.isOneOf = function(args) {

    var self = this;

    var msg = 'Validation failed: objects do not match';

    var error = arguments.length > 0;

    args = getArguments(args, arguments);

    

    for (var i = 0; i < args.length; ++i) {

        if (self.obj == args[i]) {

            error = false;

            break;

        }

    }

    

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is not in a set of passed values

 * @param {array} args An optional array of arguments

 */

Validation.prototype.isNoneOf = function(args) {

    var self = this;

    var msg = 'Validation failed: objects do not match';

    var error = false;

    args = getArguments(args, arguments);

        

    for (var i = 0; i < args.length; ++i) {

        if (self.obj == args[i]) {

            error = true;

            break;

        }

    }

    

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate contains a supplied value

 * @param {object} value A value to find

 * @param {string} msg An optional error message

 */

Validation.prototype.contains = function(value, msg) {

    var self = this;

    msg = msg || 'Validation failed: object does not contain target';

    var error = self.obj.length != 0;

    

    for (var i = 0; i < self.obj.length; ++i) {

        if (self.obj[i] == value) {

            error = false;

            break;

        }

    }

    

    this.assert(error, msg);

    return this;    

};

 

/**

 * Checks if the value to validate is equal to a supplied value

 * @param {object} value A value to compare against

 * @param {object} arg1 An optional argument

 * @param {object} arg2 An optional argument

 */

Validation.prototype.isEqualTo = function(value, arg1, arg2) {

    var self = this;

    var caseInsensitive = findFirstOfType([arg1, arg2], 'boolean') || false;

    var msg = findFirstOfType([arg1, arg2], 'string') || 'Validation failed: objects do not match';

    var left = self.obj.toString();

    var right = value.toString();

            

    if (caseInsensitive) {

        left = left.toLowerCase();

        right = right.toLowerCase();

    }

 

    var error = (left != right);

    

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is a string

 * @param {string} msg An optional error message

 */

Validation.prototype.isString = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is not a string';

    var error = ((typeof self.obj !== 'string') && (self.obj.toString() != obj));

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is neither null nor a whitespace string

 * @param {string} msg An optional error message

 */

Validation.prototype.isNotNullOrWhitespace = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is null or whitespace';

    var error = ((self.obj === null) || (self.obj.toString().trim().length == 0));

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is a number

 * @param {string} msg An optional error message

 */

Validation.prototype.isNumber = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is not a number';

    var error = ((typeof self.obj !== 'number') && (Number(self.obj) != self.obj));

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is a positive number

 * @param {string} msg An optional error message

 */

Validation.prototype.isPositive = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is not a number';

    var error = (Number(self.obj) <= 0);

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is a negative number

 * @param {string} msg An optional error message

 */

Validation.prototype.isNegative = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is not a number';

    var error = (Number(self.obj) >= 0);

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is an odd number

 * @param {string} msg An optional error message

 */

Validation.prototype.isOdd = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is not odd';

    var error = (Number(self.obj) % 2 === 0);

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is an even number

 * @param {string} msg An optional error message

 */

Validation.prototype.isEven = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is not even';

    var error = (Number(self.obj) % 2 !== 0);

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is a finite number

 * @param {string} msg An optional error message

 */

Validation.prototype.isFinite = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is infinite';

    var error = !isFinite(self.obj);

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is a function

 * @param {string} msg An optional error message

 */

Validation.prototype.isFunction = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is not a function';

    var error = (typeof self.obj !== 'function');

    this.assert(error, msg);

    return this;    

};

 

/**

 * Checks if the value to validate is a boolean

 * @param {string} msg An optional error message

 */

Validation.prototype.isBoolean = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is not a boolean';

    var error = (typeof self.obj !== 'boolean');

    this.assert(error, msg);

    return this;    

};

 

/**

 * Checks if the value to validate is defined

 * @param {string} msg An optional error message

 */

Validation.prototype.isDefined = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is undefined';

    var error = (self.obj === undefined);

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is null

 * @param {string} msg An optional error message

 */

Validation.prototype.isNull = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is not null';

    var error = (self.obj !== null);

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is of a primitive type

 * @param {string} msg An optional error message

 */

Validation.prototype.isPrimitive = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is not of a primitive type';

    var type = typeof self.obj;

    var error = ((type !== 'number') && (type !== 'string') && (type !== 'boolean'));

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is an array

 * @param {string} msg An optional error message

 */

Validation.prototype.isArray = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is not an array';

    var error = !Array.isArray(self.obj);

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate matches a regular expression

 * @param {string} regex A regular expression

 * @param {string} msg An optional error message

 */

Validation.prototype.isMatch = function(regex, msg) {

    var self = this;

    msg = msg || 'Validation failed: object does not match regular expression';

    var error = !(new RegExp(regex).test(self.obj.toString()));

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is valid JSON

 * @param {string} msg An optional error message

 */

Validation.prototype.isJSON = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is not valid JSON';

    var error = false;

    try

    {

        error = (typeof JSON.parse(self.obj) !== 'object');

    }

    catch(e)

    {

        error = true;

    }

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate has a given length

 * @param {number} max The maximum length

 * @param {object} arg1 An optional argument

 * @param {object} arg2 An optional argument

 */

Validation.prototype.hasLength = function(max, arg1, arg2) {

    var self = this;

    var msg = findFirstOfType([arg1, arg2], 'string') || 'Validation failed: length does not fall between the given values';    

    var min = findFirstOfType([arg1, arg2], 'number') || 0;        

    var str = self.obj.toString();

    var error = str.length > max;

 

    if (!error) {

        error = (str.length < min);

    }

    

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is a Promise

 * @param {string} msg An optional error message

 */

Validation.prototype.isPromise = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is not a promise';

    var error = ((typeof Promise === 'undefined') || !(self.obj instanceof Promise));

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is a Date

 * @param {string} msg An optional error message

 */

Validation.prototype.isDate = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is not a date';

    var error = !(self.obj instanceof Date);

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is an Error

 * @param {string} msg An optional error message

 */

Validation.prototype.isError = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is not an error';

    var error = !(self.obj instanceof Error);

    this.assert(error, msg);

    return this;

};

 

/**

 * Negates the validation logic

 * @returns {object} The Validation object

 */

Validation.prototype.not = function() {

    this.negated = !this.negated;

    return this;

};

 

/**

 * Validates an object

 * @returns {object} The Validation object

 */

Object.prototype.validate = function() {

    return Validation.validate(this);

};

 

Validation.validate = function(obj) {

    var val = new Validation(obj);

    return val;

};

 

if (typeof module !== 'undefined') {

    module.exports = Validation;

}

It’s usage is simple, as would be expected from a fluent library:

//the object to validate

var obj = '1';

 

//fire a bunch of validations - not all make sense

Validation

    .validate(obj)

    .isDefined()

    .isPrimitive()

    .isValid(function(x) { return true })

    .contains('1')

    .isOdd()

    .isOneOf('1', '2', '3')

    .isNoneOf('4', '5', '6')

    .isEqualTo(1)

    .isNotNullOrWhitespace()

    .isString()

    .isNumber()

    .hasLength(2, 1)

    .isMatch('\\d+')

    .isPositive();

The included checks are:

  • isValid: takes a JavaScript function to validate the object;
  • isOneOf, isNoneOf: check to see if the target object is contained/is not contained in a set of values;
  • contains: checks if the object to validate – possibly an array – contains in it a given value;
  • isEqualTo: performs a string comparison, optionally case-insensitive;
  • isString, isNumber, isFunction, isArray, isDate, isJSON, isPromise, isError, isBoolean, isPrimitive: what you might expect from the names;
  • isNotNullOrWhitespace: whether the target object’s string representation is neither null or white spaces;
  • isDefined, isNull: checks if the target is defined (not undefined) or null;
  • isMatch: checks if the object matches a given regular expression;
  • hasLength: validates the object’s length (maximum and optional minimum size);
  • isOdd, isEven, isPositive, isNegative, isFinite: whether the object is odd, even, positive, negative or finite.

All of the checks can be negated as well by using not:

Validation

    .validate(obj)

    .not()

    .isNull()

    .isEven()

    .isNegative()

    .isPromise()

    .isArray()

    .isFunction()

    .isDate()

    .isError()

    .isJSON();

And most can also take an optional message, that will be used instead of the default one:

Validation

    .validate(obj)

    .isNull('Hey, I'm null!');

The actual bootstrapping Validation “class” is actually optional, because of the Object prototype extension method validate:

obj

    .validate()

    .isDefined();

The checks that accept array parameters (isOneOf, isNoneOf) can either take a single array argument or an undefined number or arguments (notice the difference):

obj

    .validate()

    .isOneOf([ 1, 2, 3])

    .isNoneOf(4, 5, 6);

Finally, the actual outcome of a failed validation can be controlled through the reporting function, for example, if you prefer to log to the console instead of raising an exception:

Validation

    .validate(obj)

    .reporting(function(msg) { console.log(msg) })

    .isDefined();

The code is available in GitHub: https://github.com/rjperes/FluentValidationJS/. Please use it as you like, and let me know if you face any issues. By all means, improve it as you like, but let me hear about it! Winking smile

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.

Adding Custom Validation Messages in Silverlight

Sometimes it is useful to add our own validation messages to a Silverlight control, normally because we are doing custom validation through code, not through the reguar IDataErrorInfo, INotifyDataErrorInfo or ValidationAttributes. This is not straightforward, but it is possible. An example might be:

//this class is used for throwing an exception upon binding

//has to be public, doesn't need to implement any interfaces

public class TagModelError

{

    private readonly String errorMessage;

 

    public TagModelError(String errorMessage)

    {

        this.errorMessage = errorMessage;

    }

 

    public Object Tag

    {

        get

        {

            return new Object();

        }

        set

        {

            throw new ValidationException(this.errorMessage);

        }

    }

}

 

//extension method for adding validation messages on a control

public static void AddValidationError(this Control control, String errorMessage)

{

    var expression = elm.GetBindingExpression(FrameworkElement.TagProperty);

 

    if (expression == null)

    {

        expression = control.SetBinding(FrameworkElement.TagProperty, new Binding("Tag")

        {                

            Mode = BindingMode.TwoWay,

            ValidatesOnExceptions = true,

            UpdateSourceTrigger = UpdateSourceTrigger.Explicit,

            Source = new TagModelError(errorMessage)

        }) as BindingExpression;

    }

 

    expression.UpdateSource();

}

In this example I am using the TagProperty, because it is seldom used, but you can use whatever you like. The trick here is to force the update of a bound object, which throws an exception, which in turn is propagated as a validation error.

ASP.NET Web Forms Prompt Validator

For those still using Web Forms and Microsoft’s validation framework, like yours truly – and I know you’re out there! -, it is very easy to implement custom validation by leveraging the CustomValidator control. It allows us to specify both a client-side validation JavaScript function and a server-side validation event handler.

In the past, I had to ask for confirmation before a form was actually submitted; the native way to ask for confirmation is through the browser’s confirm function, which basically displays a user-supplied message and two buttons, OK and Cancel. I wrapped it in a custom reusable validation control, which I am providing here:

   1: [DefaultProperty("PromptMessage")]

   2: public sealed class PromptValidator : CustomValidator

   3: {

   4:     [DefaultValue("")]

   5:     public String PromptMessage { get; set; }

   6:  

   7:     protected override void OnPreRender(EventArgs e)

   8:     {

   9:         var message = String.Concat("\"", this.PromptMessage, "\"");

  10:  

  11:         if ((this.PromptMessage.Contains("{0}") == true) && (this.ControlToValidate != String.Empty))

  12:         {

  13:             message = String.Concat("String.format(\"", this.PromptMessage, "\", args.Value)");

  14:         }

  15:  

  16:         this.ClientValidationFunction = String.Concat("new Function('sender', 'args', 'args.IsValid = confirm(", message, ")')");

  17:         this.EnableClientScript = true;

  18:  

  19:         base.OnPreRender(e);

  20:     }

  21: }

A sample usage without any target control might be:

   1: <web:PromptValidator runat="server" PromptMessage="Do you want to submit your data?" ErrorMessage="!"/>

And if you want to specifically validate a control’s value:

   1: <web:PromptValidator runat="server" PromptMessage="Do you want to accept {0}?" ErrorMessage="!" ControlToValidate="text" ValidateEmptyText="true"/>

When submitting your form, you will get a confirmation prompt similar to this (Chrome):

image