Getting SharePoint 2013 apps and WebAPI to work

With the release of Visual Studio 2013, Microsoft also added a very nice MVC template for remote SharePoint Apps. This is a slightly modifed version of the default MVC5 template, which doesn’t, for example, contain an own authentication provider, but does contain all the stuff like bootstrap and the latest jquery version. Some extra helper files have been added (via a nuget package) to assist in the communication with SharePoint 2013. This does work perfectly with the Actions defined in MVC controllers, but the code doesn’t work together with ApiControllers. Yet 😉 This blogpost shows us how (roughly) the same functionality can be implemented for use with the WebApi controller! At the end, the full source code is provided, it works in azure, as well as with high trust solutions. A good read on the basics of the SharePointContextProvider can be found here and here

Note: the provided code is by no means production ready! I didn’t test it thoroughly, but the basic scenario’s do work!

Different concepts

At first, the concepts of MVC and WebAPI are different. Basically, MVC is optimized to return views as HTML, whereas the WebAPI is optimized to return structured data. Dave Ward wrote a good post on this subject. When looking at the technical aspects of MVC and WebAPI, there are some more differences, which causes the code not to work with WebAPI controllers.

MVC, APIControllers and the SharePointContextProvider

The MVC Controllers inherit from “System.Web.MVC.Controller” and provides properties like HTTP Context and the Request property, which is a HttpRequestBase. Those two properties are heavily used in the SharePoint helper classes that help creating and ensuring the SharePoint contexts. Whenever an action is being executed, the SharePointContextProviderAttribute tries to enforce a SharePoint context, which can return 3 different results:

  • The context is OK
  • The context cannot be created yet: redirect back to the portal to gather the right information. This is mostly the case when the remoteweb has been bookmarked and the page is requested via that bookmark. The user will be redirected to SharePoint to login, and after the login, the user is redirected back to SharePoint. The check will return “OK” then.
  • The context is not available and can’t be created: redirect to the error view.
switch (SharePointContextProvider.CheckRedirectionStatus(filterContext.HttpContext, out redirectUrl))
            {
                case RedirectionStatus.Ok:
                    return;
                case RedirectionStatus.ShouldRedirect:
                    filterContext.Result = new RedirectResult(redirectUrl.AbsoluteUri);
                    break;
                case RedirectionStatus.CanNotRedirect:
                    filterContext.Result = new ViewResult { ViewName = "Error" };
                    break;
            }

The function on line 1, “SharePointContextProvider.CheckRedirectionStatus” not only checks wether or not the user should be redirected, but also creates the SharePointContext, whenever this is possible. The first condition for a succesful return status (return OK or ShouldRedirect) is the presence of a SPHostUrl in the querystringparameters. Using a function called GetSPHostUrl, this parameter is retrieved from the RequestUri:

public static Uri GetSPHostUrl(HttpRequestBase httpRequest)
public static Uri GetSPHostUrl(HttpRequest httpRequest)

Whenever the context can be created, this context is stored in the HttpContext.Session. Below the HighTrustContext is listed, the implementation of the Acs variant of SaveSharePointContext is slightly different, but uses the HttpContext.Session as well.

protected override void SaveSharePointContext(SharePointContext spContext, HttpContextBase httpContext)
        {
            httpContext.Session[SPContextKey] = spContext as SharePointHighTrustContext;
        }

ApiController

The ApiController however, inherits from “System.Web.Http.ApiController“. This is a quite different class compared to the System.Web.MVC.Controller. It doesn’t contain a HttpContext, but a HttpControllerContext, which doesn’t, for example, contain a Session. This isn’t a surprise: ApiControllers are, by default, stateless. The Request and RequestContext properties are respectively of type HttpRequestMessage and HttpRequestMessageContext. It doesn’t take much to see that Controller and ApiController are complete different classes and due to the fact that the ApiController doesn’t have a httpContext and thus, no session, there is no way to use the provided helper classes with the ApiController.

The Solutions

