I’ve been playing around with ASP.NET 3.5 this weekend, and am building my first real website with it. Much to my horror, as a long-time Java developer, I’m finding it to be incredibly delightful to work with.
C# feels like a slightly-improved version of Java, the tooling is pretty good (even though I’m still not thrilled with the code editor in VS2010), the Razor syntax used in views is quite clean, and the whole thing is pretty easy to work with. It isn’t perfect, and I haven’t tried to go to production with my website yet, but all-in-all, it is a pretty good developer experience.
Working on my website this weekend, I ran into a problem that I – like probably every other web developer – have run into many times before: I needed to take some data that had line breaks in it and display it on a page, and somehow figure out how to convert those line endings into <br> tags. A lot of languages and frameworks have something built-in to handle this – PHP’s nl2br(), for example – but I couldn’t find anything in the documentation far ASP.NET MVC. This is a fairly trivial problem, but it is also a pretty common one and doing it wrong can lead to security problems, so I figured it was worth spending a few minutes figuring out the best way to do it and documenting it.
Doing some research, it seems like folks were recommending 2 main approaches to solving this:
1. Convert the line-endings to <br>’s before saving the content to the database. I didn’t care for this because saving HTML into the database complicates Cross-Site Scripting (XSS) defense. It also makes it problematic if you later want to display that same content outside of HTML (for example, in a mobile phone app).
2. Call Replace(“\r\n”, “<br>”) on the string when displaying it. I don’t care for this either – it seems repetitive (I’m lazy – I hate having to do something once, much less 500 times), and even worse, since you have to use @Html.Raw() to display it, it can open you up to XSS attacks.
Thankfully, MVC’s Html Helpers and C#’s extension methods provide a way to come up with a relatively simple and robust solution. My requirements were:
1. It had to be easy to use.
2. It had to protect against XSS attacks.
3. It should probably try to take into account the fact that there are (naturally) a number of different ways to end a line: \r\n on Windows, \r on really old Macs, and \n just about everywhere else.
This is what it ended up looking like:
static Regex LineEnding = new Regex(@"(\r\n|\r|\n)+"); public static MvcHtmlString Nl2br(this HtmlHelper html, string text, bool isXhtml = true) { var encodedText = HttpUtility.HtmlEncode(text); var replacement = isXhtml ? "<br />" : "<br>"; return MvcHtmlString.Create(LineEnding.Replace(encodedText, replacement)); } |
And here is how you’d use it:
@Html.Nl2br(@Model.Description) |
And here are a few tests for good measure:
[TestMethod] public void TestCRLF() { var input = "Hello\r\nWorld!"; string result = MyHelpers.Nl2br(null, input).ToHtmlString(); var expected = "Hello<br />World!"; Assert.AreEqual(expected, result); } [TestMethod] public void TestLF() { var input = "Hello\nWorld!"; string result = MyHelpers.Nl2br(null, input).ToHtmlString(); var expected = "Hello<br />World!"; Assert.AreEqual(expected, result); } [TestMethod] public void TestNonXhtmlOnOldMac() { var input = "Hello\rWorld!"; string result = MyHelpers.Nl2br(null, input, false).ToHtmlString(); var expected = "Hello<br>World!"; Assert.AreEqual(expected, result); } [TestMethod] public void TestXSS() { var input = "Hello\nWorld!\r\nSpecial Message: <script>alert('pwned!');</script>"; string result = MyHelpers.Nl2br(null, input).ToHtmlString(); var expected = "Hello<br />World!<br />Special Message: <script>alert('pwned!');</script>"; Assert.AreEqual(expected, result); } |
A few notes:
– You’ll note that we HtmlEncode the raw text first, then replace the line breaks with <br> tags. This is so that any potentially malicious user-supplied content gets escaped, but our HTML doesn’t.
– If the page you are working with is XHTML, the <br> tags should be self-closing. I’ve made this the default in this case, but you could pass in false to denote that they shouldn’t be self-closing.
– It is worth noting that you can’t pass dynamic parameters (such as ViewBag.Message) into an extension method, so you’ll have to explicitly cast them to a string.
– I’m quite new to C# and the .NET platform, so any comments or criticisms about this approach are certainly welcome.
Anyway, extension methods make it pretty easy to create your own HTML helpers – I’ve already ended up making handful of them to save time.
I’d add this one as the first line of Nl2Br to make it more robust:
if (string.IsNullOrEmpty(text)) return MvcHtmlString.Empty;