Content Control Using ASCX–Only UserControls With BatchCompile Turned Off

This is a bit of a painful one; I’ve inherited a “content control” system which is essentially a vast number of ascx files generated outside of the development team, outside of version control, and dumped directly onto the webservers. These did not have to be in the project because the site is configured with batch=”false”.

I had been given the requirement to implement dynamic content functionality within the controls.

These ascx files are referenced directly by a naming convention within a container aspx page to LoadControl(“~/content/somecontent.ascx”) and render within the usual surrounding master page. Although I managed to get this close to pulling them all into a document db and creating a basic CMS instead, unfortunately I found an even more basic method of using existing ascx files and allowing newer ones to have dynamic content.

An example content control might look something like:

<%@ Control  %>
<div>
<ul>
    <li>
        <span>
            <img src="http://memegenerator.net/cache/instances/250x250/8/8904/9118489.jpg" style="height:250px;width:250px;" />
            <a href="http://memegenerator.net/">Business Cat</a>
            <span class="title">&#163;19.99</span>
        </span>
    </li>
    <li>
        <span>
            <img src="http://memegenerator.net/cache/instances/250x250/8/8904/9118489.jpg" style="height:250px;width:250px;" />
            <a href="http://memegenerator.net/">Business Cat</a>
            <span class="title">&#163;19.99</span>
        </span>
    </li>
    <li>
        <span>
            <img src="http://memegenerator.net/cache/instances/250x250/8/8904/9118489.jpg" style="height:250px;width:250px;" />
            <a href="http://memegenerator.net/">Business Cat</a>
            <span class="title">&#163;19.99</span>
        </span>
    </li>
</ul>
</div>

One file, no ascx.cs (these are written outside of the development team, remember). There are a couple of thousand of them, so I couldn’t easily go through and edit them to all. How to now allow dynamic content to be injected with minimal change?

I started off with a basic little class to allow content injection to a user control:

public class Inject : System.Web.UI.UserControl
{
    public DynamicContent Data { get; set; }
}

and the class for the data itself:

public class DynamicContent
{
    public string Greeting { get; set; }
    public string Name { get; set; }
    public DateTime Stamp { get; set; }
}

Then how to allow data to be injected only into the new content files and leave the heaps of existing ones untouched (until I can complete the business case documentation for a CMS and get budget for it, that is)? This method should do it:

private System.Web.UI.Control RenderDataInjectionControl(string pathToControlToLoad, DynamicContent contentToInject)
{
    var control = LoadControl(pathToControlToLoad);
    var injectControl = control as Inject;

    if (injectControl != null)
        injectControl.Data = contentToInject;

    return injectControl ?? control;
}

Essentially, get the control, attempt to cast it to the Inject type, if the cast works inject the data and return the cast version of the control, else just return the uncast control.

Calling this with an old control would just render the old control without issues:

const string contentToLoad = "~/LoadMeAtRunTime_static.ascx";
var contentToInject = new DynamicContent { Greeting = "Hello", Name = "Dave", Stamp = DateTime.Now };

containerDiv.Controls.Add(RenderDataInjectionControl(contentToLoad, contentToInject));

232111_codecontrol_static

Now we can create a new control which can be created dynamically:

<%@ Control CodeBehind="Inject.cs" Inherits="CodeControl_POC.Inject" %>
<div>
<%=Data.Greeting %>, <%=Data.Name %><br />
It's now <%= Data.Stamp.ToString()%>
</div>

<div>
<ul>
    <li>
        <span>
            <img src="http://memegenerator.net/cache/instances/250x250/8/8904/9118489.jpg" style="height:250px;width:250px;" />
            <a href="http://memegenerator.net/">Business Cat</a>
            <span class="title">&#163;19.99</span>
        </span>
    </li>
    <li>
        <span>
            <img src="http://memegenerator.net/cache/instances/250x250/8/8904/9118489.jpg" style="height:250px;width:250px;" />
            <a href="http://memegenerator.net/">Business Cat</a>
            <span class="title">&#163;19.99</span>
        </span>
    </li>
    <li>
        <span>
            <img src="http://memegenerator.net/cache/instances/250x250/8/8904/9118489.jpg" style="height:250px;width:250px;" />
            <a href="http://memegenerator.net/">Business Cat</a>
            <span class="title">&#163;19.99</span>
        </span>
    </li>
</ul>
</div>

The key here is the top line:

<%@ Control CodeBehind="Inject.cs" Inherits="CodeControl_POC.Inject" %>

Since this now defines the type of this control to be the same as our Inject class it gives us the same thing, but with a little injected dynamic content