To be able to use the WebAPI easily within SharePoint 2013 apps, and keeping in mind that there was no way to map the ApiController properties in such a way that they would fit into the Controller properties, two solutions came accross

  • Adding a session to the API Controller and add some code to the helper classes
  • Keep the API “pure” and add a helper class

Adding a session to the API Controller

Adding a session to the API Controller is a solution and can provide some interesting possibilities, for example when the WebAPI is supportive to the MVC application and needs to share a Session with the MVC part of your solution. In situations like Single Page Applications, this is not always a viable solution. Information on how to add a session to a ApiController, can be found here. As this solution doesn’t work with the default behaviour of ApiControllers, this wasn’t a solution for me.

Keeping the WebAPI “pure” and add a helper class

Another solution was to “copy” the SharePoint provider logic, and modify this logic in such a way, that it would fit the ApiController. An additional benefit is that is runs in conjunction with the helperclasses: there is one downside: the helperclasses provided by microsoft (via nuget), need to be altered a bit.

SharePointContextFilterAttribute to use with the WebAPI

At first, a new SharePointContextFilterAttribute needs to be created, I chose “SharePointApiControllerContextFilter”. It inherits from “System.Web.Http.Filters.ActionFilterAttribute”, which is designed to work with ApiControllers:

public class SharePointApiControllerContextFilterAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
        {
            if (actionContext == null)
            {
                throw new ArgumentNullException("actionContext");
            }

            Uri redirectUrl;
            switch (SharePointApiControllerContextProvider.CheckContextStatus(actionContext.ControllerContext, out redirectUrl))
            {
                case ContextStatus.Ok:
                    return;
                case ContextStatus.NotOk:
                    actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.MethodNotAllowed, "Context couldn't be created: access denied");
                    break;
            }
        }
    }

On lines “13 and 15” we see that 2 results are handled: Ok and NotOK. As the controller just provides data (or no data), it shouldn’t take care of redirects. The app that makes use of the ApiController needs to handle possible errors and take action on that event. In case of the MVC application, it already is an application, so it makes sense to handle the redirect logic in the MVCFilterAttribute.

On Line “11”, the redirection logic has been substituted with “CheckContextStatus”, to reflect the action it executes.

Altering the SharePointContextProvider to support ApiControllers

The next step, was a bit harder, although it wasn’t too hard. The SharePointContext and SharePointContextProvider needed to be migrated to support the WebAPI. This basically came down to:

  • Remove session related code
  • Replace the use HttpRequestBase with another class
  • Replace the use of the HttpContext with HttpControllerContext
  • Make sure that the AppContextToken can be retrieved by the ApiController

I choose to put the logic into a new class, because the signature of some methods needed to be changed. I didn’t want to change the code too much: when microsoft updates the nugetpackages, it’s easier to add the modifications again.

The original class diagram is as follows:

After copying and modifying the code, my class diagram still preserves the same structure. Please take notice of absence of several methods in the SharePointApiControllerContentextProvider:

As the ApiController is stateless by default, I decided to remove the Load, Save and validation methods, the context is created over and over again, on each request. This process could be optimized, but hey, it’s just a proof of concept ;). Eventually, all came down to Creating the SharePoint context.

As the High Trust solution was the easiest to implement, I will start with that one 😉

High Trust Solutions

The Creation of the SharePoint context for full trust solutions was quite easy: As this form of remote apps works on a Server to Server trust (with certificates), all that is needed is an identity. I used the code provided by Microsoft as the basis to start with:

protected override SharePointContext CreateSharePointContext(Uri spHostUrl, Uri spAppWebUrl, string spLanguage, string spClientTag, string spProductNumber, HttpRequestBase httpRequest)
        {
            WindowsIdentity logonUserIdentity = httpRequest.LogonUserIdentity;
            if (logonUserIdentity == null || !logonUserIdentity.IsAuthenticated || logonUserIdentity.IsGuest || logonUserIdentity.User == null)
            {
                return null;
            }

            return new SharePointHighTrustContext(spHostUrl, spAppWebUrl, spLanguage, spClientTag, spProductNumber, logonUserIdentity);
        }

