Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

Showing Syntax Colored Source Code in ASP.NET Content


:P
On this page:

I’ve been working on cleaning up some old samples and building some new ones for a few tools I’m re-working and one thing that I like to do is provide access to the page source easily by providing a button or link to view the relevant code files. Typically this means a ASPX page and code behind, but it could be a whole batch of files like ASPX, Codebehind, a Javascript file and maybe a backend service implementation for Ajax callbacks – heck in some cases even a plain class file.

Syntax highlighting for code of various kinds is a key ingredient in this process and one library I’ve been using for many use is the Manoli C# Format library which provides HTML output for code formatting. I ran into this many years ago after searching for an easy to use and easily customizable solution and have stuck with it. At the time I made a few minor modifications and added one language I use occasionally for code samples (FoxPro) to the supported languages. Adding the new language was breeze and that was a big consideration at the time.

Basic Syntax Highlighting

The Manoli library provides the ability to convert a snippet of code into CSS styled HTML markup that – with the matching styles set – looks fairly nice in most situations. The code to do this is as simple as it should be:

SourceFormat Format = CSharpFormat.Create(SourceLanguages.CSharp);
CodeBlock = Format.FormatCode(CodeSnippet);

A csharp.css file is provided with the library and you just have to make sure that wereever the code rendered is displayed that it has access to this CSS file or at least the styles inside of your own CSS file.

That works but in a more realistic environment you likely have to deal with a piece of code contained as part of another block of text. For example, here on this WebLog when you post a comment you can inject <code lang="C#">… code here… </code> to inject a variety of different types of source code inside of the comment block. The routine that handles this is a little more complex, because it has to deal with parsing the code blocks out of the text block. Here’s what that code looks like:

/// <summary>
/// Formats the content by encoding the string as HTML and expanding URLs
/// </summary>
/// <param name="bodyText"></param>
/// <returns></returns>
public string FormatComment(string bodyText)
{
    // *** Find any CodeBlocks 
    List<string> codeBlocks = new List<string>();
    int blockCount = 0;
    while (true)
    {
        // *** strip out source code blocks
        string codeBlock =  StringUtils.ExtractString(bodyText, "<code", "</code>");
        if (string.IsNullOrEmpty(codeBlock))
            break;

        bodyText = bodyText.Replace("<code" + codeBlock + "</code>", "##CodeMarker" + blockCount.ToString() + "##");

        // *** format code here
        string formatted = codeBlock.Substring(codeBlock.IndexOf(">")+1);


        if (!string.IsNullOrEmpty(formatted))
        {
            string language = StringUtils.ExtractString(codeBlock, "lang=\"", "\"");
            language = language.ToLower();

            SourceFormat Format = CSharpFormat.Create(language);
            if (Format != null)
            {
                formatted = formatted.Trim('\r', '\n');   // /r in <pre> text cause extra lines
                codeBlock = "<div class='commentcode'>" + Format.FormatCode(formatted) + "</div>";
            }
        }

        codeBlocks.Add(codeBlock);
        blockCount++;
    }

    // *** Html Encode and add breaks to the non-code text
    bodyText =  StringUtils.ExpandUrls( StringUtils.DisplayMemoEncoded( bodyText ) );

    // *** No code blocks we're done
    if (codeBlocks.Count < 1)
        return bodyText;

    blockCount = 0;
    foreach (string CodeBlock in codeBlocks)
    {
        bodyText = bodyText.Replace("##CodeMarker" + blockCount.ToString() + "##", CodeBlock);
        blockCount++;
    }

    return bodyText;
}

This is obviously a somewhat specific implementation to my environment which is marked up with <code lang="lang"></code> blocks, but you can probably figure out how to make this work in other similar scenarios.

Displaying Page Code

The above scenario of embedded source code in text is common at least on development sites or blogs, but the point of this article is really the ability to display source code for the current page or more appropriate all the relevant page content. This is obviously a very specific and probably somewhat rare scenario reserved for those folks that create demos or other developer related material that might benefit from looking at source code right on the page that you are visiting.