const string contentToLoad = "~/LoadMeAtRunTime_dynamic.ascx";
var contentToInject = new DynamicContent { Greeting = "Hello", Name = "Dave", Stamp = DateTime.Now };

containerDiv.Controls.Add(RenderDataInjectionControl(contentToLoad, contentToInject));

232111_codecontrol_dynamic

Just a little something to help work with legacy code until you can complete your study of which CMS to implement Smile

Comments welcomed.

A Quirk of Controls in ASP.Net

As part of the legacy codebase I’m working with at the moment I have recently been required to edit a product listing page to do something simple; add an extra link underneath each product.

 

Interestingly enough the product listing page is constructed as a collection of System.Web.UI.Controls, generating an HTML structure directly in C# which is then styled after being rendered completely flat.

 

For example:, each item in the listing could look a bit like this

public class CodeControl : Control 
{ 
    protected override void CreateChildControls() 
    { 
        AddSomeStuff(); 
    }

    private void AddSomeStuff() 
    { 
        var image = new Image 
        { 
            ImageUrl = "http://memegenerator.net/cache/instances/250x250/8/8904/9118489.jpg", 
            Width = 250, 
            Height = 250 
        }; 
        Controls.Add(image);

        var hyperlink = new HyperLink { NavigateUrl = "http://memegenerator.net/", Text = "Business Cat" }; 
        Controls.Add(hyperlink);

        var title = new HtmlGenericControl(); 
        title.Attributes.Add("class", "title"); 
        title.InnerText = "£19.99"; 
        Controls.Add(title); 
    } 
}

 

And then the code to render it would be something like:

private void PopulateContainerDiv() 
{ 
    var ul = new HtmlGenericControl("ul");

    for (var i = 0; i < 10; i++) 
    { 
        // setup html nodes 
        var item = new CodeControl(); 
        var li = new HtmlGenericControl("li");

        // every 3rd li reset ul 
        if (i % 3 == 0) ul = new HtmlGenericControl("ul");

        // add item to li 
        li.Controls.Add(item);

        // add li to ul 
        ul.Controls.Add(li);

        // add ul to div 
        containerDiv.Controls.Add(ul); 
    } 
}

The resulting HTML looks like:

<ul><li><img src="http://memegenerator.net/cache/instances/250x250/8/8904/9118489.jpg" style="height:250px;width:250px;" /><a href="http://memegenerator.net/">Business Cat</a><span class="title">&#163;19.99</span></li>
.. snip..

And the page itself:

232111_codecontrol_blank_unstyled

I’ve never seen this approach before, but it does make sense; define the content, not the presentation. Then to make it look nicer we’ve got some css to arrange the list items and their content, something like:

ul { list-style:none; overflow: hidden; float: none; }
li { padding-bottom: 20px; float: left; }
a, .title { display: block; }

Which results in the page looking a bit more like

232111_codecontrol_blank_styled

 

So that’s enough background on the existing page. I was (incorrectly, with hindsight, but that’s why we make mistakes right? How else would we learn? *ahem*..) attempting to implement a change that wrapped the contents of each li in a tag so that some jQuery could pick up the contents of that li and put them somewhere else on the page when a click was registered within the li.

So I did this:

// setup html nodes
var item = new CodeControl();
var li = new HtmlGenericControl("li");
var form = new HtmlGenericControl("form");

// every 3rd li reset ul
if (i % 3 == 0) ul = new HtmlGenericControl("ul");

// add item to form
form.Controls.Add(item);

// add form to li
li.Controls.Add(form);

// add li to ul
ul.Controls.Add(li);

// add ul to div
containerDiv.Controls.Add(ul);

I added in a <form> tag and put the control in there, then put the form in the li and the li in the ul. However, this resulted in the following HTML being rendered:

232111_codecontrol_elem_form

Eh? Why does the first <li> not have a <form> in there but the rest of them do? After loads of digging around my code and debugging I just tried something a bit random and changed it from a <form> to a <span>:

// setup html nodes
var item = new CodeControl();
var li = new HtmlGenericControl("li");
var wrapper = new HtmlGenericControl("span");

// every 3rd li reset ul
if (i % 3 == 0) ul = new HtmlGenericControl("ul");

// add item to form
wrapper.Controls.Add(item);

// add form to li
li.Controls.Add(wrapper);

// add li to ul
ul.Controls.Add(li);

// add ul to div
containerDiv.Controls.Add(ul);

Resulting in this HTML:

232111_codecontrol_elem_span

Wha? So if I use a <span> all is good and a <form> kills the first one? I don’t get it. I still don’t get it, and I’ve not had time to dig into it. in the end I just altered the jQuery to look for closest(‘span’) instead of closest(‘form’) and everything was peachy.

 

