Jaa


Настраиваемый поставщик утверждений Azure для SharePoint Project, часть 3

Исходная статья опубликована в четверг, 21 февраля 2012 г.

В части 1 этой серии я кратко обозначил цели этого проекта, который на высоком уровне должен использовать хранилище таблиц Windows Azure в качестве хранилища данных для настраиваемого поставщика утверждений SharePoint. Предполагается, что поставщик утверждений будет использовать набор CASI Kit для получения нужных данных из Windows Azure, чтобы обеспечить функции разрешения имен при использовании средства выбора людей (то есть адресной книги) и элемента управления для ввода. 

В части 2 я рассмотрел все компоненты, работающие в облаке — классы данных, используемые для работы с хранилищем таблиц запросами Azure, рабочую роль для чтения элементов из запросов и заполнения хранилища таблиц, а также интерфейс WCF, позволяющий клиентскому приложению создавать новые элементы в запросе и также выполнять все стандартные действия SharePoint по поиску людей — предоставлять список поддерживаемых типов запросов, выполнять поиск значений утверждений и разрешать утверждения.

В этой, заключительной, части серии мы рассмотрим различные компоненты, используемые на стороне SharePoint. В их число входит настраиваемый компонент, созданный с помощью набора CASI Kit для добавления элементов в очередь, а также для выполнения обращений к хранилищу таблиц Azure. В ней также описывается наш настраиваемый поставщик утверждений, который будет использовать компонент набора CASI Kit для связи SharePoint с этими функциями Azure.

Для начала кратко рассмотрим настраиваемый компонент набора CASI Kit. Я не собираюсь тратить на это много времени, так как набор CASI Kit подробно проанализирован в этом блоге. Этот конкретный компонент описывается в части 3 серии, посвященной набору CASI Kit. Но, если говорить вкратце, я создал новый проект библиотеки классов Windows. Затем я добавил ссылки на сборку базового класса набора CASI Kit и другие необходимые сборки .NET (описанные в части 3). Я добавил в свой проект ссылку на службу в конечную точку WCF, созданную в части 2 этого проекта. Наконец, я добавил в проект новый класс, наследуемый от базового класса набора CASI Kit, и добавил код для переопределения метода ExecuteRequest. Как, надеюсь, вы уже видели в серии о наборе CASI Kit, вот на что похож мой код переопределения ExecuteRequest:

       public class DataSource : AzureConnect.WcfConfig

       {

              public override bool ExecuteRequest()

              {

                     try

                     {

                           //создание экземпляра прокси с привязками и конечной точкой, созданными

                           //элементом управления базового класса

                           AzureClaims.AzureClaimsClient cust =

                                  new AzureClaims.AzureClaimsClient(this.FedBinding,

                                   this.WcfEndpointAddress);

 

                           //настройка канала, чтобы его можно было вызвать с

                           //FederatedClientCredentials.

                           SPChannelFactoryOperations.ConfigureCredentials<AzureClaims.IAzureClaims>

                                   (cust.ChannelFactory,

                                   Microsoft.SharePoint.SPServiceAuthenticationMode.Claims);

 

                           //создание канала к конечной точке WCF с использованием

                           //токена и утверждений текущего пользователя

                           AzureClaims.IAzureClaims claimsWCF =

                                  SPChannelFactoryOperations.CreateChannelActingAsLoggedOnUser

                                  <AzureClaims.IAzureClaims>(cust.ChannelFactory,

                                  this.WcfEndpointAddress,

                                  new Uri(this.WcfEndpointAddress.Uri.AbsoluteUri));

 

                           //установка клиентского свойства для базового класса

                           this.WcfClientProxy = claimsWCF;

                     }

                     catch (Exception ex)

                     {

                           Debug.WriteLine(ex.Message);

                     }

 

                     //теперь, закончив настройку, вызовем метод

                     return base.ExecuteRequest();

              }

       }

 

“AzureClaims” — это имя созданной ссылки на службу, которая использует интерфейс IAzureClaims, определенный в моем проекте WCF в Azure. Как ранее объяснялось в статьях о наборе CASI Kit, в основном это стереотипный код, я только добавил название своего интерфейса и класс, который будет предоставляться в приложении WCF. Кроме того, я создал страницу ASPX, называющуюся AzureClaimProvider.aspx. Процесс создания этой страницы также объясняется в статьях о наборе CASI Kit. Я просто скопировал и вставил код, описанный в части 3 серии статей о наборе CASI, а затем подставил имя своего класса и конечной точки, в которой он может быть доступен. Управляющий тег на странице ASPX для моего настраиваемого компонента набора CASI Kit выглядит следующим образом:

<AzWcf:DataSource runat="server" id="wcf" WcfUrl="https://spsazure.vbtoys.com/ AzureClaims.svc" OutputType="Page" MethodName="GetClaimTypes" AccessDeniedMessage="" />

 

