AspAlliance.com LogoASPAlliance: Articles, reviews, and samples for .NET Developers
URL:
http://aspalliance.com/articleViewer.aspx?aId=1944&pId=-1
Using ASP.NET MVC 2 with Sharepoint Publishing
page
by Martin Bailey
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 31086/ 89

Introduction

This white paper outlines one method of enabling ASP.NET MVC 2 within the Sharepoint Publishing framework. It is targeted at Sharepoint professionals who are involved in the development of Sharepoint Publishing Sites.

Overview

ASP.NET MVC is a great technology for developing web applications. Its adoption is now prevalent within the ASP.NET community. It allows for the rapid development of web applications. For those not familiar with ASP.NET MVC, you can find out more here http://www.ASP.NET/mvc/

Sharepoint is a great enteprise web application framework. The majority of Sharepoint features are concerned with providing intranet, enterprise solutions and business to business functionality.  One of the key features of Sharepoint is the Web Publishing infrastructure, this is a web application framework for developing public facing web sites.

The Sharepoint Publishing infrastructure uses standard ASP.NET WebForms and WebControls. If you know how to create conventional ASP.NET web applications you can lever this knowledge to development Sharepoint Publishing Sites.

I am sure Sharepoint Publishing site developers have often asked the following question

Wouldn’t it be great to use the powerful content management features of Sharepoint but employ the rapid development methodology that MVC provides?

Any solution needs to be as simple and robust as possible and should not require any customisation to the Sharepoint installation, ie amending the Sharepoint 12 hive. 

The following sections of this document  introduce one such solution for using the ASP.NET MVC 2 framework within a Sharepoint Publishing Site. In essence this solution involves changes to the web configuration files and the creation of some custom .NET code.

Framework Integration

The following sections outline the steps required to integrate an ASP.NET MVC 2 site with a Sharepoint Publishing Site. Sharepoint 2007 and 2010 are both built upon the .NET framework 3.5. This restricts the ASP.NET MVC 2 site to also use this version of the framework.

Creating the ASP.NET MVC 2 Site

In this example we are going to use an MVC site which was created using the ASP.NET MVC 2 Web Application template within Visual Studio 2008.

Once created and fired up we get the default MVC home page.

This example uses an empty Sharepoint Publishing Site. Sharepoint professionals will be familiar with the following page.

Configuring the IIS virtual directory

Our MVC site will run as an IIS virtual directory directly under the Sharepoint IIS web site

 

In this instance we are going to name the virtual directory ‘MVC’. This means that any request starting with http:[hostname]/MVC/ will be served by the MVC site.

 

We set the ‘web site content directory path’ to the web site path of the visual studio MVC project. Ensure it is set to ‘Run scripts’.

 

We now have a standard ASP.NET MVC 2 site sitting under our host Sharepoint web site, called ‘SampleMVC’

By default this new virtual directory will use the same application pool as the host Sharepoint application. This will allow the ASP.NET MVC site to natively access the Sharepoint SPContext object.

Referencing Sharepoint from the MVC project

In order to expose Sharepoint functionality within our ASP.NET MVC site, the Microsoft.Sharepoint.dll assembly is referenced by the ASP.NET MVC web application

For 64bit instances of Sharepoint, referencing this assembly will cause a conflict within Visual Studio when viewing .aspx files. You will get the following error regarding an attempt to load a program with an incorrect format.

This has the effect of turning off all intellisense associated with .aspx files.

 

The Microsoft.Sharepoint.dll assembly has a dependency on the Microsoft.Office.Server.Search.dll assembly. This assembly happens to be compiled as AMD64 not MSIL.

 

By default Visual Studio uses the ASP.NET development server application at design time to parse .aspx files. It is this 32bit application which conflicts with the 64bit references. This is not a problem at run time as your IIS instance will also run as a 64bit application

 

This can be resolved by removing the physical Sharepoint assemblies from the project output directories. Since these assemblies are re-created on every build, the following visual studio project post-build event script is your friend here.

cd$(OutDir)
del Microsoft.Office.Server.Search.dll
del Microsoft.Sharepoint*

Configuring the Sharepoint site web.config

By default the Sharepoint application domain is not aware that it will be required to run an ASP.NET MVC site. We need to make some changes to the Sharepoint Publishing Site web.config file to define the Routing and MVCHttpHandler configuration

Add entry to the httpHandlers section

