Saturday, August 24, 2013

XUnit.PhantomQ v1.1

I recently blogged about how to Use XUnit to Run QUnit Tests. The initial v1.0 release of XUnit.PhantomQ did not support error messages, but now in v1.1 it supports the must have feature of bringing error messages back with failed test results.

XUnit.PhantomQ on NuGet
XUnit.PhantomQ Source on GitHub

Enjoy,
Tom

Tuesday, August 13, 2013

Control Minification per Request with Web Optimizations

The Microsoft ASP.NET Web Optimization Framework is a great bundling and minification solution for your web applications. Simply grab the Microsoft.AspNet.Web.Optimization NuGet package, register your bundles, render them with a single line of code, and your environment will automatically resolve your dependencies based on whether or not the web server is running in debug mode.

But how can you debug minified styles and scripts in production?

Normally that is a difficult proposition, but here is a simple solution: JUST DON'T MINIFY THEM! With the little code snippets below you can add a simple query string parameter to disable minification for specific sessions or requests.

Adding this functionality to your website is extremely easy and requires no additional dependencies. Web Optimizations already has an internal AssetManager class that supports this functionality, we just need to access it via reflection.

Simply apply the following two steps and you will be ready to debug in production:

  1. Create the HtmlHelperExtensions class with the code below.
  2. Add a call to TrySetOptimizationEnabled inside of your ViewStart.

_ViewStart.cshtml

@using System.Web.Optimization
@{
    Layout = "~/Views/Shared/_Layout.cshtml";
    Html.TrySetOptimizationEnabled();
}

HtmlHelperExtensions.cs

public static class HtmlHelperExtensions
{
    public const string Key = "OptimizationEnabled";
 
    public static bool TrySetOptimizationEnabled(this HtmlHelper html)
    {
        var queryString = html.ViewContext.HttpContext.Request.QueryString;
        var session = html.ViewContext.HttpContext.Session;
 
        // Check the query string first, then the session.
        return TryQueryString(queryString, session) || TrySession(session);
    }
 
    private static bool TryQueryString(
        NameValueCollection queryString, 
        HttpSessionStateBase session)
    {
        // Does the query string contain the key?
        if (queryString.AllKeys.Contains(
            Key, 
            StringComparer.InvariantCultureIgnoreCase))
        {
            // Is the value a boolean?
            bool boolValue;
            var stringValue = queryString[Key];
            if (bool.TryParse(stringValue, out boolValue))
            {
                // Set the OptimizationEnabled flag
                // and then store that value in session.
                SetOptimizationEnabled(boolValue);
                session[Key] = boolValue;
                return true;
            }
        }
 
        return false;
    }
 
    private static bool TrySession(HttpSessionStateBase session)
    {
        if (session != null)
        {
            var value = session[Key] as bool?;
            if (value.HasValue)
            {
                // Use the session value to set the OptimizationEnabled flag.
                SetOptimizationEnabled(value.Value);
                return true;
            }
        }
 
        return false;
    }
 
    private static void SetOptimizationEnabled(bool value)
    {
        // Use reflection to set the internal AssetManager.OptimizationEnabled
        // flag for this request specific.
        var instance = ManagerProperty.GetValue(null, null);
        OptimizationEnabledProperty.SetValue(instance, value);
    }
 
    private static readonly PropertyInfo ManagerProperty = typeof(Scripts)
        .GetProperty("Manager", BindingFlags.Static | BindingFlags.NonPublic);
 
    private static readonly PropertyInfo OptimizationEnabledProperty = Assembly
        .GetAssembly(typeof(Scripts))
        .GetType("System.Web.Optimization.AssetManager")
        .GetProperty(
            "OptimizationEnabled",
            BindingFlags.Instance | BindingFlags.NonPublic);
}
Shout it

Enjoy,
Tom

Wednesday, August 7, 2013

Last in Win Replication for RavenDB

One of my favorite features of RavenDB is how easy it is customize and extend.

RavenDB offers an extremely easy to use built in replication bundle. To deal with replication conflicts, the RavenDB.Database NuGet Package includes an abstract base class (the AbstractDocumentReplicationConflictResolver) that you can implement with your own conflict resolution rules.