Главное, что стоит отметить, — я создал запись CNAME для "spsazure.vbtoys.com", указывающую на мое приложение Azure в cloudapp.net (это также описано в части 3 статей о наборе CASI Kit). Я задал имя метода MethodName по умолчанию, который будет вызываться страницей в качестве метода GetClaimTypes. Этот метод является методом без скобок, возвращающим список типов утверждений, поддерживаемых моим поставщиком утверждений Azure. Это позволяет использовать данный проект в качестве хорошего теста для проверки соединения между моим приложением Azure и SharePoint. Я могу просто перейти на страницу https://anySharePointsite/_layouts/AzureClaimProvider.aspx, и, если все правильно настроено, я увижу некоторые данные на этой странице. Развернув этот проект путем добавления сборки в глобальный кэш сборок и развертывания этой страницы в каталоге _layouts SharePoint, я сделал именно это — я зашел на страницу одного из своих сайтов и убедился, что она возвращает данные, таким образом я узнал, что мое соединение между SharePoint и Azure работало.

Теперь, закончив рутинные дела, я, наконец, перехожу к "приятной" части проекта, состоящей из двух частей:

  1. 1.       Создание "некоторого компонента", который будет передавать данные о новых пользователях в мою очередь Azure
  2. 2.       Создание настраиваемого поставщика утверждений, который будет использовать мой настраиваемый компонент набора CASI Kit для предоставления типов утверждений, разрешения имен и поиска утверждений.

На этом месте можно немного притормозить и вернуться на шаг назад. В данном конкретном случае я просто хотел получить какой-то результат как можно быстрее. Поэтому я создал новое веб-приложение и разрешил анонимный доступ. Как вы знаете, разрешение на уровне веб-приложения НЕ разрешает его на уровне семейства сайтов. Поэтому для данного сценария я также разрешил этот вид доступа только для корневого семейства сайтов. Я предоставил доступ ко всему содержимому сайта. Для всех остальных семейств сайтов, которые могут содержать какие-то данные только для участников, анонимный доступ НЕ включен, поэтому пользователям нужно предоставить права для присоединения.

Затем следует задуматься о том, как управлять удостоверениями, которые будут использоваться для доступа к этому сайту. Очевидно, это не то, чем мне бы хотелось заниматься. Я мог бы придумать массу различных методов для синхронизации учетных записей в Azure или выполнить что-то настолько же бессмысленное, но, как я объяснял в части 1 этой серии, существует целая группа поставщиков, уже выполняющих это, поэтому пусть они продолжают делать то, что они делают. Я имею в виду, что я воспользовался преимуществом другой облачной службы Майкрософт, реализующей управление доступом и называющейся ACS, или Access Control Service. Вкратце, ACS играет роль поставщика удостоверений для SharePoint. Поэтому я просто создал доверительное отношение между своей фермой SharePoint и экземпляром ACS, который я создал для этой POC. В ACS я добавил SharePoint в качестве проверяющей стороны, поэтому ACS известно, куда направлять пользователей, прошедших проверку подлинности. Внутри ACS я также выполнил настройки, позволяющие пользователям входить в систему, используя учетные записи Gmail, Yahoo или Facebook. После их входа ACS получает утверждение, которое я буду использовать, — адрес электронной почты — и передает его в SharePoint.

Хорошо, теперь вся рутина выполняется в фоновом режиме — Azure предоставляет хранилище таблиц и очереди для работы с данными, ACS обеспечивает службы проверки подлинности, а набор CASI Kit реализует работу с данными.

Теперь, описав всю эту внутреннюю организацию, как мы собираемся ее использовать? Ну, мне все еще хотелось бы, чтобы процесс вступления в участники стал бы максимально безболезненным, поэтому я написал веб-часть для добавления пользователей в свою очередь Azure. Фактически она проверяет, прошел ли запрос проверку подлинности (то есть пользователь щелкнул ссылку "Войти", с которой вы познакомились на анонимном сайте, вошел в службу одного из вышеупомянутых поставщиков, и ACS вернул мне свои данные об утверждении). Если запрос не прошел проверку подлинности, веб-часть не выполняет никаких действий. Но если запрос прошел проверку подлинности, в веб-части появляется кнопка, нажатие которой приводит к добавлению пользовательского утверждения на основе электронной почты в очередь Azure. Это та самая часть, которую я имел в виду, когда говорил, что нужно немного вернуться и подумать. Для POC все это замечательно и хорошо, все работает. Но можно подумать о других возможных способах обработки этого запроса. Например, пусть информация записывается в список SharePoint. Можно написать пользовательское задание таймера (с которым набор CASI Kit работает очень хорошо) и периодически обрабатывать новые запросы из этого списка. Можно использовать SPWorkItem для постановки в очередь запросов, которые будут обрабатываться позже. Можно сохранить запрос в списке и добавить пользовательский рабочий процесс, который может использоваться для процесса утверждения, а после утверждения запроса использовать действие пользовательского рабочего процесса для вызова набора CASI Kit, чтобы поместить данные в очередь Azure. Короче: это решение предоставляет МНОЖЕСТВО возможностей, вариантов и настроек — используйте все, насколько вам хватит воображения. В любой момент я фактически могу написать другую версию этой веб-части, которая помещает информацию в пользовательский список, асинхронно обрабатывает ее в какой-то момент, добавляет данные в очередь Azure, а затем автоматически добавляет учетную запись в группу "Посетители" одного из вложенных сайтов, чтобы пользователь мог войти и немедленно выполнить нужные ему действия. Но это уже для другого сообщения блога, если я соберусь его написать.