To this effect I took the Manoli library and added a ViewSourceControl to it so that it’s pretty trivial to simply add a couple of buttons to the page that look something like this:

SourceCodeButtons

You can also look at the Live Example.

With the ViewSourceControl I can simply drop a few controls on the page to display all the relevant pieces of code:

<div class="toolbarcontainer" style="margin-top: 20px;margin-bottom: 10px;">
    <manoli:viewsourcecontrol id="ViewSourceControl" runat="server" text="Show ASPX"
        displaystate="Button" codefile="BooksAdmin.aspx" />
    <manoli:viewsourcecontrol id="ViewSourceControl1" runat="server" text="Show CodeBehind"
        displaystate="Button" codefile="BooksAdmin.aspx.cs" />
    <manoli:viewsourcecontrol id="ViewSourceControl2" runat="server" text="Javascript Code"
        displaystate="Button" codefile="~/Ajax/AmazonBooks/BooksAdmin.js" />
</div> 

The control initially shows nothing and when the button is clicked displays the data. It’s a standard PostbackControl – no ajax here, although that might be a nice feature add. I didn’t do this mainly to not have other dependencies otherwise it’s easy enough (using AjaxMethodCallback inside of the control to handle the callbacks).

The control implementation does a couple of useful things – it provides parsing of files based on extensions to figure what type of language to use and it also provides the default stylesheet as a WebResource so that you don’t have to explicitly add anything to your style sheet or the full csharp.css file. It’s overridable so you can still customize the CSS if you choose, but by default the WebResource is used. The goal of all this is to make this thing as easy as possible to get on the page. and showing code.

Here’s the code:

[assembly: WebResource("Manoli.Utils.CSharpFormat.csharp.css", "text/css")]

namespace Manoli.Utils.CSharpFormat
{   

    /// <summary>
    /// Simple control that lets you show code in a page.
    /// 
    /// </summary>
    [ToolboxData("<{0}:ViewSourceControl />")]
    public class ViewSourceControl : WebControl
    {
        protected Button btnShowCode = null;
        protected string Output = null;

        const string STR_CSHARP_RESOURCE = "Manoli.Utils.CSharpFormat.csharp.css";
        
        [Description("The location of the code file using ~/ url syntax.")]
        [DefaultValue("")]
        public string CodeFile
        {
            get
            {
                return _CodeFile;
            }
            set
            {
                _CodeFile = value;
            }
        }
        private string _CodeFile = "";


        [Description("Determines which mode the control displays either as a button or displaying the code")]
        public DisplayStates DisplayState
        {
            get
            {
                return _DisplayState;
            }
            set
            {
                _DisplayState = value;
            }
        }
        private DisplayStates _DisplayState = DisplayStates.Button;

        [Description("Optional location of the CSS file that formats code. WebResource specifies loading from internal resource.")]
        public string CssLocation
        {
            get { return _CssLocation; }
            set { _CssLocation = value; }
        }
        private string _CssLocation = "WebResource";


        [Description("The button text.")]
        [DefaultValue("Show Code")]
        public string Text
        {
            get
            {
                return _Text;
            }
            set
            {
                _Text = value;
                if (this.btnShowCode != null)
                    this.btnShowCode.Text = value;
            }
        }
        private string _Text = "Show Code";
        

        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);