<httpHandlers>
<add verb="*" path="*.mvc" validate="false"    
type="System.Web.Mvc.MvcHttpHandler, System.Web.Mvc, Version=2.0.0.0, 
Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
</httpHandlers>

Add entry to the httpModules section

<httpModules>
<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing,
 Version=3.5.0.0, Culture=neutral, 
PublicKeyToken=31BF3856AD364E35" />
</httpModules>

Add entry to the compilation/assemblies section

<compilation batch="false" debug="false">
 <assemblies> 
  <add assembly="System.Web.Mvc, Version=2.0.0.0, Culture=neutral, 
   PublicKeyToken=31BF3856AD364E35" />
  </assemblies>
</compilation>

Configuring the ASP.NET MVC 2 site web.config

Since we have configured Routing in the Sharepoint Publishing Site web.config, we need to remove this from the ASP.NET MVC site web.config

Remove the UrlRoutingModule entry

 

<httpModules>
<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, 
 System.Web.Routing, Version=3.5.0.0, Culture=neutral, 
 PublicKeyToken=31BF3856AD364E35"/>
</httpModules>

Remove the following ‘add, remove’ entries from system.webserver/modules

 

<system.webServer>
  <modules runAllManagedModulesForAllRequests="true">
<remove name="UrlRoutingModule"/>
<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, 
System.Web.Routing, Version=3.5.0.0, 
Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
  </modules>
</system.webServer>

Remove the following ‘add, remove’ entries from system.webserver/handlers

 

<system.webServer>
 <handlers>
<remove name="UrlRoutingHandler"/>
<add name="UrlRoutingHandler" preCondition="integratedMode" verb="*" 
    path="UrlRouting.axd" type="System.Web.HttpForbiddenHandler, 
    System.Web, Version=2.0.0.0, Culture=neutral, 
    PublicKeyToken=b03f5f7f11d50a3a"/>
 </handlers>
</system.webServer>
Creating the custom HttpApplication

Now that we have configured the Sharepoint Publishing Site to use the Routing Module, it now requires a ‘RegisterRoutes’ method call within the ‘Application_Start’ event.

 

To do this we need to create a custom http application class that inherits Microsoft.SharePoint.ApplicationRuntime.SPHttpApplication.

 

For convenience we can create this class within the MVC project. The following code shows an example of this

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Routing;
using System.Diagnostics.CodeAnalysis;
 