If anyone knows why this might happen, please do comment. It’s bugging me.

My first productionised powershell script

I’ve been tooling around with powershell recently, trying to teach myself some basics, and a recent support request which would have previously been done manually looked like a perfect opportunity for a little ps1 script.

The request was to disable a feature on the website which is configured from a setting in the web.config file on each server. Since web.configs are xml files, I thought I could treat it as such, traversing and editing values as needed.

So here it is; pretty lengthy for what it’s doing since I don’t know the nicer ways of doing some things (e.g., var foo = (bar == baz ? 0 : 1), and var sna = !sna), and as such any comments to help out would be appreciated:

function ValueToText([string] $val){	
	if ($val -eq "1"){return "enabled"}
	else {return "disabled"}
}

[System.Xml.XmlDocument] $xd = new-object System.Xml.XmlDocument
# pipe-delimited servers to work against
$servers = "192.168.0.1|192.168.0.2|192.168.0.3" 

foreach ($server in $servers.Split("|")) {
	write-host "Now configuring " $server
	
	$file = "\\" + $server + "\d$\Web\web.config"	
	$xd.load($file)	

	# save a backup, just in case I snafu the site
	$xd.save($file + ".bak") 

	# keys to edit
	$nodelist = $xd.selectnodes("/configuration/appSettings/add[contains(@key,'Chat')]") 

	foreach ($node in $nodelist) {
	  $key = $node.getAttribute("key")
	  $val = $node.getAttribute("value")
	  $setting = ValueToText($val)
	  
	  $prompt = $key + " is currently " + $setting + ": toggle this? Y/N"
	  $toggle = read-host $prompt
	  
	  if ($toggle -eq "Y" -or $toggle -eq "y"){
		if ($val -eq "1") {$newbool = "0"}
		else {$newbool = "1"}
		
		$node.setAttribute("value", $newbool)
		
		$newsetting = ValueToText($newbool)
		$prompt = $key + " is now " + $newsetting
		write-host $prompt
	  }
	}
	$xd.save($file)
}
write-host * done *

It’s probably not much more than a “hello world”, but it certainly helped me out recently 🙂

Quick and Dirty C# Recursive Find and Replace

Say you had a vast Visual Studio solution of something ridunculous like 120+ projects and wanted to test out a few proofs of concept on improving build times.

Now say that one of the proofs of concept was to use a shared bin folder for all projects in a single solution. Editing 120+ proj files is going to make you a little crazy.

How about a little recursive find-and-replace app using regular expressions (my saviour in many menial text manipulation tasks) to do it all for you? That’d be nice, wouldn’t it? That’s what I thought too. So I just did a quick and dirty console app to do just that.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Collections.ObjectModel;

namespace RecursiveFindAndReplace
{
    class Program
    {
        static void Main(string[] args)
        {
            // where to start your directory walk
            var directoryToTraverse = @"C:\VisualStudio2010\Projects\TestSolutionWithLoadsOfProjectsInIt\";

            // what files to open
            var fileTypeToOpen = "*.csproj";

            // what to look for
            var patternToMatch = @"<OutputPath>bin\\[a-zA-Z]*\\</OutputPath>;";
            var regExp = new Regex(patternToMatch);
            // the new content
            var patternToReplace = @"<OutputPath>;C:\bin\$(Configuration)\</OutputPath>";

            // get all the files we want and loop through them
            foreach (var file in GetFiles(directoryToTraverse, fileTypeToOpen))
            {
                // open, replace, overwrite
                var contents = File.ReadAllText(file);
                var newContent = regExp.Replace(contents, patternToReplace);
                File.WriteAllText(file, newContent);
            }
        }

        // recursive method to return the files we want in all sub dirs of the initial root
        static List<string> GetFiles(string directoryPath, string extension)
        {
            var fileList = new List<string>();
            foreach (var subDir in Directory.GetDirectories(directoryPath))
            {
                fileList.AddRange(GetFiles(subDir, extension));
            }

            fileList.AddRange(Directory.GetFiles(directoryPath, extension));

            return fileList;
        }

    }
}

No doubt this could be made prettier with a little lambda, but like I said – quick and dirty.

—————–

Edit: I’ve just realised that Directory.GetFiles is inherently recursive. Duh. So the foreach instead becomes:

// get all the files we want and loop through them
foreach (var file in Directory.GetFiles(directoryToTraverse
            ,fileTypeToOpen
            ,SearchOption.AllDirectories))
{
    // open, replace, overwrite
    var contents = File.ReadAllText(file);
    var newContent = regExp.Replace(contents, patternToReplace);
    File.WriteAllText(file, newContent);
}

So that’s even quicker and slightly less dirty. Ah well.