            this.btnShowCode = new Button();
            this.btnShowCode.Text = this.Text;
            this.btnShowCode.Click += new EventHandler(this.btnShowCode_Click);
            this.Controls.Add(this.btnShowCode);
        }

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
        
            this.btnShowCode.Width = this.Width;

            // Add the stylesheet only once
            if (Context.Items["StyleAdded"] == null)
            {
                HtmlLink link = new HtmlLink();
                link.Attributes.Add("type", "text/css");
                link.Attributes.Add("rel", "stylesheet");

                if (string.IsNullOrEmpty(this.CssLocation) || this.CssLocation == "WebResource")
                {
                    // use WebResource
                    string url = this.Page.ClientScript.GetWebResourceUrl(typeof(ViewSourceControl), STR_CSHARP_RESOURCE);
                  
                    link.Attributes.Add("href", url);
                }
                else
                    link.Attributes.Add("href", this.ResolveUrl(this.CssLocation));

                this.Page.Header.Controls.Add(link);
                Context.Items["StyleAdded"] = "1";
            }
        }


        protected void btnShowCode_Click(object sender, EventArgs e)
        {
            DisplayCode();
        }

        protected void DisplayCode()
        {
            string File = this.Page.Server.MapPath(this.ResolveUrl(this.CodeFile));
            File = File.ToLower();

            // Allow only source and aspx files
            string extension = Path.GetExtension(File).ToLower();

            if ( !",.cs,.vb,.aspx,.asmx,.js,.ashx,".Contains("," + extension + ",") )
            {
                this.Output = "Invalid Filename specified...";
                return;
            }

            if (System.IO.File.Exists(File))
            {
                StreamReader sr = new StreamReader(File);
                string FileOutput = sr.ReadToEnd();
                sr.Close();

                if (File.ToLower().EndsWith(".cs") || File.ToLower().EndsWith(".asmx") || File.ToLower().EndsWith(".ashx"))
                {
                    CSharpFormat Format = new CSharpFormat();
                    this.Output = "<div class='showcode'>" + Format.FormatCode(FileOutput) + "</div>";
                }
                else if (File.ToLower().EndsWith(".js"))
                {
                    JavaScriptFormat Format = new JavaScriptFormat();
                    this.Output = "<div class='showcode'>" + Format.FormatCode(FileOutput) + "</div>";
                }
                else
                {
                    HtmlFormat Format = new HtmlFormat();
                    this.Output = "<div class='showcode'>" + Format.FormatCode(FileOutput) + "</div>";
                }

                this.Page.ClientScript.RegisterStartupScript(typeof(ViewSourceControl), "scroll",
                    "var codeContainer = document.getElementById('" + this.btnShowCode.ClientID + "');codeContainer.focus();setTimeout(function() { window.scrollBy(0,200);},100);", true);
            }
            
            this.btnShowCode.Visible = true;
            
        }

        public override void RenderControl(HtmlTextWriter writer)
        {
            base.RenderControl(writer);

            if (!string.IsNullOrEmpty(this.Output))
                writer.Write(this.Output);
        }
    }

    public enum DisplayStates
    {
        Button,
        Code
    }
}

The code also wraps the HTML generated into a top level <div class=’showcode’> tag to allow styling of the code container. The default styling is the yellow box with the red outline and some padding. The project contains the csharp.css file which is used as a Web Resource – you can customize the style sheet or copy it out to disk and use the CssLocation property to point at the external customized CSS file.

You can take a look at the code and style changes in the accompanying code download. This pretty much the original Manoli project with the ViewSourceControl included, the FoxPro language parser (rough), as well a string based Create() factory method that can load languages like in the comment example earlier.

Hopefully some of you may find these enhancements useful.

Download the updated CSharpComment code

Posted in ASP.NET  

The Voices of Reason


 

DotNetShoutout
February 25, 2009

# Showing Syntax Colored Source Code in ASP.NET Content - Rick Strahl's Web Log

Thank you for submitting this cool story - Trackback from DotNetShoutout

DotNetKicks.com
February 25, 2009

# Showing Syntax Colored Source Code in ASP.NET Content

You've been kicked (a good thing) - Trackback from DotNetKicks.com

Tommy
April 09, 2009

# re: Showing Syntax Colored Source Code in ASP.NET Content

Good job! Thanks for this sharing. I had tried on your codes as posted. Everything run perfectly, except the output of the source codes do not show any colors. I guess probably the attached codes do not included the CSS for class="toolbarcontainer".

RyanOC
July 29, 2009

# re: Showing Syntax Colored Source Code in ASP.NET Content

How did you get the left margin to be automatically removed on codepaste.net? Thanks Rick

Rick Strahl
July 29, 2009

# re: Showing Syntax Colored Source Code in ASP.NET Content

@RyanOC - I reload the code from the server with or without line numbers. It's an AJAX callback...

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024