На этот раз у меня появился соавтор, так как коллега Шэд Филлипс (Shad Phillips) помогал мне в недавнем проекте, где я работал с заказчиком по проверке концепции, в которой SharePoint 2010 использовался в качестве платформы приложений. Попутно, один из сотрудников заказчика спросил меня, не мог бы я придумать приемлемый способ принимать одобренный контент от SharePoint и публиковать его, делая его доступным людям вне корпоративной сети.
Текущая инфраструктура у заказчика не поддерживала внешний контент (скачиваемые документы и видеоролики). Много работая с Windows Azure, я сразу же подумал, что было бы совсем не сложно реализовать передачу контента в Windows Azure Storage как часть рабочего процесса, а затем — в зависимости от потребностей — делать его общедоступным или предоставлять договорной доступ к нему.
С учетом этого я поговорил со своим коллегой, Шэдом, который ранее решал подобную задачу, где он реализовал метод-пример для архивации документов, передавая их из библиотеки SharePoint в Windows Azure Storage. Хотя цели того решения отличались от моих, механика одна и та же. Поэтому сегодня Шэд и я пошагово рассмотрим реализацию-пример, которая обеспечивает продвижение контента из SharePoint в Windows Azure Storage, и расскажем немного об управлении доступом к файлам на договорной основе.
Сценарий и подготовка
Мы разработали собственную функцию, которая позволяет пользователю избирательно перемещать документы из SharePoint в Windows Azure Storage. По какой-то необъяснимой причине пользователям обычно не нравится, когда их документы перемещаются, а новых ссылок на них не дают, поэтому мы оставляли в библиотеке документов ссылку на местоположение документа в облаке, которая вела себя так, будто документ вовсе и не был перемещен в облако.
Необходимое ПО:
- Visual Studio 2010;
- Microsoft SharePoint 2010:
- Windows Azure SDK;
- Windows Azure Development Storage Service.
Конфигурация сайта SharePoint 2010 и библиотеки документов
В этом сценарии мы создали сайт SharePoint с применением шаблона Team Site. В библиотеке Shared Documents мы сформировали столбец, который позволяет помечать элемент как архивируемый и отправляемый в Windows Azure. Это делается через команду Library Settings на ленте. В Library Settings мы создали столбец со свойствами, показанными на рис. 1.
Рис. 1 Настройки столбца в шаблоне Team Site
В Advanced Settings мы также выбрали Yes для параметра Allow the management of content types.
Мы использовали это как часть Content Type, который мы назвали «Link to a Document». Затем мы создали экземпляры этого Content Type, позволяющие ссылаться на архивный документ (рис. 2).
Рис. 2 Наш новый тип контента «Link to a Document»
Добавив столбец и тип контента в библиотеку документов, мы загрузили пример документа Word с названием Services SOW.docx.
Файл web.config для SharePoint 2010
Чтобы подключиться к облаку, нужно получить настройки, необходимые для соединения с Windows Azure. В данном случае мы использовали хранилище для разработки и добавили ключи в элемент <appSettings> в web.config, как показано на рис. 3.
Рис. 3 Добавление ключей в web.config
Проект SharePoint
К счастью, SharePoint 2010 полностью взаимодействует с Visual Studio 2010, и с его помощью очень удобно создавать, отлаживать и публиковать новые функции (feature). Мы создали SharePoint Feature (о функциях SharePoint см. по ссылке msdn.microsoft.com/library/bb861828(office.12)), которая добавляет собственное действие в раскрывающиеся меню действий для элементов в библиотеке документов. Пользователь будет выбирать это действие для архивации.
Мы начали с создания решения MSSAzureArchive, используя шаблон Empty SharePoint Project (рис. 4).
Рис. 4 Выбор проекта в Visual Studio 2010
Далее мы указали сайт и уровень защиты для отладки. Мы предпочли вариант «Deploy as a farm solution», так как коду понадобится делать внешние вызовы, а решение в изолированной программной среде («песочнице») этого не позволило бы. В проект нужно добавить ссылки на Microsoft.Windows.Azure.StorageClient и System.Web. Затем мы добавили в проект элемент Empty Element, используя шаблон EmptyElement, и присвоили ему имя AzureStorageElement. Элемент <CustomAction/> был добавлен для включения нового действия в контекстное меню элемента в библиотеке документов (рис. 5).
Рис. 5 Добавление AzureStorageElement через Add New Item
После этого в проект была автоматически добавлена новая функция с именем Feature1, которую мы переименовали в MSSAzureArchive. Мы заменили содержимое файла Elements.xml для AzureStorageElement следующим:
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<CustomAction
Id="UserInterfaceCustomActions.ECBItemToolbar"
RegistrationType="List"
RegistrationId="101"
Location="EditControlBlock"
Sequence="106"
Title="Azure Storage">
<UrlAction Url="~sitecollection/
_layouts/MSSAzureArchive/
AzureStorage.aspx?ItemUrl={ItemUrl}" />
</CustomAction>
</Elements>
Для неопытных разработчиков в SharePoint на рис. 6 дано краткое описание некоторых свойств <CustomAction/> (подробнее об элементе <CustomAction/> и его свойствах см. по ссылке msdn.microsoft.com/library/ms460194).
Рис. 6 Свойства элемента <CustomAction/>
Свойство | Описание |
Id | Уникальный идентификатор |
Location | Указывает, где в SharePoint UI должен появиться элемент. В данном случае нужно местоположение — меню элемента (EditControlBlock), а не лента, например |
Sequence | Указывает приоритет действий |
Обратите внимание на свойство Url элемента UrlAction; это навигационное действие, вызывающее обработку команды на архивацию документа. На основе этой конфигурации SharePoint знает, где ему нужно разместить функцию в UI и что делать, когда кто-то ее выберет. SharePoint переадресует пользователя на созданную нами страницу, которая обработает архивацию выбранного документа. Эта страница позволит пользователю указывать или создавать целевой контейнер-хранилище для данного элемента, поэтому нам нужно было добавить в проект элемент Application Page. Вновь используя шаблоны SharePoint 2010, мы выбрали шаблон Application Page и назвали его AzureStorage.aspx (рис. 7).
Рис. 7 Добавление новой страницы в SharePoint 2010
Так как в этом примере мы не собирались поражать кого-либо дизайном своего UI, мы добавили лишь те элементы управления, без которых нельзя выполнить работу. В элемент <asp:Content> разметки страницы был включен код, показанный на рис. 8.
Рис. 8 Добавление минимально необходимых элементов управления
Document to Archive:
<asp:Label ID="fileName" runat="server" ></asp:Label> <br/>
Choose Azure Container:
<asp:DropDownList ID="azureContainers" runat="server"
Visible="true"></asp:DropDownList>
<asp:TextBox id="newContainerName" runat="server" Visible="false"></asp:TextBox>
<asp:Button ID="saveContainer" runat="server" Text="Save Container"
OnClick="SaveContainer_Click" Visible="false"></asp:Button>
<br />
<asp:Button ID="createContainer" runat="server" Text="Create New Container"
OnClick="CreateContainer_Click" />
<br/>
<asp:Button ID="archiveFile" runat="server" Text="Archive File"
OnClick="Archive_Click" />
<br/>
<asp:Label ID="errMessage" runat="server" Text=""></asp:Label>
Далее мы отредактировали отделенный код и подключили UI-элементы к коду, обеспечивающему взаимодействие с Windows Azure Storage. Инициализация клиента облачного хранилища осуществляется при событии загрузки для страницы; при этом мы получаем доступные контейнеры, используя предыдущие настройки web.config ( рис. 9).
Рис. 9 Инициализация клиента облачного хранилища
protected void Page_Load(object sender, EventArgs e)
{
this.InitializeCloudStorage();
if (!IsPostBack)
{
this.GetContainers();
}
}
private void GetContainers()
{
IEnumerable<CloudBlobContainer> blobContainers =
cloudBlobClient.ListContainers();
this.azureContainers.DataSource = blobContainers;
azureContainers.DataTextField = "Name";
this.azureContainers.DataBind();
if (azureContainers.Items.Count < 1)
{
ListItem defaultContainer = new ListItem(defaultContainerName);
defaultContainer.Selected = true;
azureContainers.Items.Add(defaultContainer);
}
}
Нас интересовала в основном функциональность архивации — на ней мы и сосредоточимся здесь. Остальной код можно скачать по ссылке code.msdn.microsoft.com/mag201012Cloudy. Мы добавили обработчик щелчков для кнопки archiveFile и подключили функцию Archive_Click. На основе элемента UrlAction мы можем получать путь к нужному элементу. Функция обработки щелчка выбирает этот элемент из SharePoint с применением его объектной модели, проверяет, не был ли он уже заархивирован, и, если нет, загружает его в выбранный контейнер ( рис. 10).
Рис. 10 Код для функции обработки щелчка элемента из SharePoint
protected void Archive_Click(object o, EventArgs e)
{
try
{
webSite = SPContext.Current.Web;
filePath = webSite.Url.ToString() +
Request.QueryString["ItemUrl"].ToString();
fileToArchive = webSite.GetFile(filePath);
string sArchived = fileToArchive.Item["IsArchived"].ToString();
bool isArchived = Convert.ToBoolean(sArchived);
if (isArchived)
{
errMessage.Text = "This document has already been archived!";
}
else
{
string newGuid = Guid.NewGuid().ToString();
string uniqueBlobName = string.Format(newGuid + "_" +
fileToArchive.Name.ToString());
blobContainer = cloudBlobClient.GetContainerReference(
this.azureContainers.SelectedValue);
blobContainer.CreateIfNotExist();
cloudBlob = blobContainer.GetBlockBlobReference(uniqueBlobName);
cloudBlob.UploadByteArray(fileToArchive.OpenBinary());
После загрузки элемента в хранилище вместо исходного документа создается новый архивный элемент типа «Link to a Document», а исходный документ удаляется. Если вместо архивации требуется публикация, то исходный элемент не удаляется, а помечается как опубликованный со ссылкой на опубликованную версию. Исходный элемент используется для получения целевой библиотеки документов и пути к оригинальному документу. Новый элемент помечается как заархивированный добавлением свойства IsArchived и присваиванием ему значения true. Но, прежде чем создавать новый элемент и присваивать ему значения, мы сначала выполняем кое-какую работу, необходимую для получения некоторых из этих значений:
SPDocumentLibrary docLib =
fileToArchive.DocumentLibrary;
Hashtable docProperties = new Hashtable();
docProperties["IsArchived"] = true;
string docLibRelPath =
docLib.RootFolder.ServerRelativeUrl;
string docLibPath = string.Empty;
webSiteCollection = SPContext.Current.Site;
docLibPath =
webSiteCollection.MakeFullUrl(docLibRelPath);
string azureURL = cloudBlob.Uri.ToString();
Функция BuildLinkToItem создает экземпляр типа контента «Link to a Document», используя путь у элементу в Windows Azure Storage. Этот экземпляр будет добавлен в библиотеку как ссылка; с ее помощью в SharePoint UI можно будет получить соответствующий элемент из Windows Azure Storage:
string azureStub = this.BuildLinkToItem(azureURL).ToString();
SPFile newFile = webSite.Files.Add(documentPath,
UTF8Encoding.UTF8.GetBytes(azureStub), docProperties, true);
SPListItem item = newFile.Item;
item["Content Type"] = "Link to a Document";
SPFieldUrlValue itemUrl = new SPFieldUrlValue();
itemUrl.Description = fileToArchive.Name;
itemUrl.Url = azureURL;
item["URL"] = itemUrl;
item["IsArchived"] = true;
item.Update();
fileToArchive.Delete();
Написав код для сохранения документа, его перемещения и замены ссылкой на Windows Azure Storage, можно было заняться компиляцией и развертыванием решения. Дважды щелкните файл Package.package, чтобы открыть дизайнер упаковки, и выберите вкладку Advanced в нижней части экрана. Именно здесь мы добавляли сборки пакета, необходимые для включения Microsoft.WindowsAzure.StorageClient.dll. Чтобы не усложнять пример, мы задали в Deployment Target значение GlobalAssemblyCache. Чтобы запустить хранилищ для разработок (Development Storage), мы перешли в Server Explorer, щелкнули узел Windows Azure Storage, а затем раскрыли узел «(Development)».
Теперь нажимаем клавишу F5 для компиляции, развертываем и инициируем сеанс браузера для запуска отладки нашей функции. После этого переходим обратно в библиотеку Shared Documents и открываем раскрывающееся меню, подключенное к ранее загруженному документу. Выбираем новый элемент, Azure Storage, и это приводит к переходу на собственную страницу приложения, где можно выбрать нужный контейнер (рис. 11).
Рис. 11 Выбор элемента Windows Azure Storage
На этой странице мы могли бы создать новый контейнер, но вместо этого использовали существующий контейнер документов и щелкнули кнопку Archive File для запуска соответствующего кода (рис. 12).
Рис. 12 Выбор контейнера
Отправив файл в Windows Azure Storage, возвращаемся в библиотеку Shared Documents. Вместо документа Services SOW.docx в ней появляется элемент «Link to a Document» (рис. 13).
Рис. 13 Ссылка на документ в библиотеке документов SharePoint
Посмотрев на свойства этого элемента, вы увидели бы поля, относящиеся к типу контента, и, в частности, URL, по которому документ теперь размещается в Windows Azure Storage (рис. 14).
Рис. 14 Свойства ссылки
Мы могли открывать документ напрямую из Windows Azure Storage, щелкая Link to a Document. Мы могли использовать свойство URL для прямого доступа к нему или через некий другой код либо UI. Например, если бы нам нужно было по-прежнему индексировать такие элементы с помощью SharePoint Index Service, то можно было бы создать собственный IFilter, которому известно, как обрабатывать тип контента «Link to a Document» для корректной индексации его содержимого.
После реализации архивирования контента из библиотеки документов SharePoint в контейнер Windows Azure Storage неаутентифицированные пользователи получали доступ только к общедоступным архивным документам.
Управление доступом при публикации
Как упоминалось, я обратился к Шэду по поводу его кода архивации документов из-за того, что он использовал Windows Azure Storage в качестве места размещения общедоступного контента, который прошел проверку и одобрен к публикации. В моем случае управление доступом не требовалось, так как документы были доступны всем. Однако через пару минут кто-нибудь обязательно спросит: «А как быть, если нам нужно публиковать какие-то материалы и открывать к ним доступ лишь определенному кругу лиц, например поставщикам, заказчикам или сотрудникам?». Зачастую задача наподобие этой решается в компаниях включением соответствующих людей в некий корпоративный домен или объединением их в федерацию для идентификации по имени и паролю. Здесь это было лишним — заказчик на самом деле не хотел устанавливать дополнительный прикладной уровень или внутренний интерфейс для управления доступом; разработка такого интерфейса уменьшила бы ему ценность решения из-за увеличения расходов на реализацию.
Одно из решений — использовать SharedAccessPolicy применительно к большим двоичным объектам (blobs). Для контейнера и таких объектов в контейнере можно установить PublicAccess в Off и написать немного кода. Ниже показано, как можно задать PublicAccess равным Off, но разрешить SharedAccess:
BlobContainerPermissions permissions = new BlobContainerPermissions();
permissions.PublicAccess = BlobContainerPublicAccessType.Off;
SharedAccessPolicy accesspolicy = new SharedAccessPolicy();
accesspolicy.Permissions = SharedAccessPermissions.Read;
permissions.SharedAccessPolicies.Add("Read", accesspolicy);
BlobContainer.SetPermissions(permissions);
Если мы запросим ресурс напрямую в контейнере хранилища, то получим ошибку с кодом 404 (Page Not Found). При загрузке больших двоичных объектов мы делаем для них похожую работу, но создаем SharedAccessPolicy, который разрешает чтение, задает срок действия этого разрешения и запрашивает SharedAccessSignature:
SharedAccessPolicy policy = new SharedAccessPolicy();
policy.Permissions = SharedAccessPermissions.Read;
policy.SharedAccessExpiryTime = DateTime.Now.AddDays(5);
string SharedAccessSignature = destBlob.GetSharedAccessSignature(policy));
Вызов GetSharedAccessSignature возвращает примерно такую строку:
?se=2010-08-26T18%3A22%3A07Z&sr=b&sp=r&sig=WdUHKvQYnbOcMwUdFavn4QS0lvhAnqBAnVnC6x0zPj8%3D
Если я присоединяю эту строку запроса к концу URI двоичного объекта, то должен впоследствии принимать ее обратно при условии, что срок действия не истек. Подробнее о подписи и политиках совместного доступа см. по ссылке msdn.microsoft.com/library/ee395415.
Чтобы решить эту задачу, я генерировал бы подписи и предоставлял бы подписанные URI с длительным сроком действия, что упростило бы их создание в момент загрузки, а затем хранил бы список ссылок на опубликованные документы. Для разработки более безопасного варианта и при необходимости обеспечивать доступ единственному пользователю на короткое время понадобилось бы создание соответствующей части UI. Через этот UI пользователь мог бы запрашивать доступ к одному или более ресурсам и получать подписанные URI, разрешающие доступ на короткое время.
Универсальное решение — и для облака, и для локального размещения
Шэд и я использовали здесь одну универсальную реализацию для решения двух разных задач. Такое решение особенно удобно, так как нам требовалась предоставляемая облаком конкретная часть функциональности (масштабируемость, надежность и расширяемое хранилище), чтобы избежать лишних усилий с нашей стороны и свести затраты к минимуму. Основная идея в том, что концепции, стоящие за нашим решением, не требуют его размещения исключительно в облаке или локально на предприятии. Эти варианты можно легко смешивать. Со временем станет все легче и легче смешивать корпоративные сети и облачные. Полагаю, что в будущем между ними не будет особой разницы. Итак, если вы ищете решения для своего ПО или бизнес-систем, то, наверное, сделаете здесь паузу и задумаетесь: «Что в облачных вычислениях может мне помочь?».