Archive for January, 2008

Storing LINQ Objects in SQL-Based Session State

// January 30th, 2008 // 3 Comments » // LINQ

Scott Hanselman recently posted about various options you have for session storage while using ASP.Net. In the comments of his post I brough up an issue I recently encountered at work (where we use SQL Server session state):

LINQ-To-Sql generated objects are not marked [Serializable] and cannot be stored in out-of-process session storage.

To get around this I have whipped up the following helper class which will serialize LINQ-To-Sql objects if you set the DataContext's Serializable property to "Unidirectional".

Note: The following class does not current work for storing List<X> where X is a LINQ-To-Sql object. I'll be working to resolve that sometime later this week.

   1: using System;
   2: using System.IO;
   3: using System.Runtime.Serialization;
   4: using System.Text;
   5: using System.Xml;
   6: using System.Web;
   7: 
   8: namespace SquaredRoot.Helper
   9: {
  10:   public class SessionHelper
  11:   {
  12: 
  13:     private SessionHelper(){}
  14: 
  15:         public static T Get<T>( string key ) where T : class
  16:         {
  17: 
  18:             object o = HttpContext.Current.Session[key];
  19:             if( o == null )
  20:                 return default( T );
  21: 
  22:             if( o.GetType() == typeof( string ) && typeof( T ) != typeof( string ) )
  23:             {
  24:                 string s = o as string;
  25:                 Stream stream = new MemoryStream( Encoding.Unicode.GetBytes(s) );
  26:                 XmlDictionaryReader xml = XmlDictionaryReader.CreateTextReader( stream, new XmlDictionaryReaderQuotas() );
  27:                 DataContractSerializer dcs = new DataContractSerializer( typeof( T ) );
  28:                 T t = (T)dcs.ReadObject( xml, true );
  29:                 xml.Close();
  30:                 return t;
  31:             }
  32:             else
  33:                 return o as T;
  34: 
  35:         }
  36: 
  37:         private static bool HasClassAttribute( object o, Type attribute, bool inherit )
  38:         {
  39:             return ( o.GetType().GetCustomAttributes( attribute, inherit ).Length > 0 );
  40:         }
  41: 
  42:         public static void Set( string key, object item )
  43:         {
  44: 
  45:             object value = item;
  46:             if( HasClassAttribute( item, typeof(DataContractAttribute), false )
  47:         && !HasClassAttribute( item, typeof(SerializableAttribute), false ) )
  48:             {
  49:                 DataContractSerializer dcs = new DataContractSerializer(item.GetType());
  50:                 StringBuilder sb = new StringBuilder();
  51:                 XmlWriter writer = XmlWriter.Create(sb);
  52:                 dcs.WriteObject( writer, item );
  53:                 writer.Close();
  54:                 value = sb.ToString();
  55:             }
  56: 
  57:             if( HttpContext.Current.Session[key] == null )
  58:         HttpContext.Current.Session.Add( key, value );
  59:             else
  60:         HttpContext.Current.Session[key] = value;
  61: 
  62:         }
  63: 
  64:     }
  65: }
Kick It on DotNetKicks.comShout It on DotNetShoutOuts.com

MVC: Strongly Typed ViewData Without A Code-Behind

// January 7th, 2008 // 8 Comments » // MVC

If you’ve tried to use strongly-typed ViewData with a ViewPage or ViewContentPage without using a code-behind file you may have run into the curious scenario of how to specify a generic in the <%@ Page %> element’s Inherits attribute.

The Problem

Let’s say you wanted to specify that the ViewData for this view would be an integer. Normally you would specify this in the code-behind like so:

   1: using System;
   2: using System.Web;
   3: using System.Web.Mvc;
   4:
   5: namespace MVC_Example.Views.Home
   6: {
   7:     public partial class Index : ViewPage<int>
   8:     {
   9:     }
  10: }

Since we don’t have a code-behind file though, we have to change the .aspx file’s <%@ Page %> element. Trying to set it to the following will not work:

   1: <%@ Page Language="C#" Inherits="MVC_Example.Views.Home.Index<int>" %>

The Basic Answer

