Friday, March 26, 2010

Constructor injection for ActionFilters in ASP.Net MVC with auto-wiring

As you know ActionFilters allow to execute your code when action is executed. It is very convenient extension point. But what makes it painful – is how to provide dependencies into your custom ActionFilter. There are some solutions for it. E.g. here mentioned solution for constructor injection. But it has a drawback. Instead of natural syntax like:

   1: [AuthorizationFilter]
   2: public ActionResult SomeAction()
   3: {
   4:     ...
   5: }

you need to write:

   1: [Filter(typeof(AuthorizationFilter))]
   2: public ActionResult SomeAction()
   3: {
   4:     ...
   5: }

which is quite unnatural. Another solution uses property injection. It works but once you use auto-wiring with constructor injection this method also looks not very good.

When you have auto-wiring you become a bit lazy – you just want your IoC container wires all dependencies for you. So what is the problem? Suppose we have custom ActionFilter which has constructor with parameter. In this case you will not be able to write [YourCustomAttribute] as compiler will warn you that it has a parameter in constructor and you need to specify it. But if this parameters come from IoC – then the only option for you is to leave parameter-less constructor and call Container.Resolve() in the body of constructor.

So is there a way to inject constructor dependencies in ActionFilters using auto-wiring without dependency on IoC container? As ASP.Net is quite extensible framework – the answer is yes. Suppose that we need AdminOnlyAttribute (which is not IActionFilter actually, but IAuthorizationFilter. But for this post it is not important) which has 1 dependency ISecurityService:

   1: [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
   2: public class AdminOnlyAttribute : FilterAttribute, IAuthorizationFilter
   3: {
   4:     private ISecurityService securityService;
   5:  
   6:     public AdminOnlyAttribute()
   7:     {
   8:     }
   9:  
  10:     public AdminOnlyAttribute(ISecurityService securityService)
  11:     {
  12:         this.securityService = securityService;
  13:     }
  14:  
  15:     public void OnAuthorization(AuthorizationContext filterContext)
  16:     {
  17:         if (!this.securityService.IsCurrentUserAdmin())
  18:         {
  19:             filterContext.Result = new HttpUnauthorizedResult();
  20:         }
  21:     }
  22: }

Note that it has 2 constructors: parameter-less and constructor with parameter of type ISecurityService. We need parameter-less constructor in order to have possibility to write just [AdminOnly] without compiler errors:

   1: [AdminOnly]
   2: public class AdminController : Controller
   3: {
   4:     ...
   5: }

In this case MVC will create AdminOnlyAttribute using parameter-less contructor. But OnAuthorization() method supposes that 2nd contructor was called and that securityService is not null. So how can we provide not null securityService if MVC creates it with securityService = null? The answer is – custom ControllerActionInvoker. We will analyze action filters for action before to call it and use well known feature of IoC containers which by default try to create object using maximum number of dependencies:

   1: public class MostMatchedControllerActionInvoker : ControllerActionInvoker
   2: {
   3:     private IContainer container;
   4:  
   5:     public MostMatchedControllerActionInvoker(IContainer container)
   6:     {
   7:         this.container = container;
   8:     }
   9:  
  10:     protected override AuthorizationContext InvokeAuthorizationFilters(
  11:         ControllerContext controllerContext, IList<IAuthorizationFilter> filters,
  12:         ActionDescriptor actionDescriptor)
  13:     {
  14:         var mostMatchedFilters = this.getMostMatchedFilters(filters);
  15:         return base.InvokeAuthorizationFilters(controllerContext, mostMatchedFilters,
  16:             actionDescriptor);
  17:     }
  18:  
  19:     protected override ActionExecutedContext InvokeActionMethodWithFilters(
  20:         ControllerContext controllerContext, IList<IActionFilter> filters,
  21:         ActionDescriptor actionDescriptor, IDictionary<string, object> parameters)
  22:     {
  23:         var mostMatchedFilters = this.getMostMatchedFilters(filters);
  24:         return base.InvokeActionMethodWithFilters(controllerContext, mostMatchedFilters,
  25:             actionDescriptor, parameters);
  26:     }
  27:  
  28:     private IList<T> getMostMatchedFilters<T>(IList<T> originalFilters)
  29:     {
  30:         if (originalFilters == null)
  31:         {
  32:             return null;
  33:         }
  34:  
  35:         var mostMatchedFilters = new List<T>();
  36:         foreach (var filter in originalFilters)
  37:         {
  38:             mostMatchedFilters.Add((T)this.container.GetInstance(filter.GetType()));
  39:         }
  40:         return mostMatchedFilters;
  41:     }
  42: }

I.e. for each filter we call container.GetInstance(filter.GetType())) method of IoC container (I used StructureMap, but particular type of container is not important here) which in turn will call constructor with maximum number of dependencies. So we will override AdminOnlyAttribute which was created by MVC with parameter-less constructor by another instance which is created using constructor with parameter.