In this case, it all came down to replace the HttpRequestBase by an available property in the ControllerContext, in which the WindowsIdentity property was available. I ended up using the RequestContext, which was available in the ControllerContext:

protected override SharePointApiControllerContext CreateSharePointContext(Uri spHostUrl, Uri spAppWebUrl, string spLanguage, string spClientTag,
            string spProductNumber, HttpControllerContext httpControllerContext)
        {
            WindowsIdentity logonUserIdentity = (WindowsIdentity)httpControllerContext.RequestContext.Principal.Identity;
            if (logonUserIdentity == null || !logonUserIdentity.IsAuthenticated || logonUserIdentity.IsGuest || logonUserIdentity.User == null)
            {
                return null;
            }

            return new SharePointApiControllerHighTrustContext(spHostUrl, spAppWebUrl, spLanguage, spClientTag, spProductNumber, logonUserIdentity);

        }

That was all that was needed for the High Trust apps!

Creating the ACS Context Provider

Creating the SharePoint context in a application that makes use of the Azure Access Control Services, is a total different story.

The CreateSharePointContext is implemented as follows:

protected override SharePointContext CreateSharePointContext(Uri spHostUrl, Uri spAppWebUrl, string spLanguage, string spClientTag, string spProductNumber, HttpRequestBase httpRequest)
        {
            string contextTokenString = TokenHelper.GetContextTokenFromRequest(httpRequest);

            if (string.IsNullOrEmpty(contextTokenString))
            {
                return null;
            }

            //…

            return new SharePointAcsContext(spHostUrl, spAppWebUrl, spLanguage, spClientTag, spProductNumber, contextTokenString, contextToken);
        }

The problem is in the method GetContextTokenFromRequest in the TokenHelper class:

public static string GetContextTokenFromRequest(HttpRequestBase request)
        {
            string[] paramNames = { "AppContext", "AppContextToken", "AccessToken", "SPAppToken" };
            foreach (string paramName in paramNames)
            {
                if (!string.IsNullOrEmpty(request.Form[paramName]))
                {
                    return request.Form[paramName];
                }
                if (!string.IsNullOrEmpty(request.QueryString[paramName]))
                {
                    return request.QueryString[paramName];
                }
            }
            return null;
        }

Within this method, the application context token is retrieved, from the request form parameters or from the querystring . After some fiddling around (or however it is called when using fiddler), It came out that after startup of an application, the app context token is send as a form parameter of the request. According to MSDN, it’s not possible to provide the AppContextToken as a startup parameter in the querystring. As its not possible to get the original post parameters via javascript, I had to find another solution.

It turned out that using cookies to provide the app context token is allowed, regarding this quote on MSDN, within the “Tips and FAQ: Oauth and remote apps for SharePoint” article:

Is it okay to keep AppContext (obtained from a SharePoint POST request) as a hidden input field on the page?
Putting the AppContext value in a hidden field or in a cookie is fine. If you have a server-side cache, you can also consider extracting the “CacheKey” from the context token, putting that in the cookie or hidden input field, and keeping the refresh token (and even the access token) and other information in a server-side cache (that way you can make this cache durable between sessions).

This is the point where the SharePointContextProvider needed a slight modification: at the moment that a SharePoint context has been created, I insert a cookie, which can be used by the ApiController:

if (spContext != null)
                {
                    string contextTokenString = TokenHelper.GetContextTokenFromRequest(httpContext.Request);
                    System.Web.HttpCookie cookie = new HttpCookie("appContextToken")
                    {
                        Value = contextTokenString,
                        Secure = true,
                        HttpOnly = true
                    };
                    httpContext.Response.AppendCookie(cookie);
                    SaveSharePointContext(spContext, httpContext);
                }

//Note: this could be solved in various ways, I just choose the “fastest” one.

