Wednesday, August 4, 2010

Sending HTML content to an asp.net MVC View

I have co-authored an article in the upcoming print issue of Visual Studio magazine(VSM). This covers asp.net MVC 2.0 and data annotations for the vb camp.

I happened to be reading one of Joe's blog where shows how to read HTML snippet from an external file and send it to a View. I got thinking on different ways to achieve this result. One good way way to sharpen your coding skills is to try and figure out different ways to solve the same problem. I posted my response as a comment, but I think that this deserves more discussion, so here we go...

Joe's suggestion is to read the text/html into a string and then dump it into the ViewData dictionary, which is a standard way of passing data from a Controller to a View, which is probably the easiest way to take html snippets and embed them into your page. A word of caution: when you send HTML to a browser to display, you need to have checks to ensure that no malicious html/javascripts get through(especially if it is user-generated content). CSRF, XSS and Clickjacking are among the top ten security threats in 2010. In this case the usual defense of html-encoding will will not work as it defeats the very purpose of wanting to render the HTML.

I will discuss several other methods. The most direct way is to call the ReadAllText method from the View, and the content of the snippet gets dumped on the fly:

<div id="snippet_container" style="border:2px solid black">
<%= System.IO.File.ReadAllText(@"C:\temp\snippet.htm") %>
</div>

A second way to insert snippets is to create an Action whose job it is to return a string, and then you can just call RenderAction helper method.

<%Html.RenderAction("ShowSnippet",
new { filename = @"C:\temp\snippet.htm" }); %>

You can dynamically change the "filename" parameter to display any file, or insert any parameter into the ShowSnippet Action method, for that matter. The action will look like:

[ChildActionOnly]
public string ShowSnippet(string filename)
{
return System.IO.File.ReadAllText(filename);
}

Put the ChildActionOnly Attribute if you don't want the Action to be called directly. I can see lots of possibilities with this technique. You can pass any number or parameters and do all kinds of processing, such as call a web service, or build HTML dynamically from complex data. This is one way of creating widgets.

A third way is to create a View-Model, where you populate all data required by the View. There are many blogs on the subject, so I won't spend much time discussing it.

A very cool way fourth way for dynamic content is using the Ajax. This topic is too large and not the focus of this blog, so I will show you one easy way and move on. The Ajax.ActionLink helper method shown below will display content inside the specified container. You will need to insert references to the scripts: MicrosoftMvcAjax.js and MicrosoftAjax.js:

<%= Ajax.ActionLink("Click to insert snippet", "ShowSnippet",
new AjaxOptions { UpdateTargetId = "snippet_container"}) %>
<div id="snippet_container" style="border:2px solid black">snippet results go here</div>

The possibilities with Ajax are endless! If you want to just output text/html (not embed it in a View), you can simply return a string, similar to the action method shown above. You don't need a View, the string will render as html. Here are some other ways to do it, non of which require a View:

1. Return a "FileResult" - there are many ways to do this. You can use this to return any type of content, not just html.

public ActionResult myhtml1(){
return Content(System.IO.File.ReadAllText(@"C:\stuff.htm"));
return this.File(@"C:\stuff.htm","text/html"); //"File" is a method of the controller
return new FilePathResult(@"C:\stuff.htm","text/html");
return new FileStreamResult(System.IO.File.OpenRead(@"c:\stuff.htm"), "text/html");
}

2. An even simpler way: this does not have to be an MVC Action, and will work with traditional asp.net applications as it it uses a standard Response object.

public void myhtml2(){
Response.WriteFile(@"C:\stuff.htm");
}

Response.Write() and Response.TransmitFile() are similar to the WriteFile() method. Here is a cool way to steal, ahem, borrow content from some other website and pipe it to a Response:

public void myhtml3(){
var wreq = System.Net.WebRequest.Create("http://jbknet.blogspot.com/");
var wresp = wreq.GetResponse();
var sr = new System.IO.StreamReader(wresp.GetResponseStream());
Response.Write(sr.ReadToEnd());
}

For completeness sake, I will add that if you want to simply forward(redirect) the request:
1. You can use the Response.Redirect or Server.Transfer methods in your action
2. Put the "Refresh" Meta tag in your View or
3. Put Javascript forwarding code in your View (location.href or location.replace)