This code however has a problem: each filter is resolved twice. Once by MVC and second time by our code. It can lead to performance issues. In order to avoid this problem the following technique can be used. Add a mixing interface IFilterWithMostMatchedConstructor:

   1: public interface IFilterWithMostMatchedConstructor
   2: {
   3: }

Then add this interface to our AdminOnlyAttribute class:

   1: [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
   2: public class AdminOnlyAttribute : FilterAttribute, IAuthorizationFilter,
   3:     IFilterWithMostMatchedConstructor
   4: {
   5:     ...
   6: }

and slightly change getMostMatchedFilters() method of MostMatchedControllerActionInvoker:

   1: private IList<T> getMostMatchedFilters<T>(IList<T> originalFilters)
   2: {
   3:     if (originalFilters == null)
   4:     {
   5:         return null;
   6:     }
   7:  
   8:     var mostMatchedFilters = new List<T>();
   9:     foreach (var filter in originalFilters)
  10:     {
  11:         if (filter is IFilterWithMostMatchedConstructor)
  12:         {
  13:             mostMatchedFilters.Add((T)this.container.GetInstance(filter.GetType()));
  14:         }
  15:         else
  16:         {
  17:             mostMatchedFilters.Add(filter);
  18:         }
  19:     }
  20:     return mostMatchedFilters;
  21: }

So we only replace those filters which implement IFilterWithMostMatchedConstructor interface. We minimized performance overhead. Remaining overhead is just creation of AdminOnlyAttribute with parameter less constructor by MVC. But I think this is acceptable compromise in order to have auto-wiring with constructor injection for ActionFilters.

Tuesday, March 23, 2010

Get rid of modal login window when user opens Office 2007 document in Sharepoint under FBA

As described here when user clicks on some Office 2007 document which located in Sharepoint document library and when FBA is used – user may see modal login window:

“The Forms Authentication Integration Update for Office 2007

When Office SharePoint Server 2007 and Microsoft Office 2007 were first released, the Office client applications such as Microsoft Office Word and Microsoft Office Excel could not directly open a document from a site that was secured with forms authentication. This is because, as explained earlier, a 302 HTTP response code is sent back to the client when it tries to open an item in a site using forms authentication. The Office clients were not able to respond to a 302 response code, and as a result would display the actual forms logon page in the application, instead of the requested document.

An update is available for Office 2007 client applications that allow the applications to process a 302 HTTP response code. The applications that are affected by this update are Microsoft Office Word 2007, Microsoft Office Excel 2007, Microsoft Office PowerPoint 2007 and Microsoft Office SharePoint Designer2007. Because of this update, an Office application can display the forms logon page that is being used for the site in a pop-up dialog box. To do this, the application issues a request to the SharePoint site. The server sends a response that indicates its authentication method is forms authentication, including the location of the logon page that the client should use to authenticate. The Office application then renders the HTML from that logon page and enables the user to enter credentials. The credentials are sent via an HTTP POST back to the server. If the server returns a redirect response for the document that was originally requested, the Office application assumes that the identity is successfully established. It then uses the authorization cookie that the HTTP POST gave it to retrieve the document and any associated metadata, and open the item.”

We have a requirement to remove this login page when user opens office document from Sharepoint doclib. Trying out to enable client integration in internet zone and changing “HKEY_CURRENT_USER\ Software\Microsoft\Office\12.\Common\Internet\FormsBasedAuthSettings” registry key didn’t help – login window didn’t disappear.

I would separate this problem on two issues:

  1. Login window is shown for authenticated users when they open documents. If user just closes this login window without entering valid credentials – an empty office document is opened
  2. Login window is also shown to anonymous users. But despite of authenticated user, if anonymous user closes login window – then document successfully opened (of course if you configured anonymous access to the doclib from which document is loaded. But this is out of scope of the current post)

After investigation I found the following solutions for both mentioned problems:

  1. For authenticated users – persistent cookies should be used. See comments to the following post http://www.sharepointkings.com/2008/06/in-fba-when-i-open-document-from.html. We use custom login page and standard ASP.Net Login control on it. So I set RememberMeSet property to true. It in turns passed true in second parameter of FormsAuthenticateion.SetAuthCookie(…) method. See AttemptLogin() method of Login control:
  2.    1: private void AttemptLogin()
       2: {
       3:     ...
       4:     FormsAuthentication.SetAuthCookie(this.UserNameInternal, this.RememberMeSet);
       5:     this.OnLoggedIn(EventArgs.Empty);
       6:     this.Page.Response.Redirect(this.GetRedirectUrl(), false);
       7:     ...
       8: }
  3. In order to remove login window for anonymous users – special http module is required. See http://www.theblackknightsings.com/RemoveLoginBoxWhenAnonymousUsersDownloadOfficeDocumentFromSharePointSite.aspx:

“But there is one are where the out of the box experience fails regarding anonymous access and that is when you allow the users to download Microsoft Office documents. In that case IE/Office pops up a couple of Login dialogs, if the user cancels out of these the document opens as expected, but you really don't want the user to have to cancel a couple of dialogs to open your documents

The problem is that office tries to be intelligent and issues a Microsoft Office Protocol Discovery request to see how much the user is allowed to do, but SharePoint responds with access denied until the users logs in.

The solution I've found is to implement a HttpModule which rejects the Microsoft Office Protocol Discovery request if the user isn't logged in and this gets rid of the Login boxes”

After I applied this module – login window disappeared for anonymous users.

These 2 solutions helped me to avoid problem with modal login window both for authenticated and anonymous users.

Saturday, March 20, 2010

Set locale of current thread for publishing pages in Sharepoint

In my previous post I described how Sharepoint configures CurrentUICulture and CurrentCulture properties of current thread based on SPWeb.Language. I also wrote that described algorithm is used only for application layouts page (pages which located in 12/template/layouts folder), but not for publishing pages (or content or site pages). And I said that I will write another blog post about setting  of locale of current thread if request goes to publishing pages. And I keep my promise :)

