Menu

Razor Truncate Function for Previewing Content

February 5, 2015 by Christopher Sherman

In my tutorial on using Umbraco Partial Views with Razor, I provided sample code that made use of a Truncate function that provides a preview of text content. If you try to implement the sample code, you’ll notice the Truncate function is not available to you because it originates from a custom Razor helper. My Truncate helper fixes a bug in the UmbracoHelper.cs source that was inadvertently stripping out anchor tags. This fix was merged into the umbraco:dev-v7 branch, so it should be available in a subsequent Umbraco release.

In case you want to make use of Truncate in advance of it being available in the Umbraco core, or just have it available to non-Umbraco projects, follow these steps:

  1. Add the App_Code folder to your project.
  2. Right-click the App_Code folder and add a new .cshtml page named RazorHelpers.
  3. Inside the file, paste the code below.
@helper Truncate(string html, int length, bool addElipsis, bool treatTagsAsContent)
{
using (var outputms = new MemoryStream())
{
using (var outputtw = new StreamWriter(outputms))
{
using (var ms = new MemoryStream())
{
using (var tw = new StreamWriter(ms))
{
tw.Write(html);
tw.Flush();
ms.Position = 0;
var tagStack = new Stack();

                    using (TextReader tr = new StreamReader(ms))
                    {
                        bool isInsideElement = false,
                            lengthReached = false,
                            insideTagSpaceEncountered = false,
                            isTagClose = false;

                        int ic = 0,
                            currentLength = 0,
                            currentTextLength = 0;

                        string currentTag = string.Empty,
                            tagContents = string.Empty;

                        while ((ic = tr.Read()) != -1)
                        {
                            bool write = true;

                            switch ((char)ic)
                            {
                                case '<':
                                    if (!lengthReached)
                                    {
                                        isInsideElement = true;
                                    }

                                    insideTagSpaceEncountered = false;
                                    currentTag = string.Empty;
                                    tagContents = string.Empty;
                                    isTagClose = false;
                                    if (tr.Peek() == (int)'/')
                                    {
                                        isTagClose = true;
                                    }
                                    break;

                                case '>':
                                    isInsideElement = false;

                                    if (isTagClose && tagStack.Count > 0)
                                    {
                                        string thisTag = tagStack.Pop();
                                        outputtw.Write("");
                                    }
                                    if (!isTagClose && currentTag.Length > 0)
                                    {
                                        if (!lengthReached)
                                        {
                                            tagStack.Push(currentTag);
                                            outputtw.Write("<" + currentTag);
                                            if (!string.IsNullOrEmpty(tagContents))
                                            {
                                                if (tagContents.EndsWith("/"))
                                                {
                                                    // No end tag e.g.

.
tagStack.Pop();
}

                                                outputtw.Write(tagContents);
                                                write = true;
                                                insideTagSpaceEncountered = false;
                                            }
                                            outputtw.Write(">");
                                        }
                                    }
                                    // Continue to next iteration of the text reader.
                                    continue;

                                default:
                                    if (isInsideElement)
                                    {
                                        if (ic == (int)' ')
                                        {
                                            if (!insideTagSpaceEncountered)
                                            {
                                                insideTagSpaceEncountered = true;
                                            }
                                        }

                                        if (!insideTagSpaceEncountered)
                                        {
                                            currentTag += (char)ic;
                                        }
                                    }
                                    break;
                            }

                            if (isInsideElement || insideTagSpaceEncountered)
                            {
                                write = false;
                                if (insideTagSpaceEncountered)
                                {
                                    tagContents += (char)ic;
                                }
                            }

                            if (!isInsideElement || treatTagsAsContent)
                            {
                                currentTextLength++;
                            }

                            if (currentTextLength <= length || (lengthReached && isInsideElement))
                            {
                                if (write)
                                {
                                    var charToWrite = (char)ic;
                                    outputtw.Write(charToWrite);
                                    currentLength++;
                                }
                            }

                            if (!lengthReached && currentTextLength >= length)
                            {
                                // Reached truncate limit.
                                if (addElipsis)
                                {
                                    outputtw.Write("…");
                                }
                                lengthReached = true;
                            }

                        }

                    }
                }
            }
            outputtw.Flush();
            outputms.Position = 0;
            using (TextReader outputtr = new StreamReader(outputms))
            {@{
                var outputString = new HtmlString(outputtr.ReadToEnd().Replace("  ", " ").Trim());}
                    @outputString

}
}
}
}

Here’s a sample using the function in a Razor view:

 <div>
 @RazorHelpers.Truncate(Model.ArticleCopy.ToString(), 200, true, false)
 <a href="@Model.URL">Read more &nbsp;&#8594;</a>
 </div>

Umbraco SendGrid