суббота, апреля 30, 2011

Удаление элементов из SPList через ProcessBatchData

Всем разработчикам Sharepoint 2010 (да и вообще любой версии Sharepoint или MOSS) известна ситуация с медленной работой объектной модели и большом времени поиска\модификации через нее.
Так вот мне однажды пришлось писать систему массового удаления данных из разных списков сайта под управлением Sharepoint 2010. Решения через SPListItem.Delete(), SPList.Items.DeleteItem(idx) и даже SPList.Items.DeleteItemById(itemId) давали жуткие показатели времени работы (удаление 100 элементов за 60000(!)-120000(!) секунд). И тут я вспомнил про волшебную возможность SPWeb.ProcessBatchData которая позволяет выполнять CAML запросы напрямую, минуя слой объектной модели.( Забегая вперед скажу что такое решение мне дало производительность в районе ~650сек на 5000 записей.)
Что-ж покопав документацию на сайте Microsoft (http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spweb.processbatchdata.aspx) я подумал что все тривиально и создал свое первое решение. Радостно посапывая я ткнул в кнопочку деплой и запустил свой код. Как ни странно код рухнул. Не буду описывать процесс поиска ошибки и сразу скажу: Разбивайте свои запросы на пачки! Не больше 180 запросов в одном теге . Лично я разбиваю свои запросы в пачки по 100.
Поправив код и сделав разбиение на пачки я запустил все снова. Как и ожидалось пепелац не взлетел. А я поимел совершенно смутившую меня ошибку ArgumentException. Тут надо отметить удивительную абсолютную невразумительности сообщений Sharepoint об ошибках.
Вот к примеру как выглядел мой Expcetion
Exception type: ArgumentException
[Message] = "0x80070057"
[ParamName] = ""
[Data] = "System.Collections.ListDictionaryInternal"
[TargetSite] = "Void GetListItemDataWithCallback2(Microsoft.SharePoint.Library.IListItemSqlClient, System.String, System.String, System.String, System.String, Microsoft.SharePoint.Library.SAFEARRAYFLAGS, Microsoft.SharePoint.Library.ISP2DSafeArrayWriter, Microsoft.SharePoint.Library.ISPDataCallback, Microsoft.SharePoint.Library.ISPDataCallback, Microsoft.SharePoint.Library.ISPDataCallback, Microsoft.SharePoint.Library.ISPDataCallback, Microsoft.SharePoint.Library.ISPDataCallback, Boolean ByRef)"
[StackTrace] = "   в Microsoft.SharePoint.Library.SPRequestInternalClass.GetListItemDataWithCallback2(IListItemSqlClient pSqlClient, String bstrUrl, String bstrListName, String bstrViewName, String bstrViewXml, SAFEARRAYFLAGS fSafeArrayFlags, ISP2DSafeArrayWriter pSACallback, ISPDataCallback pPagingCallback, ISPDataCallback pPagingPrevCallback, ISPDataCallback pFilterLinkCallback, ISPDataCallback pSchemaCallback, ISPDataCallback pRowCountCallback, Boolean& pbMaximalView)
   в Microsoft.SharePoint.Library.SPRequest.GetListItemDataWithCallback2(IListItemSqlClient pSqlClient, String bstrUrl, String bstrListName, String bstrViewName, String bstrViewXml, SAFEARRAYFLAGS fSafeArrayFlags, ISP2DSafeArrayWriter pSACallback, ISPDataCallback pPagingCallback, ISPDataCallback pPagingPrevCallback, ISPDataCallback pFilterLinkCallback, ISPDataCallback pSchemaCallback, ISPDataCallback pRowCountCallback, Boolean& pbMaximalView)
   в Microsoft.SharePoint.SPListItemCollection.EnsureListItemsData()
   в Microsoft.SharePoint.SPListItemCollection.GetEnumerator()
   в MRR.DVIZ.Workflows.ClearFormsWorkflow.ClearFormsWorkflow.FindRunning(Object sender, EventArgs e)
   в System.Workflow.ComponentModel.Activity.RaiseEvent(DependencyProperty dependencyEvent, Object sender, EventArgs e)
   в System.Workflow.Activities.CodeActivity.Execute(ActivityExecutionContext executionContext)
   в System.Workflow.ComponentModel.ActivityExecutor`1.Execute(T activity, ActivityExecutionContext executionContext)
   в System.Workflow.ComponentModel.ActivityExecutor`1.Execute(Activity activity, ActivityExecutionContext executionContext)
   в System.Workflow.ComponentModel.ActivityExecutorOperation.Run(IWorkflowCoreRuntime workflowCoreRuntime)
   в System.Workflow.Runtime.Scheduler.Run()"