I added a helper method for use within the SharePointApiControllerContextProvider to retrieve the appContextToken from the cookie that has been set in initial request. All the other effort I had, was to rewrite the CreateSharePointContext, to be able to send the HttpControllerContext as a parameter, that was all.

When a plan comes together

Due to the modification to the existing helper classes, the app context token is now stored in a cookie. Whenever a user starts the remote webapplication and he hits an action that is decorated with the SharePointContextFilterAttribute, a cookie is created with this token in it.

Whenever a webAPI call is executed, which is decorated with the SharePointApiControllerContextFilterAttribute, the code tries to create the SharePoint context: that specific cookie is needed to create this context. When the token is not available, a “MethodNotAllowed” response is created, otherwise, there is no problem to go on with the execution of the logic.

In my TestApiController, I copied the “default” logic that normally comes with the SharePoint MVC app, decorated the Action with our new filterattribute.

public class TestApiController : ApiController
    {
        [SharePointApiControllerContextFilter]
        [System.Web.Http.HttpGet]
        public string Get()
        {
            var spContext = SharePointApiControllerContextProvider.Current.GetSharePointContext(ControllerContext);

            Microsoft.SharePoint.Client.User spUser = null;
            using (var clientContext = spContext.CreateUserClientContextForSPHost())
            {
                if (clientContext != null)
                {
                    spUser = clientContext.Web.CurrentUser;

                    clientContext.Load(spUser, user => user.Title);

                    clientContext.ExecuteQuery();
                }
            }
            return "user: " + spUser.Title;
        }
    }

Within my MVC view, I added a
<div “testapiresult”>

and some javascript code:

</pre>
<h1></h1>
<pre>
@section scripts
{
<script type="text/javascript">// <![CDATA[
        $(document).ready(function () {
            $.get("https://localhost:44319/api/testapi/get" + location.search)
             .done(function (result) {
                 $('#testapiresult').text(result);
                });
        });

// ]]></script>
}

After running the code my username eventually shows up!

From  the same session, it’s possible to call the WebAPI on it’s own too:

Whenever I call the TestApiController directly from a browser that didn’t authenticate against SharePoint and didn’t create SharePointContext yet, I get a “nice” error message:

Summary

The helper classes provided by microsoft don’t work together with the WebAPI. As this is a very interesting piece of technology that can be used within Single Page Apps, I added a helper class to be able to use those ApiControllers within your application. It wasn’t too hard, just a bit of work to find out how this stuf works. My implementation is (very likely) far from perfect, but it does give a smooth start on building your own logic.

The sourcecode can be downloaded here

Happy coding!!