Итак, как я описывал выше, я просто позволяю пользователю нажать кнопку, как при обычном входе в систему, а затем использую свой настраиваемый компонент набора CASI Kit для обращения к конечной точке WCF и добавления данных в очередь Azure. Вот код для этой веб-части — он достаточно простой благодаря набору CASI Kit:

public class AddToAzureWP : WebPart

{

 

       //кнопка, нажатие которой нужно отслеживать, чтобы можно было

       //добавить пользователя в Azure

       Button addBtn = null;

       Label statusLbl = null;

 

       protected override void CreateChildControls()

       {

              if (this.Page.Request.IsAuthenticated)

              {

                     addBtn = new Button();

                     addBtn.Text = "Запрос членства";

                     addBtn.Click += new EventHandler(addBtn_Click);

                     this.Controls.Add(addBtn);

 

                     statusLbl = new Label();

                     this.Controls.Add(statusLbl);

              }

       }

 

       void addBtn_Click(object sender, EventArgs e)

       {

              try

              {

                     //поиск удостоверения утверждений

                     IClaimsPrincipal cp = Page.User as IClaimsPrincipal;

 

                     if (cp != null)

                     {

                           //получение удостоверения утверждений, чтобы можно было перечислить утверждения

                           IClaimsIdentity ci = (IClaimsIdentity)cp.Identity;

 

                           //поиск утверждения для электронной почты

                           //проверка наличия утверждений перед выполнением следующего кода

                           if (ci.Claims.Count > 0)

                           {

                                  //поиск утверждения для адреса электронной почты

                                  var eClaim = from Claim c in ci.Claims

                                         where c.ClaimType == "https://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"

                                         select c;

 

                                  Claim ret = eClaim.FirstOrDefault<Claim>();

 

                                  if (ret != null)

                                  {

                                         //создание строки для передачи в очередь Azure:  утверждение, значение и отображаемое имя

                                         //обратите внимание, что я использую в качестве разделителей "#", так как существует только один параметр, и набор CASI Kit

                                         //использует в качестве разделителя ";", потому требуется другое значение.  В случае использования ";" CASI попытается и

                                         //создаст три параметра, а в реальности существует только один

                                         string qValue = ret.ClaimType + "#" + ret.Value + "#" + "Email";

 

                                         //создание подключения к Azure и передача

                                         //создание экземпляра элемента управления

                                         AzureClaimProvider.DataSource cfgCtrl = new AzureClaimProvider.DataSource();

 

                                         //задание свойств для возвращения данных; необходимо настроить свойства кэша, так как мы используем его программно

                                         //хотя фактически кэш в данном случае не используется

                                         cfgCtrl.WcfUrl = AzureCCP.SVC_URI;

                                         cfgCtrl.OutputType = AzureConnect.WcfConfig.DataOutputType.None;

                                         cfgCtrl.MethodName = "AddClaimsToQueue";

                                         cfgCtrl.MethodParams = qValue;

                                         cfgCtrl.ServerCacheTime = 10;

                                         cfgCtrl.ServerCacheName = ret.Value;

                                         cfgCtrl.SharePointClaimsSiteUrl = this.Page.Request.Url.ToString();

 

                                         //выполнение метода

                                         bool success = cfgCtrl.ExecuteRequest();

 

                                         if (success)

                                         {

                                                //если сработало, сообщите пользователю

                                                statusLbl.Text = "<p>Ваши данные успешно добавлены.  Теперь можно связаться с любым из " +

                                                       "других участников-партнеров или нашим персоналом службы поддержки для получения прав доступа к контенту " +

                                                       "партнера.  Обратите внимание, что обработка запроса занимает до " +

                                                       "15 минут.</p>";

                                         }

                                         else

                                         {

                                                statusLbl.Text = "<p>Возникла проблема при добавлении ваших данных в Azure; повторите попытку позже или " +

                                                       "свяжитесь со службой поддержки, если проблема не исчезает.</p>";

                                         }

                                  }

                           }

                     }

              }

              catch (Exception ex)

              {

                     statusLbl.Text = "Возникла проблема при добавлении ваших данных в Azure; повторите попытку позже или " +

                           "свяжитесь со службой поддержки, если проблема не исчезает.";

                     Debug.WriteLine(ex.Message);

              }

       }

}

 