[HelpLink] = ""
[Source] = ""
Поиски google ничего вразумительного не дали. Правда у меня зародилось смутное сомнение в том что я что-то не так делаю с форматом CAML запроса. Я даже не могу сказать что мена натолкнуло на эту мысль, интуиция если только.
Не буду рассказывать свои танцы вокруг подбора нового формата. скажу лишь что:
<Batch OnError="Continue">

<SetList>b15da36c-6ed8-4fe1-bbcc-89a1996c1dbe</SetList>
<Method ID="1" Cmd="Delete"><Field Name="ID">638</Field></Method>
<Method ID="2" Cmd="Delete"><Field Name="ID">639</Field></Method>
<Method ID="3" Cmd="Delete"><Field Name="ID">648</Field></Method>
<Method ID="4" Cmd="Delete"><Field Name="ID">649</Field></Method>
<Method ID="5" Cmd="Delete"><Field Name="ID">650</Field></Method>
<Method ID="6" Cmd="Delete"><Field Name="ID">651</Field></Method>
<Method ID="7" Cmd="Delete"><Field Name="ID">652</Field></Method>
<Method ID="8" Cmd="Delete"><Field Name="ID">653</Field></Method>
<Method ID="9" Cmd="Delete"><Field Name="ID">654</Field></Method>
<Method ID="10" Cmd="Delete"><Field Name="ID">655</Field></Method>
<Method ID="11" Cmd="Delete"><Field Name="ID">656</Field></Method>
<Method ID="12" Cmd="Delete"><Field Name="ID">657</Field></Method>

</Batch>
не красиво но не правильно. Надо:
<?xml version="1.0" encoding="UTF-8"?>

<Batch OnError="Return">
<Method ID="1,Del"><SetList 
Scope="Request">465ebd0a-d6d4-4736-81d7-27cac86e8952</SetList> <SetVar 
Name="ID">20</SetVar><SetVar Name="Cmd">Delete</SetVar></Method>
<Method ID="2,Del"> <SetList 
Scope="Request">465ebd0a-d6d4-4736-81d7-27cac86e8952</SetList> <SetVar 
Name="ID">21</SetVar><SetVar Name="Cmd">Delete</SetVar></Method>
<Method ID="3,Del"> <SetList 
Scope="Request">465ebd0a-d6d4-4736-81d7-27cac86e8952</SetList> <SetVar 
Name="ID">22</SetVar><SetVar Name="Cmd">Delete</SetVar></Method>
<Method ID="4,Del"> <SetList 
Scope="Request">465ebd0a-d6d4-4736-81d7-27cac86e8952</SetList> <SetVar 
Name="ID">23</SetVar><SetVar Name="Cmd">Delete</SetVar></Method>
<Method ID="5,Del"> <SetList 
Scope="Request">465ebd0a-d6d4-4736-81d7-27cac86e8952</SetList> <SetVar 
Name="ID">24</SetVar><SetVar Name="Cmd">Delete</SetVar></Method>
<Method ID="6,Del"> <SetList 
Scope="Request">465ebd0a-d6d4-4736-81d7-27cac86e8952</SetList> <SetVar 
Name="ID">25</SetVar><SetVar Name="Cmd">Delete</SetVar></Method>
<Method ID="7,Del"> <SetList 
Scope="Request">465ebd0a-d6d4-4736-81d7-27cac86e8952</SetList> <SetVar 
Name="ID">26</SetVar><SetVar Name="Cmd">Delete</SetVar></Method>
<Method ID="8,Del"> <SetList 
Scope="Request">465ebd0a-d6d4-4736-81d7-27cac86e8952</SetList> <SetVar 
Name="ID">27</SetVar><SetVar Name="Cmd">Delete</SetVar></Method>
<Method ID="9,Del"> <SetList 
Scope="Request">465ebd0a-d6d4-4736-81d7-27cac86e8952</SetList> <SetVar 
Name="ID">28</SetVar><SetVar Name="Cmd">Delete</SetVar></Method>
<Method ID="10,Del"> <SetList 
Scope="Request">465ebd0a-d6d4-4736-81d7-27cac86e8952</SetList> <SetVar 
Name="ID">29</SetVar><SetVar Name="Cmd">Delete</SetVar></Method>
<Method ID="11,Del"> <SetList 
Scope="Request">465ebd0a-d6d4-4736-81d7-27cac86e8952</SetList> <SetVar 
Name="ID">30</SetVar><SetVar Name="Cmd">Delete</SetVar></Method>
<Method ID="12,Del"> <SetList 
Scope="Request">465ebd0a-d6d4-4736-81d7-27cac86e8952</SetList> <SetVar 
Name="ID">31</SetVar><SetVar Name="Cmd">Delete</SetVar></Method>

