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:
App_Code
folder to your project.App_Code
folder and add a new .cshtml
page named RazorHelpers
.@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 →</a>
</div>