The value of the Inherits attribute appears to be delivered straight to the CLR without any language specific parsing, which means we have to specify it the way the CLR wants to interpret it. I browsed around and found (here and here) some information that points out the proper way to do this:

   1: <%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage`1[ [System.Int32,mscorlib] ]" %>

WTF?

Eww, huh? The syntax is gnarly, but it gets the job done. Let’s break the syntax down to make sure you understand what is going on:

ParentType`1[ [ChildType,ChildTypeAssembly] ]

  • “ParentType” is fairly obvious. This is the generic object that you want to create a reference to.
  • “`1[ [" is a bit more obscure. The "`1" specifies the number of generics in this argument, which we'll come back to in a second. The double square brackets are required. A single square bracket will NOT work.
  • "ChildType" is the type of object you wish to have the generic consume (what is the proper terminology here? anyone know?).
  • "ChildTypeAssembly" is the name of the assembly (NOT the namespace) that contains the ChildType. All of your common value types will be located in "mscorlib".

Multiple Generic Types

Let's say ViewPage took two arguments as part of its generic declaration area (ie: ViewPage<X,Y>). It doesn't, by the way, but we'll pretend it does for the sake of argument; you may find this information useful someday.

To have two type parameters, you'll have to change the "`1" to a "`2" and add an extra "[ChildType, ChildTypeAssembly]“ reference, separated by a space. More parameters would continue to follow the same logic.