Давайте вкратце рассмотрим представленный код. Сначала я проверяю, что запрос прошел проверку подлинности. Если прошел, я добавляю кнопку на страницу и добавляю обработчик события для события нажатия кнопки. В обработчике события нажатия кнопки я получаю ссылку IClaimsPrincipal на текущего пользователя, а затем просматриваю коллекцию пользовательских утверждений. Я выполняю запрос LINQ для коллекции утверждений, чтобы найти утверждение для электронной почты, которое является утверждением удостоверения для моего SPTrustedIdentityTokenIssuer. Если я нахожу утверждение для электронной почты, я создаю объединенную строку с типом утверждения, значение утверждения и понятное имя утверждения. И снова такой подход не является строго обязательным для данного сценария, но так как я хотел, чтобы созданный код был полезен в более общем сценарии, я написал код таким образом. Эта объединенная строка является значением для метода, который у меня есть в WCF для добавления данных в очередь Azure. Затем я создаю экземпляр моего настраиваемого компонента набора CASI Kit и настраиваю его для вызова метода WCF, добавляющего данные в очередь, а потом я вызываю метод ExecuteRequest для фактического использования данных. 

Если я получаю ответ, показывающий, что данные были успешно добавлены в очередь, я сообщаю об этом пользователю. В противном случае я сообщаю ему о проблеме и о том, что может понадобиться повторить вход позже. В реальном сценарии, конечно же, я бы куда подробнее записывал ошибки в журнал, чтобы можно было точно отследить, что случилось и почему. Но даже в этом случае набор CASI Kit запишет все сведения об ошибке в журналы ULS в SPMonitoredScope, поэтому всему выполненному с запросом сопоставляется уникальный код связи, позволяющий просмотреть все действия, связанные с запросом. Поэтому у меня и сейчас уже все неплохо.

Хорошо — мы прошли через все эти трудности, и я показал, как данные добавляются в очередь Azure и оттуда попадают в рабочий процесс, а затем добавляются в хранилище таблиц. Фактически это и было конечной целью, и теперь можно переходить к настраиваемому поставщику утверждений. Пора вызвать набор CASI Kit и запросить используемое хранилище таблиц Azure. Рассмотрим самые интересные моменты пользовательского поставщика утверждений.

Сначала взглянем на пару атрибутов уровня класса:

//конечная точка WCF, которую мы будем использовать для функций адресной книги

//тестовый url-адрес:  https://az1.vbtoys.com/AzureClaimsWCF/AzureClaims.svc

//производственный url-адрес:  https://spsazure.vbtoys.com/AzureClaims.svc

public static string SVC_URI = "https://spsazure.vbtoys.com/AzureClaims.svc";

 

//значение типа утверждения удостоверения

private const string IDENTITY_CLAIM =

       "https://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress";

 

//коллекция поддерживаемых типов утверждений; она не будет меняться в течение

// времени жизни STS (w3wp.exe), поэтому мы будем кэшировать ее сразу после получения

AzureClaimProvider.AzureClaims.ClaimTypeCollection AzureClaimTypes =

       new AzureClaimProvider.AzureClaims.ClaimTypeCollection();

 

Сначала я использую константу для ссылки на конечную точку WCF, к которой следует подключить набор CASI Kit. Обратите внимание, что я применяю как тестовую, так и производственную конечные точки. Если набор CASI Kit используется программно, как мы собираемся в нашем настраиваемом поставщике утверждений, всегда необходимо сообщать набору, с какой конечной точкой WCF он должен взаимодействовать.

Затем, как я описал ранее, я использую в качестве своего утверждения удостоверения утверждение для электронной почты. Так как в своем поставщике я буду ссылаться это утверждение множество раз, я просто добавил соответствующую константу на уровне класса.

Наконец, у меня есть коллекция AzureClaimTypes. Я объяснил в части 2 этой серии статей, почему я используют коллекцию, и я просто сохраняю ее на уровне класса, чтобы мне не приходилось извлекать эту информацию по новой при каждом вызове моего метода FillHierarchy. Обращения к Azure обходятся дорого, поэтому я по возможности минимизирую их.

Вот соответствующий фрагмент кода:

internal static string ProviderDisplayName

{

       get

       {

              return "AzureCustomClaimsProvider";

       }

}

 

internal static string ProviderInternalName

{

       get

       {

              return "AzureCustomClaimsProvider";

       }

}

 

//*******************************************************************

//ТЕПЕРЬ ИСПОЛЬЗУЕМ ЭТО СВОЙСТВО ПРИ СОЗДАНИИ УТВЕРЖДЕНИЯ ДЛЯ ОБЪЕКТА СРЕДСТВА ВЫБОРА

