8619 Views // 0 Comments // Not Rated

SharePoint 2013 Web Parts And The App Model, Part 2: The MVC "Server" Architecture

Welcome to part two of this SharePoint 2013 web part saga. In this post, I'm going to get into the details behind the "server" portion of the overall solution. Revisit part one for the back ground, or skip ahead to part three for the rest of the architecture. And if you're as weirdly excited about deployments as I am, check out part four.

Part one finished up with a discussion about the server object model no longer being in vogue for SharePoint 2013 development. So since our web parts aren't talking directly to the server anymore, let's take a look at the architecture needed to support them.

The public-facing sites from part one use SharePoint publishing for the most of the content, and a high trust, server-to-server provider hosted app for data access and to serve any pages whose functionality doesn't require SharePoint. The app is MVC 4, using the new Web API to support our web parts, and standard controllers for everything else.

Here's what it looks like. Start -> Apps -> mspaint.exe

Web Part Architecture

First, on the left we have our MVC app. When you create an "App for SharePoint 2013" project in Visual Studio, it inexplicitly dumps an ASP.NET web application project on you for the app bits. I always blow this away and create a new MVC project and associate it with the SharePoint app. To do this, just manually delete the ASP.NET project, add an MVC one, highlight the SharePoint project in Solution Explorer, hit F4, and select the MVC project in the "Web Project" dropdown at the bottom of the properties. (I'll go into much more detail about how to configure your development environment for SharePoint apps in an upcoming post.)

Associating Web Project With App Project

When you perform this association, Visual Studio "app-inizes" your MVC project, adding CSOM references and generating a large static class named "TokenHelper." TokenHelper basically does the work of using a certificate to allow our MVC project to talk to SharePoint anonymously; in other words, it handles the "high trust server-to-server (S2S)" implementation for provider hosted apps. Part of this middle layer is the CacheMonster, which I blogged about previously.

The real meat here is the set of Web API controllers which provide REST "services" to our web parts over in SharePoint. I only put the word services in quotes out of homage for WCF, which, now that I've seen how easy it is to get the MVC Web API stuff working with CSOM, will be quickly departing from my life. RIP WCF...you'll be reunited with Silverlight soon in technology heaven. Or, actually, you'll probably go to hell because you are evil.

The way it works is that each web part basically has its own controller. You might be able to normalize this out of a bit, but remember that these controllers are our "code behind" for the web parts. That's why we're going to have a lot of small Web API controllers verses a more conventional service layer consisting of a few WCF services, each with many operation contracts.

Following the Web API pattern, all of these controllers will have a Get method that the web parts will be consuming. Each Get takes in two strings: a "page" (the SharePoint URL of the page hosting the web part) and an "args" (the raw serialized web part properties). Some controllers will also have a Post that is called via PowerShell for deployment purposes. A good example of this is navigation. A NavigationController (for example) would have a Post that creates taxonomy terms, and a Get that consumes and caches them before dynamically building the HTML for something like a mega nav that's beyond the out-of-the-box capabilities. More on deployment later; let's take a look at a controller now.

Code Listing 1

  1. public class NavigationController : SPController
  2. {
  3. [SPContext()]
  4. public WebPartModel Get(string page, string args)
  5. {
  6. //initialization
  7. WebPartModel response = new WebPartModel();
  8. try
  9. {
  10. //deserialize args into web part properties
  11. WebPartArgsModel webPartProperties = this.GetModel(args);
  12. //get model to send back to web part
  13. response.Model = this.GetDataAsModel(page, webPartProperties);
  14. response.HTML = this.GetDataAsHTMLBlob(page, webPartProperties);
  15. }
  16. catch (Exception ex)
  17. {
  18. //error
  19. response.Error = ex.ToString();
  20. }
  21. //return
  22. return response;
  23. }
  24. [WebApiKey()]
  25. public void Post(string hostUrl)
  26. {
  27. try
  28. {
  29. //open context
  30. using (ClientContext context = this.GetContext(hostUrl))
  31. {
  32. //check context
  33. if (context != null)
  34. {
  35. //create data
  36. }
  37. }
  38. }
  39. catch (Exception ex)
  40. {
  41. //error handling
  42. }
  43. }
  44. }

There's a whole bunch going here. First, in Line #1, you'll see that we don't inherit directly from ApiController, but instead from a base class called "SPController." This base controller wraps some of the calls to TokenHelper and performs other "utility" functions common to our SharePoint data calls. For example, in Line #11, GetModel is on SPController, and has the logic to convert the serialized web part properties from the client into a more manageable object. It can also get a ClientContext from a specific page's URL, verses that of a web or site collection.

All of our Gets (check out Line #3) are decorated with a filter I couldn't help but name "SPContext." Since CSOM needs to know the SharePoint URL it's trying to communicate with, we need to ensure that it's passed to us via query string. That's SPContext's job. This is the kind of thing I love about MVC: it's so beautifully modular: base controllers for some tasks and custom action filters for others.

Next, on Line #7, we new up a WebPartModel. This is a POCO class, a standard MVC model, that frames our response to the client. It has a property of type Object that Line #13 sets to an instance of another model that's specific to this particular controller. It also has an "Error" (see Line #19) string property (so the client knows if CSOM barfed up in MVC) and an "HTML" string property so that you can just send a blob of markup back to the client verses a model. This is on Line #14; if you set both, the model binding will take precedence.

The Post starts out with a different action filter: WebApiKey. This is a quick and dirty way to lock down data-creating functionality on public-facing sites: force the caller to send us a particular guid (that we keep safe in the web.config file). This is baked into the PowerShell script that the deployment uses to populate our lists and libraries. There are certainly more sophisticated ways to achieve this security, but it works fine for these purposes.

The aforementioned PowerShell also passed in the URL of the site collection for which we'll be creating data as the "hostUrl" parameter. That's all we need to new up a ClientContext and go to town on populating a pristine site. The only egg in this basket I haven't been able to crack is conceiving a good way to report exceptions. If we throw, we'll get blood back in PowerShell in the form of a generic HTTP 500 error from the HttpWebRequest it uses to post to our Web API. If we swallow, we have a false positive. So until I can figure out how (or if) I can talk to ULS from MVC, it's custom-log-and-throw when CSOM fails.

Let's take a look at our base controller and models real quick before heading across the wire to the web part architecture in part three. The following listing shows the contents of our "SPController" which inherits from ApiController. As a base MVC controller, we'll mainly only find helper functions that could be relevant to any request in the application, not actions. I implement these as protected methods.

Code Listing 2

  1. protected string GetAccessToken(string hostUrl)
  2. {
  3. //return
  4. return TokenHelper.GetS2SAccessTokenWithWindowsIdentity(hostUrl.GetHostUrl(), null);
  5. }
  6. protected ClientContext GetContext(string hostUrl, string accessToken)
  7. {
  8. //return
  9. return TokenHelper.GetClientContextWithAccessToken(hostUrl.GetHostUrl().ToString(), accessToken);
  10. }
  11. protected ClientContext GetContext(string hostUrl)
  12. {
  13. //initialization
  14. string url = hostUrl.GetHostUrl().ToString();
  15. //return
  16. return TokenHelper.GetClientContextWithAccessToken(url, this.GetAccessToken(url));
  17. }
  18. protected WebPartArgsModel GetModel(string args)
  19. {
  20. //initialization
  21. WebPartArgsModel model = new WebPartArgsModel();
  22. //parse args
  23. if (!string.IsNullOrWhiteSpace(args))
  24. {
  25. //serialize args
  26. using (MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(args)))
  27. {
  28. //build model
  29. DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(WebPartArgsModel));
  30. model = (WebPartArgsModel)serializer.ReadObject(ms);
  31. }
  32. }
  33. //return
  34. return model;
  35. }
  36. protected string SerializeModel<T>(T model)
  37. {
  38. //initialization
  39. string data = string.Empty;
  40. //get a stream
  41. using (MemoryStream ms = new MemoryStream())
  42. {
  43. //serialize model
  44. DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T));
  45. serializer.WriteObject(ms, model);
  46. //reset stream
  47. ms.Position = 0;
  48. using (StreamReader sr = new StreamReader(ms))
  49. {
  50. //write from model
  51. data = sr.ReadToEnd();
  52. }
  53. }
  54. //return
  55. return data;
  56. }

The first three methods on Line #'s 1, 6, and 11 are little wrappers around TokenHelper. These just save a few lines of code you'd have to write every time you need to call into CSOM. On Line #4 is the first usage of the "GetHostUrl" extension method (not shown) that simply returns the root of a full SharePoint page Url to feed into ClientContext. The final two methods, "GetModel" and "SerializeModel" assist in converting between proper object representations of web part properties and raw strings. Here is what that object looks like:

Code Listing 3

  1. public class WebPartArgsModel
  2. {
  3. public List<WebPartArgModel> Args { get; set; }
  4. }
  5. public class WebPartArgModel
  6. {
  7. public string Name { get; set; }
  8. public string Value { get; set; }
  9. }

As you can see, this is a basic POCO that lets us consume our web part properties as a list of name/value pairs, alleviating ever having to deal with them as raw, vapid strings. Since we're doing an HTTP GET to, well, get our data, we are basically limited to what can be stuffed into the query string. This forces us to have to serialize the properties on the client, ship them over the wire along with our request, and reconstitute them on the server.

This completes the MVC portion of the web part. Now it's time to take a look at the web part itself architecture itself in part three.

No Tags

No Files

No Thoughts

Your Thoughts?

You need to login with Twitter to share a Thought on this post.