Publishing functionality is available on sites with activated Publishing Infrastructure feature (there are several publishing features actually). By default it is activated on sites created with Publishing Site or Collaboration Site templates under Publishing category on site or site collection creation page. I would separate 2 types of pages in publishing infrastructure:

  • publishing page layouts. They are provisioned into http://example.com/_catalogs/masterpage. You can find them in Site Settings > Master pages and page layouts
  • publishing page instances – i.e. publishing pages itself. These are the pages which were created based on some publishing page layout. Most often publishing page instances are created in OTB Pages doclib (actually they should be created there because Sharepoint has several assumptions when handle publishing page instances. One of the assumption is that publishing page instance is located in Pages doclib. But this is not necessary, i.e. you can create publishing page instances in custom doclib also. In this case additional actions are required in order to avoid limitation of Sharepoint – this is out of scope of current blog post. I will write another post about it)

I.e. there is analogy with OOP: publishing page layouts are kind of “classes” of pages, and publishing page instances are “objects”. Often Sharepoint uses the following classes for mentioned pages:

  • Microsoft.SharePoint.Publishing.PublishingLayoutPage for publishing page layout pages
  • Microsoft.SharePoint.Publishing.TemplateRedirectionPage for publishing page instances

Both classes inherit PublishingCachablePage class which in turn inherits WebPartPage class. Lets see constructor of WebPartPage:

   1: public WebPartPage()
   2: {
   3:     this._exclusion = false;
   4:     HttpContext current = HttpContext.Current;
   5:     if ((current != null) &&
   6:        (SPRequestModuleData.GetRequestData(current, null, false) == null))
   7:     {
   8:         this._exclusion = true;
   9:     }
  10:     if (this._exclusion)
  11:     {
  12:         SPWeb contextWeb = SPControl.GetContextWeb(HttpContext.Current);
  13:         if (contextWeb != null)
  14:         {
  15:             CultureInfo locale = contextWeb.Locale;
  16:             CultureInfo languageCulture = contextWeb.LanguageCulture;
  17:             Utility.SetThreadCulture(locale, languageCulture, false);
  18:         }
  19:     }
  20: }

