Short Guides

When Spark meets ASP.NET WebForms

February 21st, 2010  |  Published in Short Guides

This short guide will provide a proof of concept for an alternative to the traditional ASP.NET (WebForms) templated controls & provide an insightful use for the powerful Spark template engine that hasn’t been mentioned elsewhere.

The Spark template engine is a well-documented, open source view engine for .NET, mainly used as an alternative to the “WebForms” engine built into ASP.NET MVC.  It can also be used with Castle MonoRail or even in a standalone application (for example, a console application that creates emails from a template & sends them).  What excites me about this particular template engine is much of its syntax is derived by adding a few extra tags & attributes to HTML, allowing for a cleaner look for your template while not sacrificing the page’s rendering performance (unlike with other templating engines).  Taking the example on the front page of the Spark website, a bit of WebForms code that is written like this…….

<% (IEnumerable<Product> products = (IEnumerable<Product>)ViewData["products"]; %>
<% if (products.Any())
    { %>
        <ul>
            <% foreach (Product p in products)
                { %>
                     <li><%= p.Name %></li>
            <% } %>
        </ul>
    } else { %>
        <p>No products available</p>
<% } %>

…can simply be expressed using Spark as:

<viewdata products="IEnumerable[[Product]]"/>
<ul if="products.Any()">
  <li each="var p in products">${p.Name}</li>
</ul>
<else>
  <p>No products available</p>
</else>

If you’re unfamiliar with Spark & I’ve got you interested in it, it’s well worth looking at the documentation for more syntactic tricks it can perform.

One problem we’ve continuously been running up against is we’ve had to build a server control on an ASP.NET WebForms project that produces custom HTML depending on values from our model.  However, because we’re building these custom server controls for a Content Management System, we don’t have access to the page object, any form of code behind or anything from the model when we need them – the only way we can define them is on an ASCX template that lacks a code behind.  This rules out any conditional logic where we define the template, & results in the dreaded HTML strings written in the control’s .cs file if we wanted to do more than using simple WebForms controls.

What I thought would be nice is to have a custom control with a spark template as its children, with the code for the control only dealing with fetching the domain model & assembling a view model.  Something like:

<sc:MySparkControl runat="server">
    <ul if="products.Any()">
        <li each="var p in products">${p.Name}</li>
    </ul>
    <else>
        <p>No products available</p>
    </else>
</sc:MySparkControl>

The great thing is this idea is possible to implement! What you need to do is create a new control that is derived from System.Web.UI.WebControls.Literal.  What you then do is very similar to what you do when running spark from a standalone application.  The main difference is the template is the Text property of the control:

public class Product
{
    public Product(int id, string name)
    {
        ID = id;
        Name = name;
    }

    public int ID
    {
        get;
        protected set;
    }

    public string Name
    {
        get;
        set;
    }
}

public abstract class MyViewModel : AbstractSparkView
{
    public IEnumerable<Product> products { get; set; }
}

public class MySparkControl : Literal
{
    protected override void OnPreRender(EventArgs e)
    {
        /* Set up spark with its settings, with an in-memory view
           folder since we don't wish to use any external files */

        SparkSettings settings = new SparkSettings().SetPageBaseType(typeof(MyViewModel));

        InMemoryViewFolder templates = new InMemoryViewFolder();
        SparkViewEngine engine = new SparkViewEngine(settings)
        {
            ViewFolder = templates
        };

        /* Add template - note we still need to specify a name even
           though everything is in memory */

        templates.Add("template.spark", this.Text);

        // Render template
        SparkViewDescriptor descriptor = new SparkViewDescriptor().AddTemplate("template.spark");

        StringWriter output = new StringWriter();

        var view = (MyViewModel)engine.CreateInstance(descriptor);

        IEnumerable<Product> myProducts;
        // Fetch the products at this point & put them in myProducts

        view.products = myProducts;
        view.RenderView(output);

        this.Text = output.ToString();

        base.OnPreRender(e);
    }
}

Here I’ve also thrown in a view model & a basic product class for the sake of demonstration.

Provided you fill in some code to initialise the myProducts IEnumerable & do all of the web.config setting up for this control, it will give you a list of the names of the products given. No HTML written in C# strings, & you have full flow of control from within your template with the power & ease of the Spark syntax.  The one drag I’ve discovered is you cannot use the traditional <%= %> notation from within the control template, but I personally feel this is a minor drawback as the alternatives offered by Spark (!{ } for without HTML encoding or ${ } with it) are much neater.

For my purposes this implementation will do the job fine. But there are additions that I invite any reader to implement.  First of all, what if we can bind this control to a WebForms data source, such as a LINQ, SQL, or Object Data Source control?  This may mean we can even have a rival to the GridView, Repeater, etc. on our hands. Another nice idea may be to have the ability to specify the location of an on-disk template as a parameter to the control.  This idea of a template on WebForms tag isn’t just limited to Spark – there are other view engines you may well be able to carry this to, such as NHaml, NDjango, NVelocity & Brail.

Happy templating!

Log4SSIS

February 14th, 2010  |  Published in Short Guides

This short guide, my first of many I hope, will show you how to route log messages from SQL Server Integration Services, or SSIS, to a log provider like log4net or Commons Logging when running a package from a console application.

For one of my projects, we were (over)using SSIS for a number of automated data integration tasks.  It has been documented elsewhere that SSIS does have a number of shortcomings.  I’ve quite often had the nightmare of having to write out task scripts in Visual Basic* when SSIS didn’t have the right task for me.  Plus we were seriously being let down by its poor logging, especially when attempting to trace problems through SQL Server Management Studio.  We had also moved our version control rightfully from Visual SourceSafe to Subversion, but an SSIS package won’t work well with SVN as it’s literally 1 long line!

So we’ve decided that we’ll eventually re-write all of our tasks as custom C# programs (as an aside, we’re using Quartz.NET to perform internal scheduling & provide us with some simple concurrency).  Some of these tasks have been trivial to implement, but others need more time & therefore budget to move over.  However, we could benefit from improved logging from day 1 yet still use some SSIS tasks for now.

The idea here is to run an SSIS task from a C# console application & have all of the log messages from SSIS sent to a log provider like log4net or Commons Logging.  The idea was that we can log useful messages to an XML file, do away with useless debug messages like verifying a task or data flow, & have the messages popping up on a console (in colour for us!) when we’re running the program manually.

In order to do this, you need to create a project that references Microsoft.SqlServer.ManagedDTS.  After doing that you’ll need to create a class that’s derived from Microsoft.SqlServer.Dts.Runtime.LogProviderBase, as shown below:

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable"),
 DtsLogProvider(DisplayName = "SSISLogProvider", Description = "Writes log entries for events to an alternative log provider", LogProviderType = "Custom")]