internal static string SPTrustedIdentityTokenIssuerName

{

       get

       {

              return "SPS ACS";

       }

}

 

 

public override string Name

{

       get

       {

              return ProviderInternalName;

       }

}

 

Причина, по которой я хочу выделить этот код, заключается в следующем: поскольку мой поставщик выдает утверждения удостоверений, он ДОЛЖЕН быть поставщиком по умолчанию для SPTrustedIdentityTokenIssuer. Объяснение того, как это сделать, выходит за рамки этого сообщения, но я рассматривал эту задачу в другом месте своего блога. Главное — нужно помнить о необходимости надежной связи между именем, используемым для своего поставщика, и именем, используемым для SPTrustedIdentityTokenIssuer. Значение, которое я использовал для ProviderInternalName, — это имя, которое я должен вставить в свойство ClaimProviderName для SPTrustedIdentityTokenIssuer. Кроме того, мне нужно использовать имя SPTrustedIdentityTokenIssuer при создании утверждений удостоверений для пользователей. Поэтому я создал SPTrustedIdentityTokenIssuer, который называется "SPS ACS", и добавил его в свое свойство SPTrustedIdentityTokenIssuerName. Вот почему я кодирую здесь эти значения.

Так как я не использую в этом поставщике никаких дополнений утверждений, я не писал никакой код, переопределяющий FillClaimTypes, FillClaimValueTypes или FillEntityTypes. Следующим фрагментом кода, который я использую, является FillHierarchy, в котором я сообщаю SharePoint, какие типы утверждений я поддерживаю. Вот соответствующий код:

try

{

       if (

               (AzureClaimTypes.ClaimTypes == null) ||

               (AzureClaimTypes.ClaimTypes.Count() == 0)

       )

       {

              //создание экземпляра элемента управления

              AzureClaimProvider.DataSource cfgCtrl = new AzureClaimProvider.DataSource();

 

              //задание свойств для возвращения данных; необходимо настроить свойства кэша, так как мы используем его программно

              //хотя фактически кэш в данном случае не используется

              cfgCtrl.WcfUrl = SVC_URI;

              cfgCtrl.OutputType = AzureConnect.WcfConfig.DataOutputType.None;

              cfgCtrl.MethodName = "GetClaimTypes";

              cfgCtrl.ServerCacheTime = 10;

              cfgCtrl.ServerCacheName = "GetClaimTypes";

              cfgCtrl.SharePointClaimsSiteUrl = context.AbsoluteUri;

 

              //выполнение метода

              bool success = cfgCtrl.ExecuteRequest();

 

              if (success)

              {

                     //если все работает, получает список типов утверждений

                     AzureClaimTypes =

                                                (AzureClaimProvider.AzureClaims.ClaimTypeCollection)cfgCtrl.QueryResultsObject;

              }

       }

 

       //убедитесь, что выбирающий запрашивает тип объекта, который мы возвращаем; например, администратор семейства сайтов не будет этого делать

       if (!EntityTypesContain(entityTypes, SPClaimEntityTypes.User))

              return;

 

       //в этот момент у нас есть все типы утверждений, с которыми планируется работать, и мы добавляем их в иерархию

       //проверка значения null для hierarchyNodeID; так будет при первой загрузке

       //элемента управления, но если пользователь щелкает один из узлов, будет возвращен

       //ключ щелкнутого узла.  Это позволяет расширять

       //иерархию, когда пользователь что-то щелкает, а не создавать ее сразу целиком

       if (

               (string.IsNullOrEmpty(hierarchyNodeID)) &&

               (AzureClaimTypes.ClaimTypes.Count() > 0)

              )

       {

              //перебор по каждому типу утверждения

              foreach (AzureClaimProvider.AzureClaims.ClaimType clm in AzureClaimTypes.ClaimTypes)

              {

                     //добавление всех наших узлов при первой загрузке

                     hierarchy.AddChild(new

                                                Microsoft.SharePoint.WebControls.SPProviderHierarchyNode(

                                   ProviderInternalName, clm.FriendlyName, clm.ClaimTypeName, true));

              }

       }

}

catch (Exception ex)

{

       Debug.WriteLine("Ошибка заполнения иерархии: " + ex.Message);

}

 

Теперь я собираюсь проверить, получил ли я уже список поддерживаемых мной утверждений. Если нет, то я создаю экземпляр своего настраиваемого элемента управления набора CASI Kit и обращаюсь к своему WCF для получения типов утверждений, используя вызов метода GetClaimTypes для своего класса WCF. Если я получаю данные обратно, я вставляю их в вышеописанную переменную уровня класса AzureClaimTypes, а затем добавляю их в иерархию поддерживаемых мной типов утверждений.

