Как сохранить контекст 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);