Using the Bundling and Minification Web Optimisations with Asp.Net WebForms App_Themes

by Michael Henstock 30. October 2012 11:17

The bundling and minification that arrived with the latest .Net 4.5 framework is a great tool. For those web applications that are using the WebForms App_Themes folder to style an application, it won't quite work as well as it is capable of. The App_Themes folder will automatically render down all CSS files it finds in the folder and sub directories, so if you register these files in your bundle config you will end up with two versions of the stylesheet rendered down to the client.

So how do we dynamically register some CSS files in an App_Theme folder without the default files being rendered down to the client? The short answer is, you can't. The long answer is, we can with a bit of a work around.

If we take out the CSS files (including any images etc referenced by the style sheets) and move them to another folder, say AppTheme, put our files into a directory under the AppTheme folder with the same theme name, let's go with Default. We can now go and register these files in our bundle config. The default method for registering style sheets isn't quite going to cut it as that defines all the file locations at application load time and we're going to want to dynamically change this folder depending on the theme selected for the current request.
 
Luckily, we already have this capability in the default web optimisation library. We just need to use the DynamicFolderBundle class as follows:
bundles.Add(new DynamicFolderBundle("Style", "*.css"));
And then on the page where you register the bundle, you can just register it with the following:
<%: System.Web.Optimization.Styles.Render("~/AppThemes/" + this.Theme + "/Style") %>
Which tells the bundler to look under the AppThemes/{ThemeName} folder and register all the css files under the one bundle. It's also possible for the DynamicFolderBundle to search all the sub-directories for CSS files, but you'll want to be careful with that if you have styles that reference external files such as background images.

Tags:

Asp.Net | C#

Extending CruiseControl.net with a sample trigger that fires when a file exists

by Michael Henstock 21. October 2012 11:03

This is a simple example of a CruiseControl.net Trigger server plugin which is based on the sample given in the source code. It's probably best to familiarise yourself with that and the plugin examples at http://www.cruisecontrolnet.org/projects/ccnet (of which this example is heavily based off, if you really wanted you could just go and play with the sample code provided by cruisecontrol.net and skip this article, it's okay I won't be offended).

The first caught me out (despite it being well documented here), was the need to name your plugin dlls in the format of ccnet.*.plugin.dll. That's the only requirement for your plugins as once cruise control has that it just uses reflection to find everything it needs from your dll.

To start, create your class, have it implement the ITrigger interface, and assign the ReflectorType attribute to the class with the name of your trigger as a parameter. This name is the name that will be used in the XML project configuration and can be completely different from the class name if you wish.

[ReflectorType("mySampleTrigger")]
public class SampleTrigger : ITrigger
{
    #region ITrigger Members

    public IntegrationRequest Fire()
    {
        throw new NotImplementedException();
    }

    public void IntegrationCompleted()
    {
        throw new NotImplementedException();
    }

    public DateTime NextBuild
    {
        get { throw new NotImplementedException(); }
    }

    #endregion
}

The Fire() method is the method that is called every time that CruiseControl has scheduled the next integration to occur. If you have this scheduled to occur once every minute, it will call the Fire() method every minute. This is where you want to declare your logic to determine if a build should occur.

The IntegrationCompleted() method is logic that will be called after a build has finished running that has this trigger.

The NextBuild property is used by CruiseControl to determine when the next call to the Fire method of the trigger should occur.

Now that we understand the basics of the interface, lets look at how we can create a simple trigger that fires every 15 seconds.

To start, we need access to the CruiseControl.net DateTimeProvider class. To do this, lets create a constructor to accept the provider and assign it to a field.

DateTimeProvider _dateTimeProvider;

public SampleTrigger()
    : this(new DateTimeProvider())
{
}

public SampleTrigger(DateTimeProvider dateTimeProvider)
{
    _dateTimeProvider = dateTimeProvider;
}

Next, we'll implement the NextBuild property in the interface:

public DateTime NextBuild { get; private set; }

and assign Now + 15 seconds to it in the constructor of the class:

public SampleTrigger(DateTimeProvider dtProvider)
{
    _dateTimeProvider = dtProvider;
    NextBuild = _dateTimeProvider.Now.AddSeconds(15);
}

Now we need to implement the Fire method. We just need to check the current time against the most recent time stored in the NextBuild property. If it is less than the 15 seconds stated above, we just return null, otherwise we return a new IntegrationRequest object to tell CruiseControl that we want a build to occur.

public IntegrationRequest Fire()
{
    if (_dateTimeProvider.Now < NextBuild)
        return null;

    return new IntegrationRequest(BuildCondition.ForceBuild, "Sample Trigger says GO!", null);
}

The BuildCondition parameter in the IntegrationRequest constructor can give CruiseControl a bit more information, such as only causing a build if there are modifications found within source control. As we want a build to occur every 15 seconds no matter what, we're just going to return a ForceBuild.

Finally, we'll implement the integration request to handle any post build processing, in this case, we'll just increment the NextBuild so that the next one will occur 15 seconds after the end of the current build.

public void IntegrationCompleted()
{
    NextBuild = _dateTimeProvider.Now.AddSeconds(15);
}

So finally our trigger should look something like this:

[ReflectorType("uiTestTrigger")]
public class SampleTrigger : ITrigger
{
    DateTimeProvider _dateTimeProvider;

    public SampleTrigger()
        : this(new DateTimeProvider())
    {
    }

    public SampleTrigger(DateTimeProvider dtProvider)
    {
        _dateTimeProvider = dtProvider;
        NextBuild = _dateTimeProvider.Now.AddSeconds(15);
    }

    #region ITrigger Members

    public IntegrationRequest Fire()
    {
        if (_dateTimeProvider.Now < NextBuild)
            return null;

        return new IntegrationRequest(BuildCondition.ForceBuild, "Sample Trigger says GO!", null);
    }

    public void IntegrationCompleted()
    {
        NextBuild = _dateTimeProvider.Now.AddSeconds(15);
    }

    public DateTime NextBuild { get; private set; }

    #endregion
}

Now, having a build occur every 15 seconds isn't really useful (and the interval trigger already does that for us if that's what you need). However, if we wanted to extend this to say, fire a build when a file exists on disk, it's a simple process of just adding that to this class with a few lines of code.

So to start, we alter the Fire method to check if a file exists:

public IntegrationRequest Fire()
{
    if (_dateTimeProvider.Now < NextBuild)
        return null;

    NextBuild = _dateTimeProvider.Now.AddSeconds(15);

    if (!File.Exists(@"C:\Temp\test.txt"))
        return null;

    return new IntegrationRequest(BuildCondition.ForceBuild, "Sample Trigger says GO!", null);
}

The only other thing we have added here is to increment the NextBuild property 15 seconds after we have passed the existing 15 second counter to force this file check to only occur every 15 seconds. And that's it, we're now checking if a file exists every 15 seconds, and if it does we're telling CruiseControl to force a build to start.

Where to from here? You can then start extending this such that the 15 second increment value and the file on disk are properties of the trigger that can be configured in the build configuration file to truly extend this trigger.

Tags:

C# | CruiseControl.net