Далее мы рассмотрим методы FillResolve. У методов FillResolve есть две различные сигнатуры, так как они выполняют две разные задачи. В одном сценарии мы используем конкретное утверждение со значением и типом, и SharePoint нужно только проверить его правильность. Во втором случае пользователь просто ввел некоторое значение в элементе управления SharePoint для ввода данных, и это фактически является тем же самым, что и выполнение поиска утверждений. Поэтому я рассмотрю эти варианты отдельно.

Если у меня есть конкретное утверждение и SharePoint нужно проверить значения, я вызываю написанный мною настраиваемый метод GetResolveResults. В этом методе я передаю универсальный код ресурса (URI), к которому выполняется запрос, а также тип утверждения и значение утверждения, которые SharePoint пытается проверить. В этом случае GetResolveResults выглядит примерно следующим образом:

//Обратите внимание, что тип утверждения claimType здесь передается для возможности расширения в будущем,

//а в текущем случае мы используем только утверждения удостоверений

private AzureClaimProvider.AzureClaims.UniqueClaimValue GetResolveResults(string siteUrl,

       string searchPattern, string claimType)

{

       AzureClaimProvider.AzureClaims.UniqueClaimValue result = null;

 

       try

       {

              //создание экземпляра элемента управления

              AzureClaimProvider.DataSource cfgCtrl = new AzureClaimProvider.DataSource();

 

              //задание свойств для возвращения данных; необходимо настроить свойства кэша, так как мы используем его программно

              //хотя фактически кэш в данном случае не используется

              cfgCtrl.WcfUrl = SVC_URI;

              cfgCtrl.OutputType = AzureConnect.WcfConfig.DataOutputType.None;

              cfgCtrl.MethodName = "ResolveClaim";

              cfgCtrl.ServerCacheTime = 10;

              cfgCtrl.ServerCacheName = claimType + ";" + searchPattern;

              cfgCtrl.MethodParams = IDENTITY_CLAIM + ";" + searchPattern;

              cfgCtrl.SharePointClaimsSiteUrl = siteUrl;

 

              //выполнение метода

              bool success = cfgCtrl.ExecuteRequest();

 

              //если запрос выполнен без ошибок, выполняется захват результата

              if (success)

                     result = (AzureClaimProvider.AzureClaims.UniqueClaimValue)cfgCtrl.QueryResultsObject;

       }

       catch (Exception ex)

       {

              Debug.WriteLine(ex.Message);

       }

 

       return result;

}

 

Теперь я создаю экземпляр настраиваемого элемента управления набора CASI Kit, а затем вызываю метод ResolveClaim для своего WCF. Этот метод получает два параметра, поэтому я передаю их как значения, разделенные точкой с запятой (так как этот способ используется набором CASI Kit для разделения различных значений параметров). Затем я просто выполняю запрос, и если он находит соответствие, он возвращает одно уникальное значение утверждения UniqueClaimValue. В противном случае возвращается значение null. Возвращаясь к моему методу FillResolve, вот как выглядит мой код:

protected override void FillResolve(Uri context, string[] entityTypes, SPClaim resolveInput, List<PickerEntity> resolved)

{

       //убедитесь, что выбирающий запрашивает тип объекта, который мы возвращаем; например, администратор семейства сайтов не будет этого делать

       if (!EntityTypesContain(entityTypes, SPClaimEntityTypes.User))

              return;

 

       try

       {

              //поиск соответствующих утверждений

              AzureClaimProvider.AzureClaims.UniqueClaimValue result =

                     GetResolveResults(context.AbsoluteUri, resolveInput.Value,

                     resolveInput.ClaimType);

 

              //если найдено соответствие, оно добавляется в список разрешенных утверждений

              if (result != null)

              {

                     PickerEntity pe = GetPickerEntity(result.ClaimValue, result.ClaimType,

                     SPClaimEntityTypes.User, result.DisplayName);

                           resolved.Add(pe);

              }

       }

       catch (Exception ex)

       {

              Debug.WriteLine(ex.Message);

       }

}

 

Сначала я выполняю проверку, чтобы убедиться, что выполняется запрос пользовательского (User) утверждения, так как это единственный тип утверждения, который возвращает мой поставщик. Если запрашиваемое утверждение не является утверждением User, я отбрасываю этот запрос. Затем я вызываю свой метод, чтобы разобрать утверждение, и если получаю результат, не являющийся значением null, я обрабатываю его. Для обработки утверждения я вызываю другой написанный мной метод, GetPickerEntity. Здесь я передаю тип и значение утверждения, чтобы создать утверждение удостоверения, а затем я могу добавить возвращенный методом объект PickerEntity в список экземпляров PickerEntity, переданных в мой метод. Я не собираюсь углубляться в метод, так как данное сообщение уже стало слишком длинным, и я рассматривал соответствующие действия в других сообщениях своего блога.