namespace SampleMVC
{
public class CustomHttpApplication : 
Microsoft.SharePoint.ApplicationRuntime.SPHttpApplication
    {
 
[SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers")]
       protected virtual void Application_Start(object sender, 
            EventArgs e)
       {
//We have to call this empty method otherwise we get a 
// System.ArgumentException: virtualPath
//If we inlcude System.Web.Routing, then it expects the following 
//call
            RegisterRoutes(RouteTable.Routes);
 
        }
 
        public static void RegisterRoutes(RouteCollection routes)
        {
            //this method needs to be here.
            //actual routing is configured within the MVC site
        }
 
    }
}

We need to sign the ASP.NET MVC web application assembly and GAC deploy, this allows its usage by the Sharepoint Publishing Site.

Configuring the Sharepoint site Global.asax

We need to amend the global.asax file of the Sharepoint Publishing Site to reference this new custom HttpApplication

 

Replace the default code

<%@ Assembly Name="Microsoft.SharePoint"%>
<%@ Application Language="C#" Inherits="Microsoft.SharePoint.ApplicationRuntime.SPHttpApplication" %>

With the following code

<%@ Assembly Name="Microsoft.SharePoint"%>
<%@ Assembly Name="SampleMVC, Version=1.0.0.0, Culture=neutral, 
PublicKeyToken=996c39a8508e1e46" %>
<%@ Application Language="C#" Inherits="SampleMVC.CustomHttpApplication" %>

Using SPContext within the ASP.NET MVC 2 application

 

Let’s go and access the Sharepoint application domain from our MVC site. In this example we amend the default HomeController to get the Sharepoint Publishing Site url property

[HandleError]
public class HomeController : Controller
{
    public ActionResult Index()
    {
            ViewData["Message"= "Welcome to Sharepoint MVC," 
            + "Sharepoint site   url : " 
            + SPContext.Current.Site.Url;
       
            return View();
    }
}

Since, by default, we are running this request under the built in anonymous user, IUSR_[machinename], we need to ensure we have allowed anonymous access within the Sharepoint Publishing Site.

 

If we request the home page, we can see that we have successfully retrieved the site name from the Sharepoint SPContext object

In this example every request made under http://samplemvc/MVC is routed by the MVC framework. We are still support the standard Sharepoint Publishing Site too, so http://samplemvc/Pages/default.aspx still resolves to

Using the ASP.NET MVC 2 framework in this way, unlike conventional Sharepoint Publishing web development, we can amend the View files at run time inside Visual Studio. No re-compilation / application pool reset is needed, a simple page refresh will render the new View.

 

Debugging of the ASP.NET MVC site is done by attaching the Visual Studio debugger to the Sharepoint w3wp.exe process.

Mapping the data model

We can now run an ASP.NET MVC 2 web application directly underneath a Sharepoint  Publishing Site. So how do we display the Sharepoint Published content?

 

I am going to use a real world example here, www.TheMedicineCabinet.co.uk. (Copyright SSL International plc)

 

The Medicine Cabinet web site is a standard Sharepoint Publishing Site but it uses an ASP.NET MVC 2 web application as its presentation layer.

 

Unlike our previous example, this site has been created with a Virtual Directory named ‘Pages’. This allows the urls to have the following format :

 

http://www.themedicinecabinet.co.uk/Pages/{pagename}

 

All the HTML within this site is rendered using standard ASP.NET MVC 2 Masterpages and Views, running conventional ASP.NET MVC code.

 

Each of the textual elements and images are content managed within the Sharepoint Publishing Site. Here is this associated Sharepoint Publishing content managed page.

As you can see there is nothing special about the Sharepoint Publishing Page, it is managed in the conventional way and uses standard Sharepoint Publishing controls to allow content authoring

 

So how do we present this data within ASP.NET MVC Views?

 

To lever the ASP.NET MVC framework we want to be able to express our data in a well defined View Model. This View Model may well contain hierarchical data as well as complex types.

Below is the View Model for this sites home page

    public class HomePageViewModel : BasePageViewModel
    {
        public ENT_Image MainImage { get; set; }
 
        public string MainTitle { get; set; }
 
        public string Paragraph_01 { get; set; }
 
        public string Paragraph_02 { get; set; }
 
        public List<ENT_Image> CrossSellImageList { get; set; }
    }

We need to bind this View Model to the site columns within Sharepoint. Here are some of the Sharepoint site columns created to support the required content

 

In our example of the Home page View Model, we have the following element

public List<ENT_Image> CrossSellImageList { get; set; }

 

This property is a list of Image objects. The ENT_Image class is defined within in the ASP.NET MVC site. This happens to be the MVC application domain representation of a Sharepoint Publishing Image field.

 

The View Model makes use of the rich collection objects available within the .NET framework (in this case Generic List). The concept of collections does not really exist within the Sharepoint column schema. For this reason we have had to statically define a fixed number of Image columns

 

In this instance it was know that only 4 images were required for each page, a 5th one was created as a contingency. This can be seen as a compromise, if more are required in the future then there is a requirement to change the Sharepoint content type.

Retrieving Sharepoint content

So we have our well defined View Model, and a bunch of Sharepoint columns, how do we bind the two data models together ?

 

Let’s start with the following page request to our ASP.NET MVC site

 

http://www.themedicinecabinet.co.uk/Pages/Home

 

As this is a standard ASP.NET MVC site, the page name is resolved using the following route mapping.

 

routes.MapRoute(
   "Page",
   "{pageName}",
    new { controller = "Page", action = "Index", pageName = "home" }  
 );

 

 

So this route will give us a pageName of ‘Home’

We can now lookup this physical page in our Sharepoint Publishing Site, in this case Home.aspx. Since Home.aspx is a Sharepoint Publishing page contained within the pages document library of our site, we can retrieve this using the Sharepoint object model.

 

Since we have exposed the SPContext object within our ASP.NET MVC site, we can now use this to access the authored content. The following piece of code within the Home Page Controller retrieves this Sharepoint page. (In this example our content is held in the en-gb variation web)

using (SPSite site = new SPSite(SPContext.Current.Site.Url, 
      superUserToken))
{
   using (SPWeb web = site.OpenWeb("en-gb"))
   {
      SPFile file = web.GetFile(web.ServerRelativeUrl + "/pages/" + 
      pageName + ".aspx");
 
      //retrieve field values from the file.Item object  
   }
}

The SPFile.Item property gives us the SPListItem object, this holds the values for the page content.

 

Binding the View Model

Now that we have the SPListItem object associated with the Sharepoint page we need to write some custom .NET code to bind our View Model to our Sharepoint data model.

 

The following code gets the SPField named ‘SSL_Image_01’ from the SPListItem

 

ImageFieldValue image = (ImageFieldValue)listItem["SSL_Image_01"];

 

 

It can be a bit tedious fetching each value by name. In this instance all the custom columns have been create with the prefix, “SSL_” to distinguish the custom columns from all other columns. This allows these values to be retrieved dynamical from the field schema xml of the SPListItem.

 

The following code snippet shows how a list of custom field names can be retrieved dynamically based on a query built around the prefix.

