I'm building a small application in MVC 4.5. I've got an Azure database, and i'm using code first with the Entity framework to set it up. The app is hosted on my development sharepoint area.
The Home controller's Index()
Action has the [SharePointContextFilter]
and loads, among other things, the username of the logged in user. When the application is debugged and this first action runs, the Sharepoint {StandardTokens}
get appended to the url, so SPHostUrl
and AppWebUrl
and a few other variables get added to the query string.
If i navigate away to an action without the [SharePointContextFilter]
it works fine, until i navigate back to the action with the [SharePointContextFilter]
. Then i get an error saying:
Unknown User
Unable to determine your identity. Please try again by launching the app installed on your site.
I assume this is because a few of the Sharepoint {StandardTokens}
are missing, because if i manually append them to the link like so:
@Url.Action("Index", "Home", new { SPHostUrl = SharePointContext.GetSPHostUrl(HttpContext.Current.Request).AbsoluteUri })
and mark the other action with the [SharePointContextFilter]
as well, it still works.
Hovever this seems like a needlessly complex way to solve this problem. I don't want to mark every single action in my app with the [SharePointContextFilter]
, and manually insert the {StandardTokens}
into the query string for every link i create. Shouldn't it be possible to save this information to session or a cookie in some way, so i don't have to do this?
For reference, here is some code:
HomeController.Index(), the first Action that is run.
[SharePointContextFilter]
public ActionResult Index()
{
User spUser = null;
var spContext = SharePointContextProvider.Current.GetSharePointContext(HttpContext);
using (var clientContext = spContext.CreateUserClientContextForSPHost())
{
if (clientContext != null)
{
spUser = clientContext.Web.CurrentUser;
clientContext.Load(spUser, user => user.Title);
clientContext.ExecuteQuery();
ViewBag.UserName = spUser.Title;
}
}
return View();
}
Here is the [SharePointContextFilter]
attribute (generated by visual studio):
/// <summary>
/// SharePoint action filter attribute.
/// </summary>
public class SharePointContextFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
SharePointContext currentContext = SharePointContextProvider.Current.GetSharePointContext(filterContext.HttpContext);
Uri redirectUrl;
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 links that i use. From the _Layout.cshtml file.:
<li id="Home"><a href="@Url.Action("Index", "Home", new { SPHostUrl = SharePointContext.GetSPHostUrl(HttpContext.Current.Request).AbsoluteUri })">Home</a></li>
<li id="Contract"><a href="@Url.Action("Index", "Contract", new { SPHostUrl = SharePointContext.GetSPHostUrl(HttpContext.Current.Request).AbsoluteUri })">Avrop</a></li>
If i try to use these links from an Action that isn't marked with the [SharePointContextFilter]
filter, the SPHostUrl
isn't found. If i try to link to an Action which is marked with the [SharePointContextFilter]
filter, i get the aforementioned error if the SPHostUrl
isn't included.
This basically creates a situation where i can navigate away from the filtered actions, but then i can never return to them.
I hope this was clear enough.
The Model-View-Controller (MVC) architectural pattern separates an application into three main components: the model, the view, and the controller. The ASP.NET MVC framework provides an alternative to the ASP.NET Web Forms pattern for creating MVC-based Web applications.
Another option would be to comment out the validation of the SPHostUrl in the SharePointContext class at the two places shown below. It works fine without it and voids the need for passing around QueryString parameters as it will just pull it from the Session state.
Location 1 is in public SharePointContext GetSharePointContext(HttpContextBase httpContext):
/// <summary>
/// Gets a SharePointContext instance associated with the specified HTTP context.
/// </summary>
/// <param name="httpContext">The HTTP context.</param>
/// <returns>The SharePointContext instance. Returns <c>null</c> if not found and a new instance can't be created.</returns>
public SharePointContext GetSharePointContext(HttpContextBase httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException("httpContext");
}
// Commented out to allow it to work without the SPHostUrl being passed around
//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;
}
Location 2 is in protected override bool ValidateSharePointContext(SharePointContext spContext, HttpContextBase httpContext):
protected override bool ValidateSharePointContext(SharePointContext spContext, HttpContextBase httpContext)
{
SharePointAcsContext spAcsContext = spContext as SharePointAcsContext;
if (spAcsContext != null)
{
// Commented out to allow it to work without the SPHostUrl being passed around
//Uri spHostUrl = SharePointContext.GetSPHostUrl(httpContext.Request);
string contextToken = TokenHelper.GetContextTokenFromRequest(httpContext.Request);
HttpCookie spCacheKeyCookie = httpContext.Request.Cookies[SPCacheKeyKey];
string spCacheKey = spCacheKeyCookie != null ? spCacheKeyCookie.Value : null;
// Commented out to allow it to work without the SPHostUrl being passed around
//return spHostUrl == spAcsContext.SPHostUrl &&
return !string.IsNullOrEmpty(spAcsContext.CacheKey) &&
spCacheKey == spAcsContext.CacheKey &&
!string.IsNullOrEmpty(spAcsContext.ContextToken) &&
(string.IsNullOrEmpty(contextToken) || contextToken == spAcsContext.ContextToken);
}
return false;
}
Make sure the landing page of your App which will receive the initial request with the SPAppToken variable in the HTTP Post calls SharePointContext so the session variable will be created. This can be done by adding the following attribute either above your MVC Controller class or above your MVC Action method:
[SharePointContextFilter]
Or instead calling the following line of code from the MVC Controller:
SharePointContextProvider.Current.GetSharePointContext(HttpContext);
We had the same problem - ASP.NET MVC 4.5. There are two things that worked for us:
1) There is a spcontext.js file (included in the solution - you just have to add a reference to it) that will automatically append the tokens to the URL for you. However, we were given a requirement that the URL must look "nice," so we went with option 2..
2) Put the context in the session. Have the filter first look to see if you have the context in your session, and if it's there, then use that. If not, try the query string and put the retrieved context in your session. This means that you originally have to access your site with the tokens attached to your url string, and it also means that your context will be in the session for however long that's alive - so you have to decide if that's ok.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With