If you wanted to achieve the equivalent of ViewPage<int,string> it would look like this:

   1: System.Web.Mvc.ViewPage`2[ [System.Int32,mscorlib], [System.String,mscorlib] ]

A Generic of Generics

A more likely scenario is that you may want to have your ViewData be a generic List<T>. In this case you’ll just nest the declarations. If you wanted to pass ViewData of the type List<string> to the ViewPage it would look like:

   1: System.Web.Mvc.ViewPage`1[[System.Collections.Generic.List`1[[System.String,mscorlib]], mscorlib]]
Kick It on DotNetKicks.comShout It on DotNetShoutOuts.com

BlogEngine Extension: Copy Code to Clipboard

// January 7th, 2008 // 23 Comments » // Blogging, Tools

Since I first started this blog I've been using Colin Coller's CopySourceAsHtml plugin for Visual Studio (which Guy Burstein updated for Visual Studio 2008's RTM). It was a nice little tool but as I started posting more and more code snippets to the blog I found myself wanting some additional features, specifically:

  • Easy to use. While it was perfectly simple to copy the source as html, it was a bit more cumbersome to paste it properly into Windows Live Writer. I wound up in HTML mode in WLW everytime I needed to paste some code.
  • CSS support. The Html generated by the VS plugin inlines all of the styles and makes it difficult to change the presentation of your code without tweaking the settings of the plugin at posting time.
  • Scrollable. This is mostly handled by the above feature, but I wanted to be able to limit the amount of screen real estate taken up by the code while still allowing my readers to view more of the code without having to download any files.
  • Copyable. Sometimes you see a piece of code that fits your situation perfectly, but when you copy it you wind up with a bunch of extra Html guck and line numbers at the front of every line you have to manually remove. I want to allow my readers to copy the code without any of that mess.

I found syntaxhighlighter on Google Code pretty quickly and was impressed. I'd seen it around on other people's blogs and loved the "copy to clipboard" feature, but I really wanted a server-side (or pre-posting) highlighter that would take some of the strain off the browser and wanted something with richer WLW integration.

After looking at a few more options I finally found Leo Vildosola's Code Snippet plugin, which matched up with all of my requirements except for the lack of a built-in "copy to clipboard" feature. With some help from clipboard copying javascript posted at WebChicanery I set out to build an extension for BlogEngine.net that would automatically tack on the copy feature to any code I posted via the Code Snippet plugin.

A few hours later, I present the CopyCodeToClipboard extension for BlogEngine.net! Below you will find an example of the extension displaying it's own code (mmm… dogfood) and the css I've used to style the code on my site. I've tried to make the extension css friendly so you can change it's presentation a great deal without modifying the code.

CopyCodeToClipboard.zipDownload (3.82 kb)
Contains all of the below files in one convenient package.

CopyCodeToClipboard.csDownload (7.36 kb)
This is the actual extension and should be placed into your root/App_Code/Extensions/ folder.

   1: using System;
   2: using System.Text.RegularExpressions;
   3: using BlogEngine.Core;
   4: using BlogEngine.Core.Web.Controls;
   5: 
   6: [Extension("Adds a link to syntax highlighted code to copy it to the clipboard.", "1.0", @"<a href=""http://www.squaredroot.com"">Troy Goode</a>")]
   7: public class CopyCodeToClipboard
   8: {
   9: 
  10:     private ExtensionSettings settings;
  11: 
  12:     public CopyCodeToClipboard()
  13:     {
  14: 
  15:         Post.Serving += new EventHandler<ServingEventArgs>(Post_PostServing);
  16:         Post.Saving += new EventHandler<SavedEventArgs>(Post_Saving);
  17: 
  18:         ExtensionSettings initialSettings = new ExtensionSettings(GetType().Name);
  19:         initialSettings.Help = "This extension is written to work with the <a href="http://lvildosola.blogspot.com/2007/02/code-snippet-plugin-for-windows-live.html">Code Snippet Plugin for Windows Live Writer</a> and will not work with other syntax highlighting tools.";
  20:         initialSettings.AddParameter( "copyText", "Text for 'Copy to Clipboard' button?", 255, true );
  21:             initialSettings.AddValue( "copyText", "Copy To Clipboard" );
  22:         initialSettings.AddParameter( "popupText", "Text for 'View in Popup Window' button?", 255, true );
  23:             initialSettings.AddValue( "popupText", "View Plain" );
  24:         initialSettings.AddParameter("aboveBelow", "Display above or below code?", 5, true);
  25:             initialSettings.AddValue("aboveBelow", "Below");
  26:         initialSettings.AddParameter( "style", "Any additional styling?", int.MaxValue, false );
  27:             initialSettings.AddValue( "style", "" );
  28:         initialSettings.AddParameter( "flashFile", "Path to '_clipboard.swf' Flash file?", 255, true );
  29:             initialSettings.AddValue( "flashFile", "~/_clipboard.swf" );
  30:         initialSettings.IsScalar = true;
  31:         ExtensionManager.ImportSettings(initialSettings);
  32: 
  33:         settings = ExtensionManager.GetSettings(GetType().Name);
  34: 
  35:     }
  36: 
  37:     private void Post_Saving(object sender, SavedEventArgs e)
  38:     {
  39:         //### executing here will only run the code once per post, but will modify the post itself
  40:         //InsertCopyCodeLink(e);
  41:     }
  42: 
  43:     private void Post_PostServing(object sender, ServingEventArgs e)
  44:     {
  45:         //### executing here will execute the code on every iteration, but will not modify your post
  46:         InsertCopyCodeLink(e);
  47:     }
  48: 
  49:     private void InsertCopyCodeLink( ServingEventArgs e)
  50:     {
  51:         if( !string.IsNullOrEmpty(e.Body) )
  52:         {
  53: 
  54:             //### find code-div
  55:             string postID = Guid.NewGuid().ToString().Replace( "{", "" ).Replace( "}", "" ).Replace( "-", "" );
  56:             string toFind = "<div class="csharpcode-wrapper">";
  57:             int index = e.Body.IndexOf(toFind);
  58:             while( index != -1 )
  59:             {
  60: 
  61:                 //### grab code out of code-div
  62:                 int end = e.Body.IndexOf( "</div>", index ); //### the first </div> should be the end of "csharpcode"
  63:                 end = e.Body.IndexOf( "</div>", end ); //### the next </div> should be the end of "csharpcode-wrapper"
  64:                 string code = e.Body.Substring(index, end - index);
  65: 
  66:                 //### parse code out of code-div
  67:                 int codeStart = code.IndexOf("<pre ");
  68:                 int codeEnd = code.LastIndexOf("</pre>") + 6;
  69:                 code = code.Substring(codeStart, codeEnd - codeStart);
  70:                 code = Regex.Replace( code, @"<(.|n)*?>", string.Empty ); //### strip html
  71:                 code = code.Replace( "&#160;", "" ); //### remove unnecessary &#160;s from the blank lines
  72:                 code = code.Replace( "&nbsp;", " " ); //### convert &nbsp;s to spaces
  73:                 code = Regex.Replace( code, @"^(s*)(d+): ", "" ); //### remove line numbers on first line
  74:                 code = Regex.Replace( code, @"(n)(s*)(d+): ", "n" );  //### remove line numbers on subsequent lines
  75: 
  76:                 //### create copy link
  77:                 string insertScript = @"
  78:                     <script type=""text/javascript"">
  79:                         var copyToClipboard@INDEX = CopyToClipboard_Strip('@CODE');
  80:                     </script>";
  81:                 string insertDiv = @"<div class=""CopyToClipboard"" style=""@STYLE""><div><a href=""javascript:void(0);"" onclick=""CopyToClipboard_ViewPlain(copyToClipboard@INDEX);"">@POPUPTEXT</a> | <a href=""javascript:void(0);"" onclick=""CopyToClipboard_Copy(copyToClipboard@INDEX);"">@COPYTEXT</a></div></div>";
  82: 
  83:                 //### set values for copy link and insert above/below code
  84:                 string insert = insertDiv + OutputCommonMethods() + insertScript;
  85:                 insert = insert.Replace( "@STYLE", settings.GetSingleValue("style") );
  86:                 insert = insert.Replace( "@POPUPTEXT", settings.GetSingleValue("popupText") );
  87:                 insert = insert.Replace( "@COPYTEXT", settings.GetSingleValue("copyText") );
  88:                 insert = insert.Replace( "@INDEX", postID + "_" + index.ToString() ); //### use index of code-div as a unique ID to allow multiple code-divs on this post
  89:                 insert = insert.Replace( "@CODE", code.Replace( "", "" ).Replace( "'", "'" ).Replace( "rn", "rn" ).Replace( "rnrnrn", "rn" ) );
  90:                 if( settings.GetSingleValue("aboveBelow").ToLower() == "above" )
  91:                     e.Body = e.Body.Insert( index, insert );
  92:                 else
  93:                     e.Body = e.Body.Insert( e.Body.IndexOf( "</div>", end + 1 ) + 6, insert );
  94: 
  95:                 //### prep index to find next code-div
  96:                 index = index + insert.Length + 1; //### ensure we don't find this same code-div again
  97:                 if( index > e.Body.Length ) break;
  98:                 index = e.Body.IndexOf( toFind, index ); //### find any other code divs
  99: 
 100:             }
 101:         }
 102:     }
 103: 
 104:     private string OutputCommonMethods()
 105:     {
 106: 
 107:         //### only output the following once per page
 108:         if( System.Web.HttpContext.Current.Items["CopyToClipboard_JSOutput"] == null )
 109:         {
 110:             //### add shared javascript
 111:             string flashPath = System.Web.VirtualPathUtility.ToAbsolute(settings.GetSingleValue("flashFile"));
 112:             string commonMethods = @"
 113:                 <div id=""CopyToClipboard_Hidden"" style=""display:none;""></div>
 114:                 <div id=""CopyToClipboard_FlashContainer""></div>
 115:                 <script type=""text/javascript"">
 116: 
 117:                     function CopyToClipboard_Strip( text ){
 118:                         text = text.replace( /&nbsp;/g, ' ' );
 119:                         text = text.replace( /&quot;/g, '""' );
 120:                         text = text.replace( /&#39;/g, '""' );
 121:                         text = text.replace( /&amp;/g, '&' );
 122:                         text = text.replace( /&lt;/g, String.fromCharCode(60) );
 123:                         text = text.replace( /&gt;/g, String.fromCharCode(62) );
 124:                         return text;
 125:                     }
 126: 
 127:                     function CopyToClipboard_Copy( text ){
 128: 
 129:                         //### get reference to utility div
 130:                         var ele = document.getElementById('CopyToClipboard_Hidden');
 131: 
 132:                         //### the following taken from: http://webchicanery.com/2006/11/14/clipboard-copy-javascript/
 133:                         if (false && window.clipboardData) {
 134:                             window.clipboardData.setData( ""Text"", text );
 135:                         } else {
 136:                             document.getElementById('CopyToClipboard_FlashContainer').innerHTML = '';
 137:                             var divinfo = '<embed id=""CopyToClipboard_FlashFile"" src=""" + flashPath + @""" FlashVars=""clipboard=' + encodeURIComponent(text) + '"" width=""0"" height=""0"" type=""application/x-shockwave-flash""></embed>';
 138:                             document.getElementById('CopyToClipboard_FlashContainer').innerHTML = divinfo;
 139:                         }
 140: 
 141:                     }
 142: 
 143:                     function CopyToClipboard_ViewPlain( text ){
 144:                         var win = window.open( '', 'CopyToClipboard_Window', 'width=480, height=480, toolbar=no, menubar=no, scrollbars=auto, resizable=yes, location=no, directories=no, status=no' );
 145:                         win.document.write( '<html><head><title>Code</title><body style=""margin:0;padding:0;""><textarea style=""width:100%;height:100%;border:0;"">' + text + '</textarea></body></html>' );
 146:                     }
 147: 
 148:                 </script>
 149:             ";
 150:             System.Web.HttpContext.Current.Items["CopyToClipboard_JSOutput"] = true;
 151:             return commonMethods;
 152:         }
 153:         else
 154:             return "";
 155: 
 156:     }
 157: 
 158: }

WLWSyntaxHighlighter.cssDownload (1.26 kb)

The Css generated by the Windows Live Writer Code Snippet plugin, modified to fit into my blog's colors.

   1: .csharpcode-wrapper, .csharpcode-wrapper pre {
   2:   clear: both;
   3:   background-color: #f5f5f5;
   4:   border: solid 2px #A0410D;
   5:   font-family: Consolas, 'Courier New', Courier, Monospace;
   6:   font-size: 8pt;
   7:   line-height: 12pt;
   8:   margin: 15px 0 0 0px;
   9:   max-height: 227px;
  10:   overflow: auto;
  11:   padding: 0px 0px 0px 0px;
  12:   width: 454px;
  13: }
  14: .csharpcode-wrapper pre {
  15:   border-style: none;
  16:   margin: 0px 0px 0px 0px;
  17:   overflow: visible;
  18:   padding: 0px 0px 0px 0px;
  19: }
  20: .csharpcode, .csharpcode pre, .csharpcode .alt {
  21:   background-color: #f5f5f5;
  22:   border-style: none;
  23:   color: black;
  24:   font-family: Consolas, 'Courier New', Courier, Monospace;
  25:   font-size: 8pt;
  26:   line-height: 12pt;
  27:   overflow: visible;
  28:   padding: 0px 0px 0px 0px;
  29:   width: 100%;
  30: }
  31: .csharpcode pre {
  32:   margin: 0em;
  33: }
  34: .csharpcode .alt {
  35:   background-color: #f4f2df;
  36: }
  37: .csharpcode .asp {
  38:   background-color: #ffff00;
  39: }
  40: .csharpcode .attr {
  41:   color: #ff0000;
  42: }
  43: .csharpcode .cls {
  44:   color: #cc6633;
  45: }
  46: .csharpcode .html {
  47:   color: #800000;
  48: }
  49: .csharpcode .kwrd {
  50:   color: #0000ff;
  51: }
  52: .csharpcode .lnum {
  53:   color: #f1c969;
  54: }
  55: .csharpcode .op {
  56:   color: #0000c0;
  57: }
  58: .csharpcode .preproc {
  59:   color: #cc6633;
  60: }
  61: .csharpcode .rem {
  62:   color: #008000;
  63: }
  64: .csharpcode .str {
  65:   color: #006080;
  66: }

CopyCodeToClipboard.cssDownload (588 bytes)

The Css I use to style the little red tab below each code area. This is what you would change to match your blog.

   1: .CopyToClipboard{
   2:     width: 458px;
   3:     text-align: right;
   4:     margin: 0 0 5px 0;
   5: }
   6: .CopyToClipboard div{
   7:     margin-left: 248px;
   8:     background-color: #A0410D;
   9:     padding: 0px 5px 2px 5px;
  10:     text-align: center;
  11:     color: #F1C969;
  12:     width: 200px;
  13:     font-size: 9px;
  14: }
  15: .CopyToClipboard a{
  16: }
  17: .CopyToClipboard a:link{
  18:     text-decoration: none;
  19:     color: #f5f5f5;
  20: }
  21: .CopyToClipboard a:visited{
  22:     text-decoration: none;
  23:     color: #f5f5f5;
  24: }
  25: .CopyToClipboard a:hover{
  26:     text-decoration: underline;
  27:     color: #f5f5f5;
  28: }
  29: .CopyToClipboard a:active{
  30:     text-decoration: underline;
  31:     color: #f5f5f5;
  32: }

_clipboard.swfDownload (109 bytes)

A Macromedia Flash file used to circumvent Firefox & Safari's nasty habit of blocking javascript from interacting with the clipboard. Created (as best I can tell) by Mark O'Sullivan of http://lussumo.com/.

Update (July 11, 2008):
A release of the Code Snippet plug hasn't been made since the last changes to its source was checked in (in late 2007), so I went ahead and downloaded & built the source. To install the plug place the two DLLs in the zip file from below into Windows Live Writer's /Plugins/ directory.

CodeSnippetPlugin.zip (64.91 kb)

Kick It on DotNetKicks.comShout It on DotNetShoutOuts.com

MVC Authentication and Errors

// January 4th, 2008 // 21 Comments » // MVC

NOTE:
This article was written for the December CTP release of the MVC framework. Unfortunately, it does not entirely apply to the Preview 2 release or subsequent releases.

I love working with the recent CTP release of the ASP.Net MVC framework, but it is definitely an early release and is lacking many of the developer friendly features that we have grown to rely upon in WebForms. One such feature is WebForm’s easy to understand authentication model.

In the WebForms world URLs can be referenced in a web.config file and then have authentication rules applied to them. A rule that says that you must be logged in to view a secure page may look something like:

1 <location path=loginRequired.aspx>

2 <system.web>

3 <authorization>

4 <deny users=? />

5 </authorization>

6 </system.web>

7 </location>

A rule that says only users in the Administrators group may view it might look like this:

1 <location path=adminRequired.aspx>

2 <system.web>

3 <authorization>

4 <allow roles=Administrators />

5 <deny users=* />

6 </authorization>

7 </system.web>

8 </location>

It didn’t take long for me to run into the lack of any central authentication scheme in the new MVC framework. I searched around and found some older information from prior to the CTP release posted by Fredrik Normén that seemed to address my issues, but unfortunately one of the features his solution requires did not make its way into the CTP release: attribute based exception handling.

Looking through the code samples on the page you see how he uses the .Net frameworks built in PrincipalPermission attribute (from System.Security.Permissions) to classify an action as demanding the user be in a specific role. If the user is not in that role the .Net framework will throw a SecurityException. What good does that do? Well take a look at line 3 in the below code:

1 [ControllerAction]

2 [PrincipalPermission(SecurityAction.Demand, Role="Admin"]

3 [ExceptionHandler("Error", typeof(SecurityException))]

4 public void Edit(int? id)

5 {

6

7 }

The ExceptionHandler attribute appears to take two values:

  1. The view to render in the event of an error.
  2. The type of error to match against.

So based upon this code the PrincipalPermission will interrogate the user’s roles when the action is requested and if the user is not in the “Admin” role it will throw a SecurityException. At that point the ExceptionHandler will wake up and say “hey I can handle that” and render the view named “Error”. Neat huh? Too bad ExceptionHandler doesn’t exist…

Personally I liked most of the concepts that were introduced in Frederik’s post, so I went ahead and began to implement the ExceptionHandler attribute. Along the way I realized that what was really needed was a way to apply filters to a controller. I’ve seen Ivan Carrero’s controller filter implementation, but I wanted filters that hooked straight into the MVC Controller’s three major lifecycle events: OnPreAction, OnPostAction, and OnError. By doing so I felt I would minimize the difference between code in a filter and code in a controller. Thus was born the FilterController.

Filter Controller

The FilterController is an abstract class deriving from the System.Web.Mvc.Controller. It’s primary purpose is to interrogate itself via reflection when it is created and to then load any attributes that implement the IControllerFilter interface:

1 /// <summary>

2 /// Descendent of the MVC Controller class that adds capability of processing filters specified by attributes.

3 /// </summary>

4 public abstract class FilterController : Controller

5 {

6

7 /// <summary>

8 /// Default constructor.

9 /// </summary>

10 public FilterController()

11 {

12 Filters = GetFilterAttributes();

13 foreach( IControllerFilter filter in Filters )

14 {

15 filter.Initialize(this);

16 }

17 }

The filters are then called for each of the three integration events: OnPreAction, OnPostAction, and OnError. Here is what the OnError event does:

1 /// <summary>

2 /// Passes control of the OnError event on to all filters that want to handle it.

3 /// </summary>

4 /// <param name=”actionName”>The name of the action being requested when the exception was thrown.</param>

5 /// <param name=”methodInfo”>Reflection object representing the action being requested when the exception was thrown.</param>

6 /// <param name=”exception”>The exception thrown while the action was being requested.</param>

7 /// <returns>A boolean denoting the successful handling of the event.</returns>

8 protected override bool OnError( string actionName, MethodInfo methodInfo, Exception exception )

9 {

10 bool handled = false;

11 bool allTrue = true;

12 foreach( IControllerFilter filter in Filters )

13 {

14 if( filter.HandleOnError )

15 {

16 handled = true;

17 if( !filter.OnError( actionName, methodInfo, exception ) )

18 allTrue = false;

19 }

20 }

21 if( !handled )

22 throw exception;

23 return allTrue;

24 }

The OnPreAction and OnPostAction events look almost exactly like the OnError event.

To fulfill my initial goal of obtaining functionality similar to that described in Frederik’s post, I have created two filters:

  • SecurityFilter
  • ErrorHandlerFilter

Security Filter

While the PrincipalPermission attribute used in Frederik’s post handles many security scenarios well, it wasn’t as flexible or keyboard friendly as I would prefer. I created the SecurityFilter and an arrangement of sub-filters to create what I think is an easier solution.

To use the security filter in your controller you must first inherit from FilterController and apply the [SecurityFilter] attribute.

1 using System;

2 using System.Web.Mvc;

3 using SquaredRoot.Mvc;

4

5 namespace Example

6 {

7

8 [SecurityFilter]

9 public class MyController : FilterController

10 {

11

12 }

This alone does nothing, but you are now able to add one or more of the SecurityFilter‘s sub-filters to this controller or it’s actions. The sub-filters I have created are:

  • RequireLogin
    Validates that the user is logged in.
  • RequireAnonymous
    Validates that the user is NOT logged in.
  • RequireRole
    Validates that the user is in the specified role.
  • RequireAnyRole
    Validates that the user is in at least one of the specified roles
  • RequireEachRole
    Validates that the user is in every one of the specified roles.

Let’s imagine a controller for a simple bulletin board system. In order to post to this forum you must be logged in, if you want to delete a post you must be in either the “Administrators” role or the “Moderators” role, and if you want to undelete a post you must be in the “Administrators” group. That controller would look something like:

1 [SecurityFilter]

2 [RequireLogin]

3 public class ForumController : FilterController

4 {

5

6 [ControllerAction]

7 public void Post( string message ){ … }

8

9 [ControllerAction,RequireAnyRole( "Administrators", "Moderators" )]

10 public void Delete( int id ){ … }

11

12 [ControllerAction,RequireRole( "Administrators" )]

13 public void Undelete( int id ){ … }

14

15 }

By applying the [RequireLogin] attribute to the class you have applied that filter to all of the actions as well, which means you must be logged in to call the Post method. The other two methods use the appropriate version of the role requirement filters to achieve their goal.

What happens if the filter validations fail? In the case of an anonymous user attempting to access a restricted resource an AnonymousAccessException (which derives from SecurityException) is thrown while all other scenarios throw a SecurityException. What you do with those exceptions leads us to…

Error Handler Filter

Using the above ForumController, let’s add the ErrorHandler filter:

1 [SecurityFilter]

2 [ErrorHandlerFilter]

3 [RequireLogin]

4 public class ForumController : FilterController

5 {

6

7 }

Like with the last filter, this filter by itself does nothing but allow us to use the ErrorHandler sub-filter. Let’s go ahead and add two sub-filters: one to handle security exceptions and one to handle all other exceptions.

In the event of a security exception we’ll render the “AccessDenied” view while all other exceptions will render the “SystemError” view:

1 [SecurityFilter]

2 [ErrorHandlerFilter(ErrorHandlerMode.Render)]

3 [RequireLogin]

4 [ErrorHandler( 1, "AccessDenied", typeof(SecurityException) )]

5 [ErrorHandler( 2, "SystemError", typeof(Exception) )]

6 public class ForumController : FilterController

7 {

8

9 }

First notice that we are now providing the ErrorHandlerFilter attribute with an option that says ErrorHandlerMode.Render. This is because in the event of an error we want the controller to render the view with the name passed in. Later on we’ll look at the other mode: ErrorHandlerMode.Redirect.

Next notice that we are providing three values to each of the two ErrorHandler sub-filters:

  1. The order in which the sub-filter should be processed. This is important because the order the attribute is returned by reflection is unknown.
  2. The name of the view to render. Just like calling RenderView() from an action, this view name must be accessible to the controller (either in the controller’s view directory or in the Shared directory).
  3. The type of the exception to match against.

Keep in mind that these sub-filters could be applied at either the class level or the method level and that method-level sub-filters are processed before class-level sub-filters. We’ll stick with class-level throughout this article.

An “Access Denied” page popping up whenever we try to go somewhere we aren’t allowed to without logging in isn’t the best user experience. Let’s improve it by sending anonymous users to the login page instead. This time however, we don’t want just render the login view, we want to actually redirect to the SecurityController‘s Login action. While we’re at it, I’ll show you an example of handling multiple exceptions with one sub-filter:

1 [ErrorHandler( 1, "Login,Security",

2 ErrorHandlerMode.Redirect,

3 typeof(AnonymousAccessException) )]

4 [ErrorHandler( 2, "AccessDenied",

5 typeof(SecurityException) )]

6 [ErrorHandler( 3, "BadArgument",

7 typeof(ArgumentException),

8 typeof(ArgumentNullException),

9 typeof(ArgumentOutOfRangeException) )]

10 [ErrorHandler( 4, "SystemError",

11 typeof(Exception) )]

Here in line 1 we specify not the name of the view to render, but the name of the action and controller to redirect to (in the format “action,controller”). The handler knows to process this as a redirect because we’ve changed the mode for this one sub-filter to ErrorHandlerMode.Redirect. Lines 7, 8, & 9 illustrate the capability for one sub-filter to match against many exceptions.

Download The Code

I hope you find these filters useful. If they don’t happen to match your particular problem, then feel free to write your own filters. To do so you only have to implement the IControllerFilter interface. I’ve attempted to make it even easier to do so by providing a base class named ControllerFilter that already implements the interface and has several hooks for you to take advantage of.

You are free to use or modify this code for anything including commercial purposes. The only restriction I ask is that you do not take credit for this work yourself, but I do not require any specific attribution.

I have packaged the code into four different releases:

NOTE: These projects were built on Vista x64. If you are running a 32-bit version of Windows you may initially have trouble building the source versions below. To fix this you’ll need to re-add the System.Web.Extensions reference. See my comment below for more details.

Full Source Release
The entire solution zipped up.
Controller Filters (Full Source).zip

Example Site & Binaries Release
(Recommended) Just the binaries zipped up with an example site.
Controller Filters (Example Site).zip

Filter Source Release
The source code for the FilterController and filters. No example site.
Controller Filters (Filter Source).zip

Filter Binaries Release
The binaries for the FilterController and filters only. No source.
Controller Filters (Filter Binaries).zip

Kick It on DotNetKicks.comShout It on DotNetShoutOuts.com

MVC Template Fix

// January 2nd, 2008 // 6 Comments » // MVC

Several weeks ago I blogged about a bug I found in the CTP release of the MVC framework. The gist of the bug is that controls declared in the html portion of a page or user control could not be referenced from the code-behind of that page or user control. ScottGu posted a comment clarifying that this was due to a bug in the templates released in the CTP.

After nearly a month of following Scott's advice on how to fix the bug, I finally grew tired of such a repetitive task and have fixed the templates. You'll find a zip file containing the fix and installation instructions at the bottom of this post. Before I get into that though, let me explain the workaround (suggested by ScottGu) that I've been using until now:

The Easy, Repetitive Workaround

The problem is that the template for pages, master pages, and user controls only include the declarative HTML file and it's code-behind. The template is missing the designer file used by Visual Studio to hide the references generated by the server controls declared in your HTML. Here is what a MVC page looks like right after creation:

SimpleWorkaround-Before

You could manually create a designer class to fix this issue, but that would be a pain. Luckily Visual Studio provides us with the handy "Convert to Web Application" feature to generate the designer class for us. Simply right-click on the .ASPX (or .Master or .ASCX) and click the "Convert to Web Application" option on the context menu.

SimpleWorkaround-During

After doing so, you should see this:

SimpleWorkaround-After

Voilà! Your codebehind file for that page/control (and that page/control only) should now work as expected. Its quick and simple, but I'm sure you can imagine already the annoyance at having to do this for each and every page or control you create. That thought leads us to…

The Slightly More Complex Permanent Solution

First, an admission of guilt: I only fixed the C# templates. I apologize to any VB.Net'ers out there ahead of time.

There are four templates that must be fixed:

  1. View Master Page – A master page.
  2. View Page – A normal page.
  3. View Content Page – A page that uses a master page.
  4. View User Control – A control.

These four templates are found in two different places: the item templates folder and the item templates cache folder.

Those folders are found at: "Common7IDEItemTemplatesCSharpWeb1033" and "Common7IDEItemTemplatesCacheCSharpWeb1033" respectively (within your VS 9 root folder).

The item templates folder contains zip files which are at some point unzipped and stored in the item templates cache folder. I suppose you could just update the contents of the item templates cache folder, but I don't know if those settings would ever be written over by the item templates folder, so it is better to be safe and fix it in both places.

TemplateFix-ZipLocation

TemplateFix-CacheLocation

The contents of a typical template for pages/user controls are:

  • a vtemplate file (which serves as the manifest for the rest of the items)
  • an icon file
  • an html file (ASPX, ASCX, etc)
  • a codebehind file
  • a designer file

As you can see from the screenshot below, the templates did not include a designer file.

TemplateFix-ZipContents

In addition, the vtemplate file does not describe a designer file.

TemplateFix-TemplateFile

The fix is to add a line to the vtemplate file referencing a designer file and then add the designer file itself to the template folder/zip. The designer file should look something like:

TemplateFix-DesignerFile

I've gone ahead and done all this for you — well, actually for me, but you get the point. :-) All you need to do is download the zip file from below and follow the instructions included in the readme.txt file.

MVC Template Fix.zip (50.54 kb)

Kick It on DotNetKicks.comShout It on DotNetShoutOuts.com