Теперь поговорим о другом методе FillResolve. Как я объяснял выше, он в основном работает подобно поиску, поэтому в основном я собираюсь здесь объединить методы FillResolve и FillSearch. Оба этих метода будут вызывать написанный мной метод SearchClaims, выглядящий следующим образом:

private AzureClaimProvider.AzureClaims.UniqueClaimValueCollection SearchClaims(string claimType, string searchPattern,

       string siteUrl)

{

                    

       AzureClaimProvider.AzureClaims.UniqueClaimValueCollection results =

              new AzureClaimProvider.AzureClaims.UniqueClaimValueCollection();

 

       try

       {

              //создание экземпляра элемента управления

              AzureClaimProvider.DataSource cfgCtrl = new AzureClaimProvider.DataSource();

 

              //задание свойств для возвращения данных; необходимо настроить свойства кэша, так как мы используем его программно

              //хотя фактически кэш в данном случае не используется

              cfgCtrl.WcfUrl = SVC_URI;

              cfgCtrl.OutputType = AzureConnect.WcfConfig.DataOutputType.None;

              cfgCtrl.MethodName = "SearchClaims";

              cfgCtrl.ServerCacheTime = 10;

              cfgCtrl.ServerCacheName = claimType + ";" + searchPattern;

              cfgCtrl.MethodParams = claimType + ";" + searchPattern + ";200";

              cfgCtrl.SharePointClaimsSiteUrl = siteUrl;

 

              //выполнение метода

              bool success = cfgCtrl.ExecuteRequest();

 

              if (success)

              {

                     //если все работает, получение массива результатов

                     results =

 (AzureClaimProvider.AzureClaims.UniqueClaimValueCollection)cfgCtrl.QueryResultsObject;

              }

       }

       catch (Exception ex)

       {

              Debug.WriteLine("Error searching claims: " + ex.Message);

       }

 

       return results;

}

 

В этом методе, как было показано ранее в данном сообщении, я просто создаю экземпляр своего настраиваемого элемента управления CASI Kit. Я вызываю метод SearchClaims для своего WCF и передаю тип утверждения, которое мне нужно найти, значение утверждения, которое мне нужно найти, и максимальное число возвращаемых записей. В части 2 этой серии статей было показано, что SearchClaims просто выполняет BeginsWith для переданного условия поиска, поэтому для большинства пользователей количество результатов может легко превысить 200 значений. Но 200 — это максимальное число соответствий, которое будет показывать средство выбора людей, поэтому это все, что мне нужно. Если кому-то кажется, что пользователи будут прокручивать свыше 200 результатов в поиске результата, утверждаю, что это маловероятно.

Теперь, получив обратно нашу коллекцию UniqueClaimValues, посмотрим, как использовать ее в наших двух переопределяемых методах в настраиваемом поставщике утверждений. Сначала, вот как выглядит метод FillResolve:

protected override void FillResolve(Uri context, string[] entityTypes, string resolveInput, List<PickerEntity> resolved)

{

       //Эта версия разбора работает подобно поиску, поэтому мы рассматриваем его как

       //проверку, что выбирающий запрашивает тип объекта, который мы возвращаем; например, администратор семейства сайтов не будет этого делать

       if (!EntityTypesContain(entityTypes, SPClaimEntityTypes.User))

              return;

 

       try

       {

              //выполнение поиска соответствий

              AzureClaimProvider.AzureClaims.UniqueClaimValueCollection results =

                     SearchClaims(IDENTITY_CLAIM, resolveInput, context.AbsoluteUri);

 

              //перебор по каждому из соответствий и добавление для него объекта выбора

              foreach (AzureClaimProvider.AzureClaims.UniqueClaimValue cv in results.UniqueClaimValues)

              {

                     PickerEntity pe = GetPickerEntity(cv.ClaimValue, cv.ClaimType, SPClaimEntityTypes.User, cv.DisplayName);

                     resolved.Add(pe);

              }

       }

       catch (Exception ex)

       {

              Debug.WriteLine(ex.Message);

       }

}

Здесь просто вызывается метод SearchClaims, и для каждого возвращаемого им результата (если возвращается) создается новый объект PickerEntity, который добавляется в список таких объектов, передаваемый в переопределение метода. Все такие объекты будут показаны в элементе управления для ввода в SharePoint. Метод FillSearch использует это следующим образом:

protected override void FillSearch(Uri context, string[] entityTypes, string searchPattern, string hierarchyNodeID, int maxCount, SPProviderHierarchyTree searchTree)