So if SPRequestModuleData.GetRequestData(…) method returns null for publishing page (in this case publishign page is treated as “exclusion” as you can see in code above) locale of current thread will be initialized from SPWeb.Locale and SPWeb.LanguageCulture of current web. But what if SPRequestModuleData.GetRequestData(…)  method will return non-null result:

   1: internal static SPRequestModuleData GetRequestData(HttpContext context,
   2:     string virtualPath, bool allowCreate)
   3: {
   4:     SPRequestModuleData data = null;
   5:     if (context != null)
   6:     {
   7:         data = (SPRequestModuleData)
   8:             context.Items[typeof(SPRequestModuleData)];
   9:     }
  10:     if (allowCreate && (data == null))
  11:     {
  12:         ...
  13:     }
  14:     else
  15:     {
  16:         return data;
  17:     }
  18:     return null;
  19: }
  20:  

As you can see in our case it GetRequestData can return non-null result if HttpContext.Items collection contains instance of SPRequestModuleData (as 3rd parameter allowCreate is false). I will not deeply ananlyze in what cases Sharepoint returns null or not-nulls from GetRequestData. For this article it is enough that MOSS treats this situation as “exclusion”, i.e. I can assume that most of publishing pages should have non-null value returned from GetRequestData in constructor. Even more, if such situation occured, i.e. if GetRequestData returned null, see that last param “force” in Utility.SetThreadCulture is false also:

   1: Utility.SetThreadCulture(locale, languageCulture, false);

I already showed code of Utility.SetThreadCulture(), but it is important for this post also, so I will show it again:

   1: internal static void SetThreadCulture(CultureInfo culture,
   2:     CultureInfo uiCulture, bool force)
   3: {
   4:     HttpContext current = HttpContext.Current;
   5:     if ((force || (current == null))
   6:         || (current.Items[IsThreadCultureSet] == null))
   7:     {
   8:         if (!Thread.CurrentThread.CurrentCulture.Equals(culture))
   9:         {
  10:             Thread.CurrentThread.CurrentCulture = culture;
  11:         }
  12:         if (!Thread.CurrentThread.CurrentUICulture.Equals(uiCulture))
  13:         {
  14:             Thread.CurrentThread.CurrentUICulture = uiCulture;
  15:         }
  16:         if (!force && (current != null))
  17:         {
  18:             current.Items[IsThreadCultureSet] = true;
  19:         }
  20:     }
  21: }

I.e. even if publishing page is treated as exclusion – locale of current thread will be set only if HttpContext.Current = null (I think it is non-common situation) or when thread culture has not been initialized yet (HttpContext.Current.Items[IsThreadCultureSet] == null). So there should be a place where language is initialized for most of publishing pages (and this is not SPRequest.PreRequestExecuteAppHandler).

After analysis of all places where SetThreadCulture methods of SPUtility and WebPartPages.Utility classes are called, the most earlier place in pipeline where SetThreadCulture is called is SPWeb.InitWeb() method:

   1: private void InitWeb()
   2: {
   3:     if (!this.m_bInited)
   4:     {
   5:         ...
   6:         this.InitLocaleAndLanguageCulture();
   7:         Utility.SetThreadCulture(this.m_Locale, this.m_LanguageCulture, false);
   8:     }
   9: }
  10:  

This method is called from constructor of SPWeb. So when current SPWeb is initialized for SPContext 1st time, Sharepoint sets locale of current thread (but with force = false in opposite to SPRequest.PreRequestExecuteAppHandler Sharepoint which calls this method with force = true. Actually there are no so many places where Sharepoint calls SetThreadCulture with force = true. I found only ThreadCultureScope class and mentioned SPRequest.PreRequestExecuteAppHandler). I.e. once SetThreadCulture  method is called Sharepoint will use locale which was used in 1st call until next call with force = true. So for publishing pages mechanism a bit different. If for _layouts pages Sharepoint sets locale of current thread on later step explicitly (SPRequest.PreRequestExecuteAppHandler), for publishing pages Sharepoint sets locale of current thread on earlier steps in constructor of context SPWeb.

This is the internal mechanism of current thread locale initialization for publishing pages in Sharepoint.