Wednesday, December 21, 2011

ASP.NET MVC and SharePoint 2010

I work with SharePoint every day, but at the same time I really like ASP.NET MVC. Unfortunately these two technologies are not meant to be together, but considering that they are both a part of ASP.NET platform I tried to make them friends. The latest version of ASP.NET MVC that is written in .NET3.5 is version 2.0, so we have to stick with this version in SharePoint 2010 as well. There are already some approaches over the internet that allow to host ASP.NET MVC content in SharePoint.

For example
This solution works fine, but I don't like the idea of using /_layouts/ urls. I tried something different to achieve MVC integration in SharePoint and got the following.

1. Separate feature that deploys custom http handler to SharePoint filesystem.
2. Urls are more friendly (like http://myserver/sites/mvcsite/app.ashx/about).
3. Every subsite might have it's own MVC site and completely independent from each other.
4. Really simple configuration.

So what was done.

I created a small library where I used the concept of MVC areas - every MVC application inside of SharePoint must be an MVC area to have its own routes, controllers, etc. Every MVC application should have its own HTTP handler to distinguish MVC content from SharePoint content. This http handler should be configured correctly to server content from specific area. Here is a sample.

App.ashx - is our MVC http handler.
Layouts\MvcApp - store our MVC views, scripts, stylesheets and all other stuff for standard MVC application.
Web\Controllers - that's clear - MVC controllers.
Web\AppMvcRegistration.cs - MVC area registration class.

In order to configure everything correctly for MVC are it's necessary to write implementation of ISPMvcAreaRegistration interface in our application.

using SPMvc.Core;

namespace SPMvcSample.Web
    public class AppMvcRegistration : ISPMvcAreaRegistration
        public string AreaName
            get { return "mvcapp"; }

        public void RegisterRoutes(SPMvcAreaRegistrationContext context)
            context.MapRoute("Home", "app.ashx/home", new { controller = "Home", action = "Index" });
            context.MapRoute("About", "app.ashx/about", new { controller = "Home", action = "About" });

There are just two things to implement:

AreaName - very important - it's not just the name of area, but the folder in Layouts directory where all MVC Views should be located for particular application.
RegisterRoutes method - routes registration. During route registration the url of current subsite will be concatinated with http handler url.

This is pretty much all configuration needed to run MVC application in SharePoint.

Very familiar screenshot, isn't it (except it's running in SharePoint)? Notice the url is /sites/mvcapp/app.ashx/home.

The source code of SPMvc solution with some documentation is located here:

It's possible to deploy this solution not just in GAC, but in medium trust as well (Bin deployment). More details are here:'Bin'-deployment

Friday, November 11, 2011

Javascript: Maximum call stack size exceeded in Chrome

jQuery templates is very powerful mechanism of building client side rendering logic. But large templates are very expensive in terms of browser resources and not all browsers can deal with it. It was unexpected, but the most modern browser with the best javascript engine V8 - Google Chrome (I used version 16) failed to render large template.

The task was pretty straightforward - build html table with around 400 rows and 35 columns through jQuery templates. The table is quite big and probably it’s not the best UX to show such table to the user, but it is what it is. IE8/9, Firefox 7+ rendered everything perfectly, but then I checked Google Chrome and it failed with throwing the following error: RangeError: Maximum call stack size exceeded.

After very deep debugging of jQuery templates and jQuery I figured out the problem. jQuery templates library builds array of strings and then prepares nodes to insert them to the DOM. On large templates the resulting array is really big - in my case around 100k+ items. jQuery templates uses method which calls the following method in the end:

// Flatten any nested arrays
return ret.concat.apply( [], ret );

IE8/9 and Firefox process this amount of data absolutely fine, but Google Chrome - not. I prepared small script in jsfiddle and it looks Chrome cannot process even 70k items:

As a recommendation how to avoid this issue could be not having such number of items passed into the template or generate each item individually and insert it to the DOM - this will not create huge arrays and will work completely fine in all browsers.

Tuesday, November 1, 2011

How to remove SharePoint crawled properties

Recently I've been trying to remove crawled properties in one of my projects. The problem is that SharePoint 2010 doesn't have out of the box functionality to remove crawled properties. I tried to delete the whole category with all crawled properties but SharePoint didn't let me do that saying that it's not possible to delete not empty category. It's pretty obvious, but just in case I checked category deletion in Reflector:

So no luck here as well. It throws exception when finds crawled properties within this category after it tried to delete them.

I started to dig into this issue and found the following blog post that helped me to find a solution:

Unfortunately the solution above doesn't work as expected, even when there are no managed properties mapped to this crawled property. The thing is that the stored procedure that deletes unmapped properties (proc_MSS_DeleteCrawledPropertiesUnmappedForCategory) performs the following check - CP.IsMappedToContent = 0:

  1. Delete dbo.MSSCrawledProperties   
  2.    Where CrawledPropertyId in  
  3.    ( Select CP.CrawledPropertyId   
  4.       From MSSCrawledProperties as CP  
  5.       INNER JOIN dbo.MSSCrawledPropCategory as C  
  6.          on CP.Propset = C.Propset  
  7.       WHERE C.CategoryName = @CategoryName   
  8.         AND CP.IsMappedToContent = 0  
  9.         AND NOT EXISTS   
  10.           ( Select * from MSSSchemaPropertyMappings   
  11.             Where CrawledPropertyId = CP.CrawledPropertyId )   
  12.    )  
But for some crawled properties this value is always set to 1 even without mapped managed properties. So we need to set this value to 0 somehow. Fortunately Powershell comes to the rescue. There is IsMappedToContents property of crawled property object. And it's possible to set it to 0. Here is full piece of code with minimum checks that allows to remove crawled property.
  1. $searchAppName = "Search Service Application"  
  2. $categoryName = "Business Data"  
  4. function RemoveCrawledProperty($crawledPropertyName)  
  5. {  
  6.     $category = Get-SPEnterpriseSearchMetadataCategory -Identity $categoryName -SearchApplication $searchAppName  
  7.     $crawledProperty =  
  8.         Get-SPEnterpriseSearchMetadataCrawledProperty -Name $crawledPropertyName -SearchApplication $searchAppName -Category $category  
  9.     if ($crawledProperty)  
  10.     {  
  11.         $mappings = Get-SPEnterpriseSearchMetadataMapping -SearchApplication $searchAppName -CrawledProperty $crawledProperty  
  12.         if ($mappings)  
  13.         {  
  14.             $mappings | Remove-SPEnterpriseSearchMetadataMapping -Confirm:$false  
  15.         }  
  16.         else  
  17.         {  
  18.             Write-Host "No mappings found for '$crawledPropertyName'." -foregroundcolor yellow  
  19.         }  
  20.         $crawledProperty.IsMappedToContents = $false  
  21.         $crawledProperty.Update()  
  22.         $category.DeleteUnmappedProperties()  
  23.     }  
  24.     else  
  25.     {  
  26.         Write-Host "Crawled property '$crawledPropertyName' not found." -foregroundcolor yellow  
  27.     }  
  28. }  
Hope it will be helpful to someone.