Last In Wins Replication Conflict Resolver

John Bennett wrote a LastInWinsReplicationConflictResolver for RavenDB 1.0, and I have updated it for RavenDB 2.0 and 2.5. As always you can get that code from GitHub!

Download RavenExtensions from GitHub

Once you have built your resolver, you need only drop the assembly into the Plugins folder at the root of your RavenDB server and it will automatically be detected and loaded the next time that your server starts.

public class LastInWinsReplicationConflictResolver
    : AbstractDocumentReplicationConflictResolver
{
    private readonly ILog _log = LogManager.GetCurrentClassLogger();
 
    public override bool TryResolve(
        string id,
        RavenJObject metadata,
        RavenJObject document,
        JsonDocument existingDoc,
        Func<string, JsonDocument> getDocument)
    {
        if (ExistingDocShouldWin(metadata, existingDoc))
        {
            ReplaceValues(metadata, existingDoc.Metadata);
            ReplaceValues(document, existingDoc.DataAsJson);
            _log.Debug(
                "Replication conflict for '{0}' resolved with existing doc",
                id);
        }
        else
        {
            _log.Debug(
                "Replication conflict for '{0}' resolved with inbound doc",
                id);
        }
 
        return true;
    }
 
    private static bool ExistingDocShouldWin(
        RavenJObject newMetadata, 
        JsonDocument existingDoc)
    {
        if (existingDoc == null ||
            ExistingDocHasConflict(existingDoc) ||
            ExistingDocIsOlder(newMetadata, existingDoc))
        {
            return false;
        }
 
        return true;
    }
 
    private static bool ExistingDocHasConflict(JsonDocument existingDoc)
    {
        return existingDoc.Metadata[Constants.RavenReplicationConflict] != null;
    }
 
    private static bool ExistingDocIsOlder(
        RavenJObject newMetadata,
        JsonDocument existingDoc)
    {
        var newLastModified = GetLastModified(newMetadata);
 
        if (!existingDoc.LastModified.HasValue ||
            newLastModified.HasValue &&
            existingDoc.LastModified <= newLastModified)
        {
            return true;
        }
 
        return false;
    }
 
    private static DateTime? GetLastModified(RavenJObject metadata)
    {
        var lastModified = metadata[Constants.LastModified];
 
        return (lastModified == null)
            ? new DateTime?()
            : lastModified.Value<DateTime?>();
    }
 
    private static void ReplaceValues(RavenJObject target, RavenJObject source)
    {
        var targetKeys = target.Keys.ToArray();
        foreach (var key in targetKeys)
        {
            target.Remove(key);
        }
 
        foreach (var key in source.Keys)
        {
            target.Add(key, source[key]);
        }
    }
}
Shout it

Enjoy,
Tom

Thursday, August 1, 2013

PhantomJS, the Headless Browser for your .NET WebDriver Tests

Did you know that Selenium already supports PhantomJS?

WebDriver is a specification for controlling the behavior of a web browser. PhantomJS is a headless WebKit scriptable with a JavaScript API. Ghost Driver is a WebDriver implementation that uses PhantomJS for its back-end. Selenium is a software testing framework for web applications. Selenium WebDriver is the successor to Selenium RC. The Selenium WebDriver NuGet Package is a .NET client for for Selenium WebDriver that includes support for PhantomJs via GhostDriver.

NuGet Packages

You need only install two NuGet packages in order to use PhantomJS with WebDriver. You will probably also want which ever Unit Testing framework you prefer. As always, I suggest xUnit.

  1. Selenium.WebDriver
  2. phantomjs.exe

PhantomJSDriver

After installing those, using the PhantomJSDriver is as easy as any other WebDriver!

const string PhantomDirectory =
    @"..\..\..\packages\phantomjs.exe.1.8.1\tools\phantomjs";
 
[Fact]
public void GoogleTitle()
{
    using (IWebDriver phantomDriver = new PhantomJSDriver(PhantomDirectory))
    {
        phantomDriver.Url = "http://www.google.com/";
        Assert.Contains("Google", phantomDriver.Title);
    }
}
Shout it

Enjoy,
Tom

Real Time Web Analytics