</Batch>
Код создающий такую структуру и выполняющие его в пачках (listMods - IEnumerable с ИД строк для уделания):
List<string> listBatch = new List();

StringBuilder batch = new StringBuilder();
StringBuilder log = new StringBuilder();

if (!string.IsNullOrEmpty(listTitle) && listMods != null && listMods.Count > 0)
{
    log.AppendFormat("Очистка списка {0}. Удалеине {1} элементов.", listTitle, listMods.Count);
    batch.AppendLine("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
    batch.AppendLine("<Batch OnError=\"Return\">");
    int batchSize = 0;
    foreach (int toDelete in listMods)
    {
        batchSize++;
        batch.AppendFormat(
            "<Method ID=\"{0},Del\"> {2} <SetVar Name=\"ID\">{1}</SetVar><SetVar Name=\"Cmd\">Delete</SetVar></Method>",
            batchSize, toDelete,
            string.Format("<SetList Scope=\"Request\">{0}</SetList>",
                            queryRoot.Lists[listTitle].ID))
            .AppendLine();

        if (batchSize >= 100)
        {
            batch.AppendLine("</Batch>");
            listBatch.Add(batch.ToString());
            batchSize = 0;

            batch = new StringBuilder(batch.Capacity);
            batch.AppendLine("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
            batch.AppendLine("<Batch OnError=\"Return\">");
        }
    }
    batch.AppendLine("</Batch>");
    listBatch.Add(batch.ToString());


    foreach (string cmd in listBatch)
    {
        try
        {
            log.Append("Исполнение запроса :").AppendLine().Append(cmd).AppendLine();
            string cmd1 = cmd;
            SPSite siteColl = currentSite;
            SPWeb site = currentWeb;
            SPSecurity.RunWithElevatedPrivileges(delegate()
                                                        {
                                                            using (
                                                                SPSite ElevatedsiteColl =
                                                                    new SPSite(siteColl.ID))
                                                            {
                                                                using (
                                                                    SPWeb ElevatedWeb =
                                                                        ElevatedsiteColl.OpenWeb(
                                                                            site.ID))
                                                                {
                                                                    ElevatedWeb.ProcessBatchData(cmd1);
                                                                }
                                                            }
                                                        });                                
        }
        catch (Exception e)
        {
            var ex = new ApplicationException("Ошибка при исполнении комманды: " + cmd, e);
            throw ex;
        }
    }
}

среда, апреля 20, 2011

Chrome\Firefox + Silverlight + CSS Styles

Если вы на своей странице используете silverlight и попытаетесь изменить значение атрибута стиля position то Chrome\Firefox заново создадут и загрузят ваше приложение.
Обойти это не получилось.

четверг, апреля 07, 2011

Настройка приложения Sharepoint 2010 для авторизации форм (FBA) с хранением данных в SQLServer

Для настройки приложения Sharepoint 2010 для работы с авторизацией форм FBA (forms based authentication) важно знать что приложение должно быть настроено для работы через Claims Based Authentication.

Самый простой вариант - при создании приложения указать Claims based [Авторизация на утверждениях] (по умолчанию приложение создается в режиме Basic authentication).
Если-же у вас уже имеется приложение для которого надо включить FBA вам придется конвертировать приложение. Сложного в этом ничего нет, но стоит помнить что конвертация НЕОБРАТИМА. 
Для конвертации приложения из Basic в Claims based вам необходимо открыть Powershell для Sharepoint 2010 с правами администратора и там выполнить команды:

$app=Get-SPWebApplication("http://site") 
$app.UseClaimsAuthentication = 1 
$app.Update() 
$app.ProvisionGlobally()

Теперь можно приступить к настройке базы данных. Для создания базы данных для хранения пользователей надо запустить мастер создания и регистрации базы данных пользователей командой:


c:\Windows\Microsoft.NET\Framework64\v2.0.50727\aspnet_regsql.exe

В своем случае я указал имя БД SharepointAuthDB, и учетные данные пользователя от которого у меня работает Sharepoint для простоты настройки администратором домена/сервера, а так-же из-за стойкой неприязни к хранению имени пользователя и пароля доступа в строке подключения.  Для создания строки подключения нам надо запомнить имя сервера, имя БД, и способ авторизации пользователя.

Далее открываем Управление службами IIS и в настройках сервера  нажимаем на ConnectionStrings(Строки подключения) и там нажимаем на "Создать". В открывшемся диалоге вводим имя сервера,имя БД (SharepointAuthDB в моем случае) и способ авторизации пользователя которые вы указывали в мастере регистрации. Так-же необходимо проверить что данный пользователь имеет права dbo и public на базу с пользователями. Так-же вам надо указать имя строки подключения (я указал SqlAuthConnection) и запомнить его.

Далее нам необходимо модифицировать 3 файла web.config

  1. Web.config от приложения Sharepoint которые вы настраиваете для авторизации форм Обычно он расположен по адресу: C:\inetpub\wwwroot\wss\VirtualDirectories\80 (можно посмотреть в настройках узла)

        <roleManager defaultProvider="c" enabled="true" cacheRolesInCookie="false">
          <providers>
            <add name="c" type="Microsoft.SharePoint.Administration.Claims.SPClaimsAuthRoleProvider, Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
            <add connectionStringName="SharepointAuthDB" applicationName="/" description="Stores and retrieves roles from SQL Server" name="FBARole" type="System.Web.Security.SqlRoleProvider, System.Web, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
          </providers>
        </roleManager>
        <membership defaultProvider="i">
          <providers>
            <add name="i" type="Microsoft.SharePoint.Administration.Claims.SPClaimsAuthMembershipProvider, Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
            <add connectionStringName="SharepointAuthDB" passwordAttemptWindow="5" enablePasswordRetrieval="false" enablePasswordReset="false" requiresQuestionAndAnswer="true" applicationName="/" requiresUniqueEmail="true" passwordFormat="Hashed" description="Stores and Retrieves membership data from SQL Server" name="FBA" type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
          </providers>
        </membership>
  2. Файл web.config от SharePoint Central Administration Обычно он расположен по адресу: C:\inetpub\wwwroot\wss\VirtualDirectories\26719 (можно посмотреть в настройках узла)
    В этом файле после тега </SafeControls> добавить секцию
    <PeoplePickerWildcards>
    <clear />
    <add key="AspNetSqlMembershipProvider" value="%" />
    <add key="FBA" value="%" />
    <add key="LdapMembershipProvider" value="*"/>
    <add key="LdapRoleManager" value="*"/>
    </PeoplePickerWildcards>

    и перед тегом </system.web> добавить секцию:
    <roleManager defaultProvider="AspNetWindowsTokenRoleProvider" enabled="true" cacheRolesInCookie="false">
    <providers>
    <add connectionStringName="SharepointAuthDB" applicationName="/" description="Stores and retrieves roles from SQL Server" name="FBARole" type="System.Web.Security.SqlRoleProvider, System.Web, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
    </providers>
    </roleManager>
    <membership defaultProvider="FBA">
    <providers>
    <add connectionStringName="SharepointAuthDB" passwordAttemptWindow="5" enablePasswordRetrieval="false" enablePasswordReset="false" requiresQuestionAndAnswer="true" applicationName="/" requiresUniqueEmail="true" passwordFormat="Hashed" description="Stores and Retrieves membership data from SQL Server" name="FBA" type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
    </providers>
    </membership>


  3. Файл web.config от Sharepoint Security Token Service (STS)Обычно он расположен по адресу: C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\WebServices\SecurityToken В этот файл необходимо добавить секцию:


    <system.web>

    <roleManager defaultProvider="c" enabled="true" cacheRolesInCookie="false">

    <providers>

    <add name="c" type="Microsoft.SharePoint.Administration.Claims.SPClaimsAuthRoleProvider, Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />

    <add connectionStringName="SharepointAuthDB" applicationName="/" description="Stores and retrieves roles from SQL Server" name="FBARole" type="System.Web.Security.SqlRoleProvider, System.Web, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />

    </providers>

    </roleManager>



    <membership defaultProvider="i">
    <providers>
    <add name="i" type="Microsoft.SharePoint.Administration.Claims.SPClaimsAuthMembershipProvider, Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
    <add connectionStringName="SharepointAuthDB" passwordAttemptWindow="5" enablePasswordRetrieval="false" enablePasswordReset="false" requiresQuestionAndAnswer="true" applicationName="/" requiresUniqueEmail="true" passwordFormat="Hashed" description="Stores and Retrieves membership data from SQL Server" name="FBA" type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
    </providers>
    </membership>
    </system.web>
Далее необходимо открыть приложение Sharepoint Central Administration  и нам в настройках приложения нажать "Поставщики проверки безопасности", выбрать необходимую зону и в настройках зоны включить галочку "Разрешить проверку подлинности на основе форм (FBA)".

Далее в поле "Имя поставщика контроля членства в ASP.NET" (membership provider) задать FBA , а в поле "Имя поставщика контроля членства в ASP.NET" (role provider) задать FBARole (если вы не меняли имя поставщиков в XML а указали как я написал). А так-же проверить что разрешен анонимный доступ (для обеспечения работоспособности сервиcов Sharepoint)

Нам осталось совсем немного. Для проверки корректности работы авторизации форм надо создать пользователей в базе данных. (ВНИМАНИЕ из-за особенностей настройки Membreship

Providers для Sharepoint 2010 управлять пользователями через стандартную оснастку IIS не получится, я могу посоветовать написать свой менеджер пользователей или воспользоваться программой MembershipSeeder)

Теперь осталось перезапустить IIS. Зайдти в Sharepoint Central Admin и в настройках приложения задать политику доступа для пользователей FBA.


Обрушение потолка в "Москва сити"


video
Сегодня 7 апреля 2010 гола приблизительно в 09:10 утра произошло обрушение потолка вестибюля выхода со станции метро "Выставочная". В данном вестибюле так-же расположен вход в открывшийся две недели назад торговый цента Амфи Сити Молл расположенный на территории бизнес комплекса Москва-Сити. Во время обрушения в вестибюле станции было достаточно много людей, лишь по счастливой случайности никто не пострадал. Из дырки образовавшейся на месте пролома тут-же начала теч вода (лужу отчетливо видно на фотографиях).




среда, апреля 06, 2011

Sharepoint 2010 и исключение file not found exception

Маленький совет: Если вы пишите приложение использующее объектную модель Sharepoint и при попытке открыть сайт как в примере:
using(SPSite sirte=new SPSite("http://site"){
......
}
И получаете исключение File not fount exception хотя сайт в браузере открывается - проверьте две вещи:

  1. Build platform для проекта должен быть .NET Framework 3.5
  2. Проект обязательно собираться для x64 платформы