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.