class SSISLogProvider : LogProviderBase
{
    protected ILog _Log = LogManager.GetLogger("SSIS");

    public override void OpenLog()
    {
        _Log.Debug("Opening logger");
    }

    public override void Log(string logEntryName, string computerName, string operatorName, string sourceName, string sourceID, string executionID, string messageText, DateTime startTime, DateTime endTime, int dataCode, byte[] dataBytes)
    {

        if (!string.IsNullOrEmpty(messageText))
        {
            string logMessage = string.Format("{0}: {1} ({2})", sourceName, messageText, logEntryName);
            switch (logEntryName)
            {
                case "OnPreExecute":
                case "OnPostExecute":
                case "OnPreValidate":
                case "OnPostValidate":
                    _Log.Debug(logMessage);
                    break;
                case "OnWarning":
                    _Log.Warn(logMessage);
                    break;
                case "OnError":
                    _Log.Error(logMessage);
                    break;
                default:
                    _Log.Info(logMessage);
                    break;
            }
        }
    }

    public override void CloseLog()
    {
        _Log.Debug("Closing logger");
    }

    public override DTSExecResult Validate(IDTSInfoEvents events)
    {
        _Log.Debug("Validating");
        return DTSExecResult.Success;
    }
}

This does look like a bit of a convoluted class, but it’s quite simple to explain. Error messages are sent to the error level, warnings are sent to the warning level, pre & post execution, validation & logger opening / closing messages are sent to the debug level and everything else is sent to info. That way you don’t have junk messages in your logs unless you really need them.

So how do you use this from your C# program?  Well, here’s a simple console application that uses Log4Net – it’s easy enough to move over to Commons Logging:

class Program
{
    static void Main(string[] args)
    {
        log4net.Config.XmlConfigurator.Configure();
        Application app = new Application();

        Package package = app.LoadPackage("mypackage.dtsx", null);

        package.LoggingOptions.EventFilterKind = DTSEventFilterKind.Inclusion;
        LogProvider log = package.LogProviders.Add(typeof(SSISLogProvider).AssemblyQualifiedName);
        package.LoggingOptions.SelectedLogProviders.Add(log);
        package.LoggingMode = DTSLoggingMode.Enabled;

        package.Execute();
    }
}

And that is pretty much all there is to it!  You can provide other details such as passwords via the instance of Package.  The only other thing to do is set app.config up for your log provider.  If there’s enough demand I’ll add a sample program with a very simple sample package for you to play with.

Happy logging!

*I am aware that in SSIS 2008, you can write tasks in C# – unfortunately I was restricted to the 2005 version.