{

       //проверка, что выбирающий запрашивает тип объекта, который мы возвращаем; например, администратор семейства сайтов не будет этого делать

       if (!EntityTypesContain(entityTypes, SPClaimEntityTypes.User))

              return;

 

       try

       {

              //выполнение поиска соответствий

              AzureClaimProvider.AzureClaims.UniqueClaimValueCollection results =

                     SearchClaims(IDENTITY_CLAIM, searchPattern, context.AbsoluteUri);

 

              //если существует ненулевое количество результатов, добавляем их в средство выбора

              if (results.UniqueClaimValues.Count() > 0)

              {

                     foreach (AzureClaimProvider.AzureClaims.UniqueClaimValue cv in results.UniqueClaimValues)

                     {

                           //узел, с которым будут связаны наши соответствия

                           Microsoft.SharePoint.WebControls.SPProviderHierarchyNode matchNode = null;

 

                           //получение объекта средства выбора для добавления в диалоговое окно

                           PickerEntity pe = GetPickerEntity(cv.ClaimValue, cv.ClaimType, SPClaimEntityTypes.User, cv.DisplayName);

 

                           //добавление узла, в котором это также должно отображаться

                           if (!searchTree.HasChild(cv.ClaimType))

                           {

                                  //создание узла, чтобы можно было показать в нем наше соответствие

                                  matchNode = new

                                         SPProviderHierarchyNode(ProviderInternalName,

                                         cv.DisplayName, cv.ClaimType, true);

 

                                  //добавление его в дерево

                                  searchTree.AddChild(matchNode);

                           }

                           else

                                  //получение узла для этой группы

                                  matchNode = searchTree.Children.Where(theNode =>

                                         theNode.HierarchyNodeID == cv.ClaimType).First();

 

                           //добавление соответствия в наш узел

                           matchNode.AddEntity(pe);

                     }

              }

       }

       catch (Exception ex)

       {

              Debug.WriteLine(ex.Message);

       }

}

 

В FillSearch я снова вызываю свой метод SearchClaims. Для каждого уникального возвращаемого значения UniqueClaimValue (при возвращении) я проверяю, добавлен ли тип утверждения в узел иерархии результатов. И снова в этом случае я всегда буду возвращать только один тип утверждения (электронная почта), но я предусмотрел возможность расширения, позволяющую позднее всегда добавить другие используемые типы утверждений. Поэтому я добавляю узел иерархии, если он отсутствует, или нахожу его, если он существует. Я беру объект PickerEntity, созданный из UniqueClaimValue, и добавляю его в узел иерархии. И это почти все, что нужно сделать.

Я не собираюсь рассматривать метод FillSchema или любое из четырех переопределений свойств типа Boolean, которые должны быть определены для каждого настраиваемого поставщика утверждений, так как в их использовании для данного сценария нет ничего особенного, а я уже описывал основы в других сообщениях этого блога. Кроме того, я не собираюсь рассматривать приемник компонентов, используемый для регистрации этого настраиваемого поставщика утверждений, так как — снова — нет ничего особенного для данного проекта, и я рассматривал соответствующие действия в другом месте. После компиляции понадобится только убедиться, что сборка для настраиваемого поставщика утверждений, а также настраиваемый компонент набора, зарегистрированы в глобальном кэше сборок на каждом сервере фермы, а также понадобится настроить SPTrustedIdentityTokenIssuer для использования настраиваемого поставщика утверждений в качестве поставщика по умолчанию (также объясняется в другом сообщении этого блога). 

Вот так выглядит базовый сценарий от начала до конца. При входе на сайт SharePoint и попытке входа или добавления нового пользователя (фактически утверждения для электронной почты) сначала настраиваемый поставщик утверждений вызывается для получения списка поддерживаемых типов утверждений, а затем еще раз при вводе значения в элементе управления для ввода или при поиске значения с помощью средства выбора людей. В каждом случае настраиваемый поставщик утверждений использует настраиваемый элемент управления набора CASI, чтобы выполнить обращение с проверкой подлинности к Windows Azure для взаимодействия с нашим WCF, который использует наши настраиваемые классы данных для получения данных из хранилища таблиц Azure. Мы распаковываем возвращенные результаты и представляем их пользователю. Благодаря этому создается законченное готовое решение экстрасети для SharePoint и Azure, которое можно использовать без изменений или изменить в соответствии со своими потребностями. К данному сообщению приложены исходный код для настраиваемого компонента набора CASI Kit, веб-часть, регистрирующая пользователя в очереди Azure, и настраиваемый поставщик утверждений. Надеюсь, что предложенное решение вам понравится, окажется полезным и позволит понять, как можно соединить эти отдельные службы для создания решений своих проблем. Вот несколько снимков экранов для конечного решения:

Корневой сайт для анонимного пользователя:

Вот как это выглядит после прохождения проверки подлинности. Обратите внимание, что на веб-части теперь появляется кнопка "Запрос членства" (Request Membership):

Вот пример средства выбора людей в действии, после поиска значений утверждений, начинающихся с “sp”:

 

Это локализованная запись блога. Исходная статья доступна по адресу The Azure Custom Claim Provider for SharePoint Project Part 3