31 thoughts on “Getting SharePoint 2013 apps and WebAPI to work

  1. Mohamed

    Is there any way to call a controller action from JavaScript without getting access denied ?
    I’ve developed SharePoint Provider hosted App. Inside the MVC application, I have a controller action that access a list stored in the SP app. I want to access that controller action throigh javaScript but without getting “access denied” error;

    Reply
    1. Bas Lijten Post author

      Hi, excuse me for the late answer. It’s due to the fact that you are having a provider hosted app: you will need to handle to security (login) for the first time yourself

      Reply
    2. Eric

      Just wanted to reply to this in case anyone else runs across this post and into this issue. The access denied error appears to be a catch all for any issues that cause ContextResult.NotOk. Turned out (at least in my case) I was missing the URL parameters that you need to pass back to the WebApi for SharePoint context to create successfully. I just added a javascript variable with all the url parameters and appended them to Url I gave my to the ajax method. For example:

      $(function () {
      var params = document.URL.split(“?”)[1];

      function getUrl (baseUrl) {
      return baseUrl + ‘?’ + params;
      }

      $.ajax({
      url: getUrl(‘/api/TestApi’),
      type: ‘GET’,
      success: function (data) {
      $(‘#userPlaceholder’).text(data);
      },
      error: function (data) {
      alert(data.responseText);
      }
      });
      });

      Otherwise thank you for posting this! This is the only guide/source code I could find and it works well with a bit of troubleshooting.

      Reply
    3. Prakash ramasamy

      The access denied issue solved for me by appending the “location.search” in the end of the api url

      Reply
  2. Andrianarivony Léon

    Interesting post!
    I’m facing the same situation today but with a small difference to have the SharePoint and hard to get the answer.
    Web Api is used on the same SharePoint Web Application. Unfortunately, in the Web Api, there is no chance to get SPContext.Current.Site, it’s always null. Does it make sense to do so or not? my feeling is not.
    So, I try to change slightly the design, put the Web Api in simple IIS Host or Console App. Then, 2 options to make SharePoint Query (Read Only).
    Read only because we are in a public facing web site with purely anonymous access.
    So, REST or SCOM, any articles founded in google is always talking Hosted Apps or SharePoint Apps to get a OAuth bearer Token.
    I can imagine to have a HttpClient making a REST call to the SharePoint Web App (internet zone) with anonymous can’t work as long as we don’t provide a user name and password ?

    Reply
  3. Chris O'Brien (@ChrisO_Brien)

    Nice work! I looked at making similar changes to the SharePointContext and related classes recently because they aren’t serializable – and this causes issues if your session provider needs it (e.g. provider-hosted app in Azure, and you want to implement “production-level” session handling).

    Anyway, I think the idea of WebAPI stuff which talks to SharePoint will be quite common, so thanks for this work 🙂

    Reply
      1. seansageek

        Agree with Chris–excellent post and an excellent solution. I’ll be giving you a shout-out in my SPS Atlanta presentation tomorrow. Looking forward to reviewing your SPS BE materials as well.

        Reply
  4. Jordi Ruiz

    Thank you so much for the brilliant solutions. I had the same problem and I was going to modify the SharePointContext file but you have been quicker.

    Reply
  5. Kim

    Hi Bas, this is very interesting but the detail is a bit beyond me. At a high level I understand that the web api app is trusted by SharePoint and that a cookie is set to hold the user’s sharepoint context, but I’m not sure of how the web api gets hold of that information. Is the user submitting a doing a post to the web api with some hidden fields, or is all the data about the user encrypted in the query string when they get redirected?

    Reply
  6. rahul patil

    Hi Bas,

    First of all, thanks for the article. It really helped me a lot.. However I have some concerns.

    I followed exact steps to make WebAPI work with SP Context. But somehow, I always get this error –

    Context couldn’t be created : access denied. What could be the cause?

    Reply
    1. mariano

      Hi Bas,
      i find your example, that i can’t to start in debug because it return bad request in the Windows of browser, but i find complete to understand the chenges. But i have a question. I create a project with an app provider-hosted, then i create a simple standard web.api within in same project, Then i insert the following script:

      $.get(“https://localhost:44329/api/values/5″ + location.search)
      .done(function (result) {
      alert(result);
      }).fail(
      function (jqXHR, textStatus, err) {
      alert(‘Error: ‘ + err + ‘ test: ‘ + textStatus);
      });

      but in console it says :” 404 not found”, but the url i right because it equals to url of app proveder-hosted.
      Do you suggest anything?
      thx
      Mariano

      Reply
  7. Mariano

    Bas, i don’t understand about GetSharePointContext in which i have:
    public SharePointContext GetSharePointContext(HttpContextBase httpContext)
    {
    if (httpContext == null)
    {
    throw new ArgumentNullException(“httpContext”);
    }

    Uri spHostUrl = SharePointContext.GetSPHostUrl(httpContext.Request);
    if (spHostUrl == null)
    {
    return null;
    }

    SharePointContext spContext = LoadSharePointContext(httpContext);

    if (spContext == null || !ValidateSharePointContext(spContext, httpContext))
    {
    spContext = CreateSharePointContext(httpContext.Request);

    if (spContext != null)
    {
    SaveSharePointContext(spContext, httpContext);
    }
    }

    return spContext;
    }

    and about
    CheckRedirectionStatus and CheckContextStatus.

    I seem that there aren’t all things about di helper class.
    Could you explain better?

    Reply
  8. Mohamed

    Hello,
    I’m getting “Invalid issuer or signature” in

    SecurityToken securityToken = tokenHandler.ReadToken(contextTokenString);

    Any advice ?
    Thanks

    Reply
  9. Rohit Sakate

    Hi,
    I am always getting “Context couldn’t be created: access denied” because it could not load spHostUrl from query string.

    i am doing following

    – provider hosted app
    – copied your modified classes to my mvc project
    – created simple api controller

    [SharePointApiControllerContextFilter]
    [HttpPost]
    public string GetProfilesByQuery(JObject request)
    {

    var spContext = SharePointApiControllerContextProvider.Current.GetSharePointContext(ControllerContext);
    _service.SPContext = spContext;
    return “success”;

    //return Request.CreateResponse(
    // HttpStatusCode.OK, _service.GetProfilesByQuery()
    // );
    }

    – calling webapi using angular js

    bajaj.ibp.common.modules.baseapp.factory(‘Service’, [‘$http’, ‘webapiUrl’, function ($http, webapiUrl) {

    return {
    ExecuteApi: function (webApi, data, callback) {
    $http.post(webapiUrl.format(webApi), data).success(callback);
    }
    }
    }]);

    am i missing something?

    Reply
  10. Pingback: user – Sharepoint 2013 webapi | Asking

  11. Pingback: user – Sharepoint 2013 with custom webapi service | Asking

  12. Willem de Boer

    This solution worked for me. However: I had to enable cookies in the security settings of Internet Explorer for the Azure domain (azurewebsites.net). Until then I also had the “Access denied” error message in my provider hosted apps. I took me a few hours before I realized this. Maybe it’s useful for other people as well.

    Reply
  13. Harshad Bhoi

    Hi Bas Lijten,

    My scenario is little bit different than what you have explained in this article.

    My implementation involves: 1) SharePoint 2013 which has all required lists and Library. 2) ASP.NET application as high trust provider hosted App and 3) Self hosted Web API.

    What changes needs to be done in your code so that it can work in my implementation.

    Thanks

    Reply
  14. Rushi

    Thanks for this post.

    When I redirect to my Provider Hosted App from SharePoint Online, SPAppWebUrl query string parameter is missing, and hence I am not able to create the SPContext.

    In AppManifest.xml, I have already added – {StandardTokens}as query string.

    Please help.

    Reply
      1. Willem de Boer

        I had to enable cookies in the security settings of my browser. After that it worked fine

        Reply
  15. Benjamin

    I’ve built your app using SharePoint 2013 on-premises, Visual Studio 2013 Community, and IIS on Windows Server 2012 R2. Every time I run this code, I get a 405 Method Not Allowed error code, and/or I end up seeing the Error view in the browser.

    I changed the ajax call within the Index view, as mentioned in the comments above, and it made zero difference. Is there something necessary to make this code work without getting the 405 error code? Maybe some IIS settings I don’t know about yet?

    Thank you in advance.

    Reply
    1. Bas Lijten

      Hi Benjamin, I am afraid that I can’t help you. It’s a long time ago since I worked on this concept and I am not really into SharePoint/apps anymore. Sorry for that. I do remember that I (sometimes) had this error and was able to fix it. but how… I really don’t remember 🙁

      Reply
  16. Pingback: SharePoint 2013 App Models Considered - Part 4

  17. Alex usa

    Hello and thank you for this post.
    I found a small issue here – Access token is always getting renewed even if its still valid. The reason is simple – its not cached (take a look at LoadSharePointContext and SaveSharePointContext methods which are not present in SharePointApiContext). There is a way to solve it either by saving it into a cookie or Session (must be enabled for WebAPI).
    Thanks again!

    Reply

Leave a Reply