Как сохранить контекст Sharepoint при перемещении приложения ASP.NET MVC без использования строки запроса?
Я создаю небольшое приложение в MVC 4.5. У меня есть база данных Azure, и я сначала использую код с инфраструктурой Entity, чтобы настроить ее. Приложение размещено в моей области sharepoint.
Домашний контроллер Index()
Действие имеет [SharePointContextFilter]
и загружает, помимо прочего, имя пользователя зарегистрированного пользователя. Когда приложение отлаживается и выполняется это первое действие, Sharepoint {StandardTokens}
присоединяется к URL-адресу, поэтому <SPHostUrl
и AppWebUrl
и несколько других переменных добавляются в строку запроса.
Если я перейду к действию без [SharePointContextFilter]
, он отлично работает, пока я не вернусь к действию с помощью [SharePointContextFilter]
. Затем я получаю сообщение об ошибке:
Unknown User
Unable to determine your identity. Please try again by launching the app installed on your site.
Я предполагаю, что это связано с тем, что некоторые из Sharepoint {StandardTokens}
отсутствуют, потому что если я вручную добавлю их к ссылке, например:
@Url.Action("Index", "Home", new { SPHostUrl = SharePointContext.GetSPHostUrl(HttpContext.Current.Request).AbsoluteUri })
и отметьте другое действие с помощью [SharePointContextFilter]
, оно все равно работает.
Похоже, это кажется ненужным сложным способом решения этой проблемы. Я не хочу отмечать каждое действие в своем приложении с помощью [SharePointContextFilter]
и вручную вставлять {StandardTokens}
в строку запроса для каждой создаваемой ссылки. Нельзя ли сохранить эту информацию в сеансе или в файле cookie каким-то образом, поэтому мне не нужно это делать?
Для справки, вот какой-то код:
HomeController.Index(), первое действие, которое выполняется.
[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();
}
Вот атрибут [SharePointContextFilter]
(созданный визуальной студией):
/// <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;
}
}
}
Ссылки, которые я использую. Из файла _Layout.cshtml.:
<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>
Если я попытаюсь использовать эти ссылки из действия, которое не отмечено фильтром [SharePointContextFilter]
, SPHostUrl
не найден. Если я попытаюсь ссылаться на действие, которое помечено фильтром [SharePointContextFilter]
, я получаю вышеупомянутую ошибку, если SPHostUrl
не включен.
В основном это создает ситуацию, когда я могу перейти от фильтрованных действий, но потом я никогда не смогу вернуться к ним.
Надеюсь, это было достаточно ясно.
Ответы
Ответ 1
У нас была та же проблема - ASP.NET MVC 4.5. Для нас есть две вещи:
1) Файл spcontext.js(включен в решение - вам просто нужно добавить ссылку на него), который автоматически добавит токены к URL-адресу для вас. Однако нам было предъявлено требование, чтобы URL-адрес выглядел "хорошо", поэтому мы пошли с опцией 2..
2) Поместите контекст в сеанс. Сначала попросите фильтр посмотреть, есть ли у вас контекст в вашем сеансе, а если он есть, используйте его. Если нет, попробуйте строку запроса и поместите извлеченный контекст в свой сеанс. Это означает, что вам изначально приходится обращаться к вашему сайту с помощью токенов, прикрепленных к вашей строке url, и это также означает, что ваш контекст будет в сеансе, если он еще жив, поэтому вам нужно решить, хорошо ли это.
Ответ 2
Другой вариант заключается в том, чтобы прокомментировать проверку SPHostUrl в классе SharePointContext в двух местах, указанных ниже. Он отлично работает без него и исключает необходимость прохождения параметров QueryString, поскольку он просто вытащит его из состояния сеанса.
Местоположение 1 находится в открытом доступе 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;
}
Местоположение 2 находится в защищенном переопределении 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;
}
Убедитесь, что целевая страница вашего приложения, которая получит первоначальный запрос с переменной SPAppToken в HTTP Post, вызывает SharePointContext, поэтому будет создана переменная сеанса. Это можно сделать, добавив следующий атрибут либо выше вашего класса MVC Controller, либо выше вашего метода MVC Action:
[SharePointContextFilter]
Или вместо этого вызывать следующую строку кода из MVC-контроллера:
SharePointContextProvider.Current.GetSharePointContext(HttpContext);