  private List<string> GetContentManagedFieldNames
      (SPListItem listItem)
  {
      List<string> ret = new List<string>();
 
      XmlDocument aDoc = new XmlDocument();
 
      aDoc.LoadXml(listItem.Fields.SchemaXml);
 
      IQueryable<XmlElement> fieldElements =
              
      Queryable.AsQueryable<XmlElement>(aDoc.GetElementsByTagName("Field")
            .Cast<XmlElement>()).Where(e =>  
            e.Attributes["Name"].Value.StartsWith("SSL_"));
 
 
      fieldElements.ToList().ForEach(x => 
      ret.Add(x.Attributes["Name"].Value));
 
     ret = ret.OrderBy(x => x).ToList();
 
     return ret;
  }

This allows the authoring content type to be extended in Sharepoint without the need to update the ASP.NET MVC application code.

 

It is no stretch of the imagination to extend this methodology by decoration of our View Model classes with the name of the associated Sharepoint SPField name. This would facilitate the automatic binding of View Model objects to Sharepoint fields.

 

We can now iterate through our list of Sharepoint field names and bind to our View Model.

Data conversion

Binding to our View Model objects is pretty straight forward for primitive types, the field values can be retrieved using the SPListItem.GetFormattedValue function, for example

 

For complex types, ie the ENT_Image type in our example, converters can be created. The following code snippet shows the conversion between a Sharepoint Image type and the View Model image type, ENT_Image

public static ENT_Image Convert(ImageFieldValue image)
{
  ENT_Image ret = new ENT_Image();
 
  if (image == null)
    return ret;
 
  ret.ImageUrl = image.ImageUrl;
 
  ret.Hypelink = image.Hyperlink;
 
  ret.OpenInNewWindow = image.OpenHyperlinkInNewWindow;
 
  ret.Title = image.AlternateText;
 
  return ret;
}

Depending on the size and complexity of the site, this can may not be trivial process. But once we have all the data bound to our View Model we can now exploit all benefits of using MVC.

 

Now that we have data bound our HomePageViewModel, we can now compose our View in the same way as any other ASP.NET MVC 2 site

Navigation

In our example navigation within the ASP.NET MVC site is driven directly from the structure of the Sharepoint Publishing Site. It also reflects the Sharepoint Publishing Site navigation settings.

 

To access this within our ASP.NET MVC site we need to reference the following namespace

 

using Microsoft.SharePoint.Publishing.Navigation;

 

Then we can get reference to the Sharepoint Publishing SiteMapNode.

 

PortalSiteMapProvider siteMapProvider = new PortalSiteMapProvider();
 
SiteMapNode siteMapNode = siteMapProvider.FindSiteMapNode("en-gb", 
      SPContext.Current.Site.RootWeb);

 

In this fashion it is easy to get the SiteMapNode object from the SPContext object. We can now iterate through each node to construct our navigation.

Conclusion

As you can see it is possible to develop an ASP.NET MVC site within Sharepoint. Although this is by no means an exclusive approach, it does provide a robust, proven methodology.

This approach is dependant on developers willing to invest in the custom code required to bind the View Model data.  Once done all the benefits of the MVC methodology can be exploited.

Links

www.themedicinecabinet.co.uk      

www.asp.net/mvc

References

Medicine Cabinet used as an example site with the kind permission of SSL International plc.

 

 


Product Spotlight
Product Spotlight 

©Copyright 1998-2021 ASPAlliance.com  |  Page Processed at 2021-10-20 11:31:11 AM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search