Linq to SharePoint. Особенности. Часть 3
Часть 1. First()/FirstOrDefault(), T-SQL IN, Path
Часть 2. Count(), Take(), Skip(), JOIN
Часть 3. Анонимный доступ, Получение списка по URL'у, Cross-Site запросы
Часть 4. SPListItem -> LINQ, Dynamic Linq to SharePoint
Часть 5. Поля Choice и MultiChoice в Linq to SharePoint
Часть 6. Сравнение производительности Linq to SharePoint и Camlex.NET
Получение списка по URL
В базовом классе Microsoft.SharePoint.Linq.DataContext, который мы используем для создание своего контекста данных, есть метод GetList
Другое дело URL списка: пользователь его изменить не может и от локализации он независим. Сложность здесь заключается в том, что у SharePoint нет методов, которые бы возвращали список по его URL. На помощь нам приходит другой метод, а именно SPWeb.GetFolder(string strUrl). Вызвав этот метод, мы получим экземпляр объекта SPFolder, имеющего свойство ParentListId. Теперь сказанное в виде метода:
/// <summary>
/// Получение списка по URL
/// </summary>
private EntityList<T> GetListByUrl<T>(string listUrl)
{
string listTitle;
using(var site = new SPSite(Web))
{
using(var web = site.OpenWeb())
{
var folder = web.GetFolder(listUrl);
var list = web.Lists[folder.ParentListId];
listTitle = list.Title;
}
}
return GetList<T>(listTitle);
}
Метод классный, но имеет ряд ограничений: URL должен быть указан относительно текущего узла, например:
"/Lists/Departments",
"Lists/Departments",
"/Lists/Departments/AllItems.aspx",
"/DocLib/Forms/AllItems.aspx"
И работать этот метод будет медленнее стандартного, т.к. создаются объекты SPSite, SPWeb, SPFolder и SPList.
Анонимный доступ
Ещё одна досадная неприятность: Linq-to-SharePoint не работает в анонимном режиме. Связано это с тем, что в случае, когда пользователь анонимен, то свойство SPContext.Current.Web.CurrentUser равно null. Из-за чего мы получаем исключение. Чтобы стало понятно, как это обойти достаточно взглянуть на конструктор класса SPServerDataConnection, используемый для получения данных:
public SPServerDataConnection(string url)
{
if (SPContext.Current != null)
{
this.defaultSite = SPContext.Current.Site;
this.defaultWeb = (SPContext.Current.Web.Url == url)
? SPContext.Current.Web
: this.defaultSite.OpenWeb(new Uri(url).PathAndQuery);
}
else
{
this.defaultSite = new SPSite(url);
this.defaultWeb = this.defaultSite.OpenWeb(new Uri(url).PathAndQuery);
}
if (!this.defaultWeb.Exists)
{
throw new ArgumentException(
Resources.GetString("CannotFindWeb", new object[] { url }));
}
this.defaultWebUrl = this.defaultWeb.ServerRelativeUrl;
this.openedWebs = new Dictionary<string, SPWeb>();
this.openedWebs.Add(this.defaultWebUrl, this.defaultWeb);
}
Как видно, в случае, когда контекста нет (SPContext.Current == null), он создается заново на основе текущего URL-адреса. Это первый момент. Второй момент заключается в том, что нам нужен пользователь, чтобы выполнить операцию от его имени. В этом нам поможет метод SPSecurity.RunWithElevantPrivilages(), который исполняет переданный ему делегат от имени учетной записи, указанной в пуле веб-приложения. Вот код с описанием:
public static void RunAsAdmin(SPSecurity.CodeToRunElevated secureCode)
{
// Анонимный доступ
var isAnonymous = SPContext.Current.Web.CurrentUser == null;
// Необходимо ли сбрасывать текущий контекст
var nullUserFlag = (SPContext.Current != null && isAnonymous);
if (nullUserFlag)
{
// Сохраняем текущий контекст
var backupCtx = HttpContext.Current;
// Сбрасываем текущий контекст
HttpContext.Current = null;
// Исполняем код
SPSecurity.RunWithElevatedPrivileges(secureCode);
// Возвращаем контекст к предыдущему значению
HttpContext.Current = backupCtx;
}
else
{
// Исполняем как есть
SPSecurity.RunWithElevatedPrivileges(secureCode);
}
}
Этот статический метод можно вставить в ваш класс, реализующий контекст.
Cross-Site запросы данных в Linq-to-SharePoint
Суть cross-site запросов заключается в том, что контекст созданный для одного сайта может обращаться к данным из другого сайта, но только в пределах одной сайт-коллекции. Возвращаясь к демонстрационному проекту из 2 части, приведу следующий пример: есть два уза с двумя списками, структура которых идентична. Чтобы дать понять контексту, что теперь данные для списка надо брать из другого узла, надо вызвать метод RegisterList(), передав ему в качестве параметров название нового списка, относительный URL нового узла:
using(var ctx = new ZhukBlogContext("http://spserver"))
{
var rootEmployees = ctx.Employees.ToList();
ctx.RegisterList<Employees>("Employees", "/SubSite", "Employees");
var subEmployees = ctx.Employees.ToList();
}
Вот как-то так.