<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/rss2full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" version="2.0"><channel><atom:id>tag:blogger.com,1999:blog-1302241583051203640</atom:id><lastBuildDate>Wed, 25 Jan 2012 09:30:07 +0000</lastBuildDate><category>SOLID</category><category>AOP</category><category>XSLT</category><category>архитектура</category><category>MVVM</category><category>Architecture</category><category>MVC</category><category>IoC</category><category>security</category><category>RBS</category><category>SharePoint</category><category>MVP</category><category>mapping</category><category>SOA</category><category>Azure</category><category>ООП</category><category>IoC-контейнер</category><category>ASP.NET</category><category>DI</category><category>C#</category><category>claims</category><category>Expression Mapper</category><category>MEF</category><category>PowerShell</category><category>рефакторинг</category><category>js</category><category>Linq</category><category>Enterprise Library</category><category>OData</category><category>Unity</category><category>web parts</category><category>DL</category><category>DDD</category><category>Reactive Extensions</category><category>WinForms</category><category>WPF</category><category>Entity Framework</category><category>Silverlight</category><category>.NET</category><title>gandjustas' blog</title><description /><link>http://gandjustas.blogspot.com/</link><managingEditor>noreply@blogger.com (Станислав Выщепан)</managingEditor><generator>Blogger</generator><openSearch:totalResults>68</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/rss+xml" href="http://feeds.feedburner.com/GandjustasBlog" /><feedburner:info uri="gandjustasblog" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><item><guid isPermaLink="false">tag:blogger.com,1999:blog-1302241583051203640.post-8111188180151488846</guid><pubDate>Wed, 11 Jan 2012 06:00:00 +0000</pubDate><atom:updated>2012-01-11T10:00:06.296+04:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Azure</category><category domain="http://www.blogger.com/atom/ns#">Reactive Extensions</category><title>Оптимизация процессинга в Windows Azure. Часть 3.</title><description>&lt;p&gt;&lt;a href="http://gandjustas.blogspot.com/2012/01/windows-azure-2.html" target="_blank"&gt;В предыдущем посте&lt;/a&gt; я описал подход, позволяющий существенно сократить количество вызовов к azure storage, который может сэкономить много денег. Но тем не менее ваши воркеры продолжают поедать ваши деньги.&lt;/p&gt;  &lt;h4&gt;А нужны ли вообще воркеры?&lt;/h4&gt;  &lt;p&gt;Оказывается&lt;em&gt; не нужны&lt;/em&gt;. Если у вас небольшое приложение и вы используете очереди для надежной (reliable) асинхронной обработки, причем сама обработка не требует больших вычислительных затрат, то вам и не нужны воркеры. Можете использовать пару методов ToObserver\ToObservable из предыдущего поста, а для оповещений обычный Subject&amp;lt;Unit&amp;gt;. &lt;/p&gt;  &lt;p&gt;Отказываться&amp;#160; от очередей в данном случае не надо, так как при масштабировании работу подхватят воркеры.&lt;/p&gt;  &lt;h4&gt;Scale Down&lt;/h4&gt;  &lt;p&gt;Как вы уже могли догадаться возможность масштабировать “вниз” в облаке не менее важна, чем масштабирование “вверх”. С учетом всех ранее перечисленных подходов можно любое приложение развернуть на одном Extra Small Instance в Windows Azure за $30 и тысячей транзакций хранилища (меньше $0.01) в месяц, если к нему будет мало обращений. Это уже сопоставимо с ценой shared-хостинга.&lt;/p&gt;  &lt;p&gt;На этом история scale down заканчивается и начинается история…&lt;/p&gt;  &lt;h4&gt;Scale Up\Out&lt;/h4&gt;  &lt;p&gt;Сразу же рекомендую посмотреть на Autoscale Application Block (кодовое имя WASABi) из комплекта Enterprise Library. &lt;a href="http://entlib.codeplex.com/wikipage?title=EntLib5Azure" target="_blank"&gt;Ссылка на Enterprise Library 5.0 Windows Azure Integration Pack&lt;/a&gt;. Этот модуль позволяет задавать правила в соответствии с которыми будет изменяться количество экземпляров ролей в вашем приложении.&lt;/p&gt;  &lt;p&gt;Но количество ролей позволяет выдерживать вычислительную нагрузку, хотя в большинстве веб-приложений хранилище становится узким местом.&lt;/p&gt;  &lt;p&gt;К сожалению Windows Azure тут не исключение. В блоге Windows Azure Storage описаны &lt;a href="http://blogs.msdn.com/b/windowsazurestorage/archive/2010/05/10/windows-azure-storage-abstractions-and-their-scalability-targets.aspx" target="_blank"&gt;scalability targets&lt;/a&gt;. Вы можете обнаружить очень интересные сведения о том что максимальное количество сообщений очереди, обрабатываемых в секунду – 500 (по другим сведениям это количество транзакций в секунду). Это очень-очень&amp;#160; мало. И надо не забывать что это предельное значение, на практике его достигнуть будет непросто. Кроме того латентность очереди может достигать 100ms.&lt;/p&gt;  &lt;p&gt;Первое что необходимо чтобы избежать высокой латентности на маленьких сообщениях в очереди - установить &lt;a href="http://msdn.microsoft.com/en-us/library/system.net.servicepointmanager.usenaglealgorithm.aspx"&gt;ServicePointManager.UseNagleAlgorithm&lt;/a&gt; значение false.&lt;/p&gt;  &lt;p&gt;Следующая проблема – максимальный размер сообщения в очереди – 8KB, так как для передачи используется Base64 кодировка, то реально данных можно передать около 6KB, кстати строки по-умолчанию не кодируются. Добрые люди уже придумали как решать такую проблему: &lt;a href="http://msdn.microsoft.com/en-us/library/windowsazure/hh690942(v=VS.103).aspx"&gt;http://msdn.microsoft.com/en-us/library/windowsazure/hh690942(v=VS.103).aspx&lt;/a&gt;&lt;/p&gt;  &lt;h4&gt;Масштабирование воркеров&lt;/h4&gt;  &lt;p&gt;Как вы думаете что будет если взять “наивную” реализацию воркера, как в &lt;a href="http://gandjustas.blogspot.com/2012/01/windows-azure-1.html" target="_blank"&gt;первом посте&lt;/a&gt; и запустить на Extra Large Instance, насколько быстрее будет работать?&lt;/p&gt;  &lt;p&gt;На самом деле вообще не будет быстрее. С этой точки зрения большое количество маленьких воркеров лучше чем один большой. Хотя тоже не лучший вариант по словам представителей Microsoft. С другой стороны куча маленьких воркеров будут пинать Azure Storage гораздо чаще, что несомненно отразится на ценнике. Того же можно добиться если запустить вручную несколько потоков с наивным циклом в воркере, развернутом на Medium instance или более крутой машине.&lt;/p&gt;  &lt;p&gt;Чтобы этого избежать надо использовать метод &lt;a href="http://msdn.microsoft.com/en-us/library/windowsazure/ee758358.aspx" target="_blank"&gt;CloudQueue.GetMessages&lt;/a&gt;. Пример ниже показывает кусок кода для итератора, который потом обрабатывается Rx.&lt;/p&gt;  &lt;pre class="brush: csharp;"&gt;while (true)
{
    var msgsObs = getMessages(32).ToListObservable();
    yield return msgsObs;
    var msgs = msgsObs[0];

    var hasMessages = false;
    foreach (var msg in msgs)
    {
        hasMessages = true;
        idleCount = 0;

        result.OnNext(msg);
    }

    if (!hasMessages)
    {
        var delay = CalulateDelay(idleCount++, MinimumIdleIntervalMs, MaximumIdleIntervalMs, 100);
        if (delay.TotalMilliseconds &amp;gt;= MaximumIdleIntervalMs)
        {
            yield break;
        }

        yield return Observable.Timer(delay).ToListObservable();
    }
}&lt;/pre&gt;

&lt;p&gt;Обратите внимание что вызов OnNext должен быть упорядочен, чтобы не возникало Race Condition. Это требование указано в &lt;a href="http://go.microsoft.com/fwlink/?LinkID=205219" target="_blank"&gt;Rx Design Guidelines&lt;/a&gt;, и если вы его не читали, то крайне рекомендую это сделать.&lt;/p&gt;

&lt;p&gt;Кроме того удаление сообщения из очереди в таком коде возлагается на внешний код. &lt;/p&gt;

&lt;p&gt;Пример:&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;from m in queue.ToObservable(notifications)
from _1 in Observable.Start(() =&amp;gt; /*work*/, Scheduler.TaskPool)
from _2 in queue.DeleteMessageAsync(m)
select Unit.Default;&lt;/pre&gt;

&lt;p&gt;Само это выражение не приводит ни к какому эффекту. Для него надо выполнить Subscribe чтобы запустить вычисления. Тогда будет использоваться TaskPool, который довольно эффективно распределяет вычисления по процессорам. Если вычисления длительные (более 10ms - 100ms), то лучше использовать Scheduler.NewThread. Если же у вас IO-bound код, то лучше будет использовать Scheduler.ThreadPool. &lt;/p&gt;

&lt;p&gt;Подходы, описанные выше помогут выжать максимум из очереди Windows Azure, оптимально расходуя ресурсы виртуальных машин при этом. Но что делать когда код упрется в ограничение количества сообщений в секунду. Ни добавление воркеров, ни увеличение толщины инстансов не поможет.&amp;#160; В таком случае может помочь секционирование. &lt;/p&gt;

&lt;p&gt;Вместо одной очереди вы создаете N очередей. При добавлении сообщения в очередь выбираете случайную. Считываете сразу из всех. Надо как-то разбираться из какой очереди пришло сообщение. Реализация такого нетривиальна и уже есть первый подобный проект на codeplex: &lt;a href="http://partitioncloudqueue.codeplex.com/"&gt;http://partitioncloudqueue.codeplex.com/&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Но Rx как всегда рулит и с его помощью очень просто сделать такой partitioning.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;На клиенте:&lt;/em&gt;&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;List&amp;lt;CloudQueue&amp;gt; queues = /*...*/;
var observers = queues.Select(q =&amp;gt; q.ToObserver(/*notifier*/))
                      .ToList();

var rnd = new Random();
var partitionedObserver = Observer.Create&amp;lt;CloudQueueMessage&amp;gt;(
        m =&amp;gt; observers[rnd.Next(observers.Count)].OnNext(m),
        e =&amp;gt; observers.ForEach(obs =&amp;gt; obs.OnError(e)),
        () =&amp;gt; observers.ForEach(obs =&amp;gt; obs.OnCompleted())
    );
partitionedObserver.OnNext(new CloudQueueMessage(/*message*/));&lt;/pre&gt;

&lt;p&gt;&lt;em&gt;На сервере:&lt;/em&gt;&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;IObservable&amp;lt;Unit&amp;gt; ProcessMessages(CloudQueue queue, /*notifier*/, /*scheduler*/)
{
    return from m in queue.ToObservable(/*notifier*/)
           from _1 in Observable.Start(/*action*/, /*scheduler*/)
           from _2 in queue.DeleteMessageAsync(m)
           select Unit.Default;
}

/*.....*/

List&amp;lt;CloudQueue&amp;gt; queues = /*...*/;
queues.Select(q =&amp;gt; ProcessMessages(q,/*notifier*/, /*scheduler*/))
      .Merge()
      .Subscribe();&lt;/pre&gt;

&lt;p&gt;Другой подход, позволяющий решить проблему ограничения на количество сообщений – пакетная передача. Вместо создания множества очередей, вы записываете множество сообщений в один пакет и предаете его. Для этих целей можно использовать CloudBlockBlob. Можно отдельными блоками загружать отдельные сообщения, а потом получить список блоков из блоба. В сообщении при этом передавать только url блоба.&lt;/p&gt;

&lt;h4&gt;Заключение&lt;/h4&gt;

&lt;p&gt;Все описанные выше способы помогут вам более эффективно реализовывать процессинг в Windows Azure. Для тех кто дочитал до сюда – сюрприз. Весь код с примерами использования есть на &lt;a href="http://rxcloud.codeplex.com/" target="_blank"&gt;codeplex&lt;/a&gt;, а также библиотека для работы с очередями доступна в &lt;a href="https://nuget.org/packages/RxCloud" target="_blank"&gt;NuGet&lt;/a&gt;.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1302241583051203640-8111188180151488846?l=gandjustas.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=YN-9UgewduQ:rOCbGensPvU:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=YN-9UgewduQ:rOCbGensPvU:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?i=YN-9UgewduQ:rOCbGensPvU:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=YN-9UgewduQ:rOCbGensPvU:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GandjustasBlog/~4/YN-9UgewduQ" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/GandjustasBlog/~3/YN-9UgewduQ/windows-azure-3.html</link><author>noreply@blogger.com (Станислав Выщепан)</author><thr:total>0</thr:total><feedburner:origLink>http://gandjustas.blogspot.com/2012/01/windows-azure-3.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-1302241583051203640.post-1547841525800103181</guid><pubDate>Tue, 10 Jan 2012 06:00:00 +0000</pubDate><atom:updated>2012-01-10T10:00:04.792+04:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Azure</category><category domain="http://www.blogger.com/atom/ns#">Reactive Extensions</category><title>Оптимизация процессинга в Windows Azure. Часть 2.</title><description>&lt;p&gt;&lt;a href="http://gandjustas.blogspot.com/2012/01/windows-azure-1.html" target="_blank"&gt;В первой части&lt;/a&gt; я показал сколько стоит использование воркер-ролей и очередей в Windows Azure и что с этим можно сделать. &lt;/p&gt;  &lt;p&gt;Довольной хороший подход – адаптировать интервал опроса новых сообщений и отключать опрос в случае их отсутствия продолжительное время. Но после выключения надо как-то включать.&lt;/p&gt;  &lt;p&gt;Для этого был создан extension-метод: &lt;/p&gt;  &lt;pre class="brush: csharp;"&gt;public static IObservable&amp;lt;CloudQueueMessage&amp;gt; ToObservable&amp;lt;T&amp;gt;(
                                                        this CloudQueue queue, 
                                                        IObservable&amp;lt;T&amp;gt; haveMoreMessages)&lt;/pre&gt;

&lt;p&gt;Этот метод возвращает сообщения очереди в виде IObservable коллекции. Включение опроса осуществляется появлением элемента в последовательности haveMoreMessages.&lt;/p&gt;

&lt;p&gt;Теперь о том как реализовать последовательность haveMoreMessages.&lt;/p&gt;

&lt;p&gt;Самый дешевый вариант взаимодействия между экземплярами ролей это internal wcf communication. Для того чтобы работать с WCF необходимо определить контракты.&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;[ServiceContract]
public interface IQueueNotifier
{
    [OperationContract(IsOneWay = true)]
    void MessageAdded(string queueName);

    [OperationContract(IsOneWay = true)]
    void NoMoreMessages(string queueName);
}&lt;/pre&gt;

&lt;p&gt;Контракт содержит всего два метода оповещения о новом сообщении в очереди и об окончании сообщений. &lt;/p&gt;

&lt;p&gt;Реализация тоже тривиальна:&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;public class QueueNotifier : IQueueNotifier
{
    private ISubject&amp;lt;string&amp;gt; moreMessages = new Subject&amp;lt;string&amp;gt;();
    private ISubject&amp;lt;string&amp;gt; queueCompleted = new Subject&amp;lt;string&amp;gt;();

    public IObservable&amp;lt;string&amp;gt; MoreMessages
    {
        get
        {
            return this.moreMessages;
        }
    }

    public IObservable&amp;lt;string&amp;gt; QueueCompleted
    {
        get
        {
            return this.queueCompleted;
        }
    }

    public void MessageAdded(string queueName)
    {
        moreMessages.OnNext(queueName);
    }

    public void NoMoreMessages(string queueName)
    {
        queueCompleted.OnNext(queueName);
    }
}&lt;/pre&gt;

&lt;p&gt;Далее комбинируя два потока получаем IObservable&amp;lt;Unit&amp;gt; пригодный для метода, описанного в начале поста.&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;public static IObservable&amp;lt;Unit&amp;gt; GetQueueNotifications(this QueueNotifier service, string queueName)
{
    return Observable.Create&amp;lt;Unit&amp;gt;(obs =&amp;gt;
    {
        var sub1 = service.MoreMessages
                          .Where(q =&amp;gt; q == queueName)
                          .Subscribe(q =&amp;gt; obs.OnNext(Unit.Default));

        var sub2 = service.QueueCompleted
                          .Where(q =&amp;gt; q == queueName)
                          .Subscribe(q =&amp;gt; obs.OnCompleted());

        return new CompositeDisposable(sub1, sub2);
    });
}&lt;/pre&gt;

&lt;p&gt;Теперь захостив QueueNotifier в воркере можно передавать ему оповещения из других ролей.&lt;/p&gt;

&lt;h4&gt;Клиентская сторона&lt;/h4&gt;

&lt;p&gt;Чтобы отправлять оповещения нужно создать ChannelFactory&amp;lt;IQueueNotifier&amp;gt; и получить экземпляр прокси на клиенте.&lt;/p&gt;

&lt;p&gt;Далее надо получить IObserver:&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;public static IObserver&amp;lt;Unit&amp;gt; CreateQueueNotifierObserver(this IQueueNotifier proxy, string queueName)
{
    return Observer.Create&amp;lt;Unit&amp;gt;(
                _ =&amp;gt; proxy.MessageAdded(queueName),
                _ =&amp;gt; proxy.NoMoreMessages(queueName),
                () =&amp;gt; proxy.NoMoreMessages(queueName)
            );
}&lt;/pre&gt;

&lt;p&gt;Надо помнить что экземпляров воркера может быть много и у вас получится по одному &lt;em&gt;наблюдателю&lt;/em&gt; на каждый инстанс воркера. При этом не надо передавать оповещение каждому воркеру, достаточно передать оповещение одному (случайному). В случае высокой нагрузки оповещения будут распределяться равномерно межу воркерами и никто их них не будет “спать”. В случае низкой нагрузки просыпаться будет только один воркер, экономя деньги.&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;public static IObserver&amp;lt;Unit&amp;gt; CombineObservers(List&amp;lt;IObserver&amp;lt;Unit&amp;gt;&amp;gt; notifiers)
{
    var rnd = new Random();

    return Observer.Create&amp;lt;Unit&amp;gt;(
            u =&amp;gt; notifiers[rnd.Next(notifiers.Count)].OnNext(u),
            e =&amp;gt; notifiers.ForEach(obs =&amp;gt; obs.OnError(e)),
            () =&amp;gt; notifiers.ForEach(obs =&amp;gt; obs.OnCompleted())
        );
}&lt;/pre&gt;

&lt;p&gt;Обратите внимание что OnCompleted рассылается всем воркерам, чтобы можно было остановить обработку сообщений.&lt;/p&gt;

&lt;p&gt;Остается только скомбинировать отправку сообщения в очередь с отправкой оповещения.&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;public static IObserver&amp;lt;CloudQueueMessage&amp;gt; ToObserver(this CloudQueue queue, IObserver&amp;lt;Unit&amp;gt; notifier)
{
    var addMessage = Observable.FromAsyncPattern&amp;lt;CloudQueueMessage&amp;gt;(queue.BeginAddMessage, queue.EndAddMessage);

    return Observer.Create&amp;lt;CloudQueueMessage&amp;gt;(
            m =&amp;gt; addMessage(m).Subscribe(notifier.OnNext, notifier.OnError),
            notifier.OnError,
            notifier.OnCompleted);
}&lt;/pre&gt;

&lt;p&gt;Таким образом получается достигнуть того что воркеры не обращаются постоянно к Azure Storage, экономя деньги и ресурсы виртуальных машин. При этом мы получили на клиенте и сервере очень простые интерфейсы, позволяющие тем не менее выполнять сложные действия&amp;#160; с ними.&lt;/p&gt;

&lt;p&gt;В следующей части дальнейшая оптимизация, библиотека и пример приложения.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1302241583051203640-1547841525800103181?l=gandjustas.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=O9RDXZs4jCk:-DKDh4hTezo:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=O9RDXZs4jCk:-DKDh4hTezo:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?i=O9RDXZs4jCk:-DKDh4hTezo:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=O9RDXZs4jCk:-DKDh4hTezo:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GandjustasBlog/~4/O9RDXZs4jCk" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/GandjustasBlog/~3/O9RDXZs4jCk/windows-azure-2.html</link><author>noreply@blogger.com (Станислав Выщепан)</author><thr:total>0</thr:total><feedburner:origLink>http://gandjustas.blogspot.com/2012/01/windows-azure-2.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-1302241583051203640.post-453820792260546635</guid><pubDate>Mon, 09 Jan 2012 06:00:00 +0000</pubDate><atom:updated>2012-01-09T10:00:03.547+04:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Azure</category><category domain="http://www.blogger.com/atom/ns#">Reactive Extensions</category><title>Оптимизация процессинга в Windows Azure. Часть 1.</title><description>&lt;p&gt;Для тех кто не в курсе: Windows Azure – “облачная” платформа Microsoft. Создавая приложения, работающие “в облаке”, у вас есть возможность разделять систему на “роли”. Бывают веб-роли, которые представляют из себя обычные веб-приложения,&amp;#160; бывают также worker-роли (далее &lt;em&gt;воркеры&lt;/em&gt;), предназначенные для вычислений. &lt;/p&gt;  &lt;p&gt;Для увеличения масштабируемости приложения используется очереди. Сообщения в очередях обрабатываются воркерами, а ставят сообщения чаще всего веб-роли или другие воркеры. Таким образом можно разбить какие-либо длительные операции на небольшие и обрабатывать их асинхронно на любом количестве узлов, так как очереди в Windows Azure специально проектировали для сценария множественных потребителей.&lt;/p&gt;  &lt;p&gt;Типовой код для воркера Windows Azure на C# такой:&lt;/p&gt;  &lt;pre class="brush: csharp;"&gt;while (true)
{
    var msg = queue.GetMessage();
    if (msg != null)
    {
        //do some work
        queue.DeleteMessage(msg);
    }
    else
    {
        Thread.Sleep(10000);
    }

    Trace.WriteLine(&amp;quot;Working&amp;quot;, &amp;quot;Information&amp;quot;);
}&lt;/pre&gt;

&lt;p&gt;Как вы думаете сколько стоит этот воркер. В смысле реальных денег потребляемых таким приложением, развернутым на Windows Azure.&lt;/p&gt;

&lt;p&gt;Для этого надо посмотреть цены: &lt;a href="https://www.windowsazure.com/en-us/pricing/details/"&gt;https://www.windowsazure.com/en-us/pricing/details/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Если задеплоить такую роль в одном small экземпляре, то получится $2,88 в день/$86,4 в месяц/~2600 рублей в месяц. Так? А вот и нет…&lt;/p&gt;

&lt;p&gt;Есть еще “скрытая” стоимость такой архитектуры, заключается она в том что транзакции к хранилищу тоже оплачиваются &lt;a href="https://www.windowsazure.com/en-us/pricing/details/#storage"&gt;https://www.windowsazure.com/en-us/pricing/details/#storage&lt;/a&gt;. Всего&amp;#160; $0.01 за 10,000 транзакций. Каждая транзакция – это один запрос к azure storage. &lt;/p&gt;

&lt;p&gt;Код выше выполняет один запрос каждые 10 секунд даже если нету никаких сообщений в очереди. 
  &lt;br /&gt;Стоимость такого кода получается 60*60*24*30/(10 * 1000) = $25,92 в месяц. вместе со стоимостью compute hours это выходит &lt;strong&gt;$112,32 в месяц&lt;/strong&gt;. &lt;strong&gt;И это даже если код не выполняет никакой работы!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Кроме того SLA гарантирует работоспособность роли 99,95% только при наличии минимум двух инстансов, так что для устойчивости надо еще умножить цену на 2. &lt;strong&gt;Итого $250 в месяц&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;Вывод&lt;/h4&gt;

&lt;p&gt;Архитектура, которую предлагает Microsoft для масштабирования довольно дорого стоит. Используйте код из примеров очень осторожно, он может увести ваш проект в большой минус.&lt;/p&gt;

&lt;h4&gt;Что делать?&lt;/h4&gt;

&lt;p&gt;Вариант первый – использовать &lt;a href="https://www.windowsazure.com/en-us/home/tour/service-bus/" target="_blank"&gt;service bus&lt;/a&gt;, в нем тоже есть очереди, но API позволяет в одной транзакции ожидать сообщения, а не сразу null возвращать при его отсутствии. &lt;/p&gt;

&lt;p&gt;Вариант второй – использовать адаптивную подстройку интервала опроса очереди и выключать опрос в случае отсутствия сообщений. &lt;/p&gt;

&lt;p&gt;Второй вариант кажется хорошей идеей так как позволяет масштабировать подход как “вниз”, так и “вверх”. Но тут возникает вопрос, а если мы прекратим прием сообщений, то как его потом возобновить? Видимо надо передать сообщение… Приходим снова к той же проблеме. &lt;/p&gt;

&lt;p&gt;Но сигнал к “пробуждению” читателя сообщений можно передавать по более дешевому каналу, например через wcf internal endpoint.&lt;/p&gt;

&lt;h4&gt;Реализация&lt;/h4&gt;

&lt;p&gt;Чтобы абстрагироваться от всех деталей с сообщениями, таймаутами и каналами удобно использовать библиотеку Rx. Я использую Experimental версию так как в ней собрано много нужных комбинаторов.&lt;/p&gt;

&lt;p&gt;Для начала надо вписать код в концепцию Rx. Длительные операции, вроде вызовов методов Cloud Storage и тайматуов сделать в виде IObservable.&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;public static IObservable&amp;lt;CloudQueueMessage&amp;gt; ObserveMessages(this CloudQueue queue)
{
    return Observable.Create&amp;lt;CloudQueueMessage&amp;gt;(obs =&amp;gt; Iterator(obs, queue));
}

private static IEnumerable&amp;lt;IObservable&amp;lt;object&amp;gt;&amp;gt; Iterator(
                                                    IObserver&amp;lt;CloudQueueMessage&amp;gt; result, 
                                                    CloudQueue queue)
{
    //Observable queue.GetMessage
    var getMessage = Observable.FromAsyncPattern&amp;lt;CloudQueueMessage&amp;gt;(
                                        queue.BeginGetMessage,
                                        queue.EndGetMessage);
    //Observable queue.DeleteMessage
    var deleteMessage = Observable.FromAsyncPattern&amp;lt;CloudQueueMessage&amp;gt;(
                                        queue.BeginDeleteMessage,
                                        queue.EndDeleteMessage);

    while (true)
    {
        //var msg = queue.GetMessage();               
        var msgObs = getMessage().ToListObservable();
        yield return msgObs;
        var msg = msgObs[0];

        if (msg != null)
        {
            //do some work
            result.OnNext(msg);

            //queue.DeleteMessage(msg);                     
            yield return deleteMessage(msg).ToListObservable();
        }
        else
        {
            //Thread.Sleep(10000);
            //Same pattern as above
            yield return Observable.Timer(TimeSpan.FromSeconds(10))
                                   .ToListObservable();
        }

        Trace.WriteLine(&amp;quot;Working&amp;quot;, &amp;quot;Information&amp;quot;);
    }
}&lt;/pre&gt;

&lt;p&gt;Теперь надо немного изменить код, сделав таймаут адаптивным.&lt;/p&gt;

&lt;p&gt;Функция вычисления таймаута:&lt;/p&gt;

&lt;pre class="brush: csharp; collapse: true;"&gt;private static TimeSpan CalulateDelay(int idleCount, int minimumIdleIntervalMs,  int maximumIdleIntervalMs, int deltaBackoffMs)
{
    // Calculate a new sleep interval value that will follow a random exponential back-off curve.
    int delta = (int)((Math.Pow(2.0, (double)idleCount) - 1.0) * (new Random()).Next((int)(deltaBackoffMs * 0.8), (int)(deltaBackoffMs * 1.2)));
    int interval = Math.Min(minimumIdleIntervalMs + delta, maximumIdleIntervalMs);

    // Pass the calculated interval to the dequeue task to enable it to enter into a sleep state for the specified duration.
    return TimeSpan.FromMilliseconds((double)interval);            
}&lt;/pre&gt;

&lt;p&gt;Честно украдена &lt;a href="http://msdn.microsoft.com/en-us/library/windowsazure/hh697709(v=vs.103).aspx" target="_blank"&gt;отсюда&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Сам код воркера:&lt;/p&gt;

&lt;pre class="brush: csharp; collapse: true;"&gt;var idleCount = 0;
while (true)
{
    var msgObs = getMessage().ToListObservable();
    yield return msgObs;
    var msg = msgObs[0];

    if (msg != null)
    {
        idleCount = 0;

        //do some work
        result.OnNext(msg);

        yield return deleteMessage(msg).ToListObservable();
    }
    else
    {
        var delay = 
                CalulateDelay(idleCount++, 
                              MinimumIdleIntervalMs, 
                              MaximumIdleIntervalMs, 
                              100);
        if (delay.TotalMilliseconds &amp;gt;= MaximumIdleIntervalMs)
        {
            yield break;
        }

        yield return Observable.Timer(delay).ToListObservable();
    }
}&lt;/pre&gt;

&lt;p&gt;Выключать цикл опроса сообщений мы научились, теперь попробуем научиться его включать. Будем считать что “внешний раздражитель”, который будет будить цикл выборки сообщений, выглядит как IObservable&amp;lt;T&amp;gt;.&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;public static IObservable&amp;lt;CloudQueueMessage&amp;gt; ObserveMessages&amp;lt;T&amp;gt;(
                                                this CloudQueue queue, 
                                                IObservable&amp;lt;T&amp;gt; haveMoreMessages)
{
    var iterator = Observable.Create&amp;lt;CloudQueueMessage&amp;gt;(
                                  obs =&amp;gt; Iterator(obs, queue));
    IDisposable subscription = null;

    return Observable.Create&amp;lt;CloudQueueMessage&amp;gt;(
        obs =&amp;gt; haveMoreMessages.Subscribe(
            _ =&amp;gt;
            {
                if (subscription == null)
                {
                    subscription = iterator.Subscribe(
                                                obs.OnNext, 
                                                obs.OnError, 
                                                () =&amp;gt; subscription = null);
                }
            }, 
            () =&amp;gt; subscription.Dispose() ));
}&lt;/pre&gt;

&lt;p&gt;Код получился запутанный, но при некоторой сноровке читается очень хорошо.&lt;/p&gt;

&lt;p&gt;На сегодня все. В следующей части я расскажу как сделать&amp;#160; пробуждение воркеров по сигналу и какими еще способами можно оптимизировать стоимость решения для Windows Azure.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1302241583051203640-453820792260546635?l=gandjustas.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=zwP2qUhJ-Sg:a3VlXu94E8Q:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=zwP2qUhJ-Sg:a3VlXu94E8Q:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?i=zwP2qUhJ-Sg:a3VlXu94E8Q:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=zwP2qUhJ-Sg:a3VlXu94E8Q:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GandjustasBlog/~4/zwP2qUhJ-Sg" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/GandjustasBlog/~3/zwP2qUhJ-Sg/windows-azure-1.html</link><author>noreply@blogger.com (Станислав Выщепан)</author><thr:total>0</thr:total><feedburner:origLink>http://gandjustas.blogspot.com/2012/01/windows-azure-1.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-1302241583051203640.post-7141352374966561775</guid><pubDate>Mon, 12 Dec 2011 06:00:00 +0000</pubDate><atom:updated>2011-12-12T10:00:10.015+04:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">DDD</category><category domain="http://www.blogger.com/atom/ns#">архитектура</category><title>Применимость DDD</title><description>&lt;p&gt;До сих пор не утихают холивары на тему DDD\rich vs anemic. С одной стороны апологеты DDD (domain driven design, дизайн на основе предметной области) твердят о том как это круто, с другой стороны говоря что оно не везде подходит. На вопрос где же оно подходит обычно затрудняются ответить или отвечают “for compex domain”, причем примеров применения такого встретить непросто.&lt;/p&gt;  &lt;a name='more'&gt;&lt;/a&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;Попробуем разобраться. Если отбросить всю философскую шелуху DDD, то придем к очень простой концепции жирной (rich, насыщенной) модели, &lt;a href="http://martinfowler.com/eaaCatalog/domainModel.html" target="_blank"&gt;описанной Фаулером&lt;/a&gt;. С одной стороны Фаулер предлагает поместить логику в классы “сущностей”, соответствующие данным предметной области. С другой стороны он прекрасно понимает что логика будет сложна и надо каким-то образом декомпозировать её. Кроме того есть логика, которая оперирует более чем одной сущностью и поместить её в один из классов сущностей не выгодно. Таким образом создаются классы сервисов, стратегий итп. Их всех объединяет свойство, что они не содержат данные предметной области и для работы обращаются к классам сущностей. По сути вся сложная логика располагается в этих самых сервисах. &lt;/p&gt;  &lt;p&gt;По мере увеличения сложности логики программы количество сервисов, стратегий и других классов не-сущностей растет, увеличивается связность. Кроме того жирная модель имеет тенденцию вытягивать много данных из внешнего хранилища для работы, поэтому по мере усложнения еще и быстродействие начинает страдать. &lt;/p&gt;  &lt;p&gt;Тут стоит отступить назад и посмотреть на общую картину. Как выглядит код приложения, использующий жирную модель (на основе модели в статье Фаулера):&lt;/p&gt;  &lt;pre class="brush: csharp;"&gt;class ContractsController
{
    public Result CalculateRecognitionsAction(int contractId)
    {
        var contract = repository.GetById(contractId);
        contract.CalculateRecognitions();
        repository.Save();
    }
}&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Contract.CalculateRecognitions&lt;/strong&gt; свою очередь выглядит как-то так:&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;public void CalculateRecognitions()
{
    this.Product.CalculateRecognitions(this);
}&lt;/pre&gt;

&lt;p&gt;А &lt;strong&gt;Product.CalculateRecognitions&lt;/strong&gt; выглядит так:&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;public void CalculateRecognitions(Contract c)
{
    var recognitions = recognitionService.CalculateRecognitions(this);
    c.SetRecognitions(recognitions);
}&lt;/pre&gt;

&lt;p&gt;Теперь попробуем выполнить простое преобразование: на верхнем уровне будем вызывать сервис, а не методы сущностей.&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;class ContractsController
{
    public Result CalculateRecognitionsAction(int contractId)
    {
        var p = repository.GetProductByContractId(contractId);
        var recognitions = recognitionsService.CalculateRecognitions(p);
        repository.SaveRecognitionsForContract(contractId, recognitions);
    }
}&lt;/pre&gt;

&lt;p&gt;Такой код меньше по объему, имеет меньшую связность межу классами, от этого он более гибок и лучше подается оптимизации.&lt;/p&gt;

&lt;h5&gt;Сдвиг предмета моделирования&lt;/h5&gt;

&lt;p&gt;Если посмотреть какие концептуальные изменения произошли в коде в примере выше, то становится понятно что центральным объектом у нас стал RecognitionsService, а не Contract. То есть вместо &lt;em&gt;модели исходных данных&lt;/em&gt; (&lt;em&gt;предметной области&lt;/em&gt;) задачи мы начали использовать &lt;em&gt;модель решения&lt;/em&gt; этой задачи. Модели исходных данных никуда не делась, просто её роль в решении стала гораздо меньше. Такая модель называется стройной (&lt;em&gt;anemic&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;Очевидно что &lt;em&gt;модель решения задачи&lt;/em&gt; для решения задачи подходит лучше чем &lt;em&gt;модель исходных данных&lt;/em&gt; задачи. Ведь моделировать &lt;em&gt;экскаватор&lt;/em&gt; правильнее чем писать &lt;em&gt;земля.Копайся() &lt;/em&gt;(спасибо за метафору &lt;a href="http://www.rsdn.ru/Users/5743.aspx" target="_blank"&gt;Sinclair&lt;/a&gt;). Причем чем сложнее задача, тем выгоднее строить модель решения, а не исходных данных (предметной области). &lt;/p&gt;

&lt;p&gt;Таким образом DDD не подходит для сложных задач.&lt;/p&gt;

&lt;h5&gt;Лирическое отступление&lt;/h5&gt;

&lt;p&gt;Если вы думаете что вынося всю логику из класса Product в ProductService\ProductHelper\ProductManager вы получаете модель решения, то вы жестоко ошибаетесь. Фактически это будет DDD, только в худшем его проявлении.&lt;/p&gt;

&lt;h4&gt;&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h5&gt;Сложные предметные области&lt;/h5&gt;

&lt;p&gt;Но все же стоит рассмотреть случай сложной предметной области. Когда исходные данные связаны между собой нетривиальными отношениями, которые надо поддерживать независимо от операций, которые производятся с данными предметной области. При этом сама предметная область состоит из множества разных типов.&lt;/p&gt;

&lt;p&gt;В такой ситуации вроде как DDD должен рулить со страшной силой, так как все правила будут упрятаны в сами сущности и не будет снаружи путей нарушить их. Фактически проверки будут срабатывать на изменение свойств.&lt;/p&gt;

&lt;p&gt;Но если подойти с точки зрения моделирования решения, то становится понятно что надо проверять правила не при любом изменении свойства, а при попытке записать эти изменения во внешнее хранилище. Для этого можно создавать классы валидаторы, которые будут вызываться в репозитарии (роль которого может играть ORM).&lt;/p&gt;

&lt;p&gt;И снова такое решение гораздо боле гибкое,чем жирная модель. Эти же валидаторы можно приспособить для валидации ввода пользователя, для возвращения ему осмысленных ошибок, а не исключений. Кроме того можно предусмотреть сохранение “черновиков” во внешнем хранилище, которые не проходят всех правил. &lt;/p&gt;

&lt;h5&gt;Так где же область применимости DDD?&lt;/h5&gt;

&lt;p&gt;Как не удивительно, но DDD хорошо работает на &lt;em&gt;простых задачах&lt;/em&gt;, там где нет необходимости разделять систему на слои, уменьшать связность путем выноса логики в отдельные стратегии итд. В простых задачах&lt;em&gt; модель исходных данных&lt;/em&gt; &lt;em&gt;(предметной области)&lt;/em&gt; и &lt;em&gt;модель решения&lt;/em&gt; почти совпадают.&lt;/p&gt;

&lt;p&gt;Например взять блог. Обычный блог как например этот. Моделью предметной области являются: блог, посты, комменты, страницы, виджеты в интерфейсе. Решение включает в себя функции: создать пост, изменить пост, удалить пост, получить список, поменять настройки блога. Все эти функции вполне можно поместить в класс Блога, а умный ORM разберется как потом все изменения положить в БД.&lt;/p&gt;

&lt;h5&gt;Но почему DDD столь популярен?&lt;/h5&gt;

&lt;p&gt;Именно потому что DDD хорошо работает на простых примерах. Ведь в книгах и презентациях невозможно привести пример на 20kloc. А на примерах в 100 строк DDD выглядит очевидно и очень привлекательно.&lt;/p&gt;

&lt;p&gt;Другая причина заключается в том что DDD это не только подход к проектированию, но это еще и подход к анализу. Именно с точки зрения анализа DDD показывает себя хорошо. Предлагает некоторый системный подход к анализу требований, выделению областей, взаимному обучению экспертов со стороны заказчика и разработчика.&lt;/p&gt;

&lt;h5&gt;Заключение&lt;/h5&gt;

&lt;p&gt;Не стоит слепо следовать DDD. У догматичного DDD очень узкая область применения. Старайтесь моделировать решение задачи, не зацикливайтесь на моделировании исходных данных. Всегда оценивайте как то или иное решение повлияет на качество кода безотносительно DDD, ООП или других баззвордов.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1302241583051203640-7141352374966561775?l=gandjustas.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=pI8rXhQqI1k:omMV_ezk17c:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=pI8rXhQqI1k:omMV_ezk17c:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?i=pI8rXhQqI1k:omMV_ezk17c:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=pI8rXhQqI1k:omMV_ezk17c:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GandjustasBlog/~4/pI8rXhQqI1k" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/GandjustasBlog/~3/pI8rXhQqI1k/ddd.html</link><author>noreply@blogger.com (Станислав Выщепан)</author><thr:total>102</thr:total><feedburner:origLink>http://gandjustas.blogspot.com/2011/12/ddd.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-1302241583051203640.post-1257112628637284684</guid><pubDate>Mon, 21 Nov 2011 06:00:00 +0000</pubDate><atom:updated>2011-11-21T10:00:01.591+04:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">C#</category><title>В поисках неподвижной точки</title><description>&lt;p&gt;Наверное все знают что в C# 3.0 лямбда выражения, которые позволяют записывать анонимные функции (то есть функции без имени).    &lt;br /&gt;Например: &lt;/p&gt;  &lt;pre class="code"&gt;seq.Select(x =&amp;gt; x * x);&lt;/pre&gt;

&lt;p&gt;Выражение x =&amp;gt; x*x является функцией одного аргумента и возвращает значение квадрата числа. &lt;/p&gt;

&lt;p&gt;А теперь попробуем&amp;#160; записать функцию факториала:&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;int&lt;/span&gt;, &lt;span style="color: blue"&gt;int&lt;/span&gt;&amp;gt; f = x =&amp;gt; x &amp;gt; 1 ? x * f(x - 1) : 1;&lt;/pre&gt;

&lt;p&gt;Компилятор C# такое выражение не компилирует. Ругается на неинициализированную переменную f в правой части. Кстати &lt;a href="http://msdn.com/roslyn" target="_blank"&gt;roslyn&lt;/a&gt; такое прожевывает нормально. Тем не менее код выше не является выражением, его нельзя передать параметром в функцию.&lt;/p&gt;

&lt;p&gt;Попробуем превратить его в выражение.&lt;/p&gt;

&lt;pre class="code"&gt;fact = f =&amp;gt; x =&amp;gt; x &amp;gt; 1 ? x * f(x - 1) : 1;&lt;/pre&gt;

&lt;p&gt;Тип выражения справа получится &lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;int&lt;/span&gt;,&lt;span style="color: blue"&gt;int&lt;/span&gt;&amp;gt;, &lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;int&lt;/span&gt;,&lt;span style="color: blue"&gt;int&lt;/span&gt;&amp;gt;&amp;gt;, параметром передается рекурсивный вызов, чтобы его сформировать надо снова подставить рекурсивный вызов в функцию.&amp;#160; Получится что-то вроде бесконечного вызова 

  &lt;br /&gt;fact(fact(fact(fact(…. но реально число вызовов конечно. &lt;/p&gt;

&lt;h5&gt;Немного теории&lt;/h5&gt;

&lt;p&gt;Для функций f которая принимает аргумент и возвращают значения из одного и того же множества (на C# это записывается как Func&amp;lt;T,T&amp;gt;, а в математике T –&amp;gt; T) может существовать “&lt;a href="http://ru.wikipedia.org/wiki/%D0%9D%D0%B5%D0%BF%D0%BE%D0%B4%D0%B2%D0%B8%D0%B6%D0%BD%D0%B0%D1%8F_%D1%82%D0%BE%D1%87%D0%BA%D0%B0" target="_blank"&gt;неподвижная точка&lt;/a&gt;” x, для которой f(x) = x. Чтобы находить неподвижные точки можно построить комбинатор g:(T-&amp;gt;T)-&amp;gt;T, такой что g(f) = x и f(x) = x. Функция g называется &lt;a href="http://en.wikipedia.org/wiki/Fixed-point_combinator" target="_blank"&gt;комбинатором неподвижной точки&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;В лямбда исчислении есть теоремы доказывающие существование неподвижных точек у некоторых функций и формулы&amp;#160; комбинаторов. Не все формулы можно перенести в типизированные языки.&lt;/p&gt;

&lt;p&gt;Краткая формула для рекурсивного комбинатора неподвижной точки Y выглядит как Y(g) = g(Y(g)). Если выполнить подстановку то получится g(g(Y(g))), выполняя подстановку бесконечное число раз получим как раз то что нам нужно для факториала.&lt;/p&gt;

&lt;h5&gt;Вернемся к практике&lt;/h5&gt;

&lt;p&gt;Попробуем написать на C#&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;static &lt;/span&gt;T Y&amp;lt;T&amp;gt;(&lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;T, T&amp;gt; f)
{
    &lt;span style="color: blue"&gt;return &lt;/span&gt;f(Y(f));
}&lt;/pre&gt;

&lt;p&gt;Но язык C# использует энергичные вычисления и Y-комбинатор сразу попытается посчитать бесконечную рекурсию. Что приведет к StackOverflowException.&lt;/p&gt;

&lt;p&gt;Ленивость вычислений как всегда вводится через лямбды.&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;static &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;T, T&amp;gt; Y&amp;lt;T&amp;gt;(&lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;T, T&amp;gt;, &lt;span style="color: #2b91af"&gt;Func&lt;/span&gt;&amp;lt;T, T&amp;gt;&amp;gt; f)
{
    &lt;span style="color: blue"&gt;return &lt;/span&gt;x =&amp;gt; f(Y(f))(x);
}&lt;/pre&gt;

&lt;p&gt;После этого вполне можно написать следующий код:&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;fact = Y&amp;lt;&lt;span style="color: blue"&gt;int&lt;/span&gt;&amp;gt;(f =&amp;gt; x =&amp;gt; x &amp;gt; 1 ? x * f(x - 1) : 1);&lt;/pre&gt;


&lt;p&gt;Или например &lt;/p&gt;

&lt;pre class="code"&gt;seq.Select(Y&amp;lt;&lt;span style="color: blue"&gt;int&lt;/span&gt;&amp;gt;(f =&amp;gt; x =&amp;gt; x &amp;gt; 1 ? x * f(x - 1) : 1));&lt;/pre&gt;

&lt;p&gt;Таким образом получили возможность записывать анонимную рекурсию в виде выражения.&lt;/p&gt;

&lt;h5&gt;Заключение&lt;/h5&gt;

&lt;p&gt;Знание фундаментальной теории очень помогает писать программы и зачастую дает возможность улучшить их крайне неожиданными способами. Изучение таких тем никогда не будет лишним багажом.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1302241583051203640-1257112628637284684?l=gandjustas.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=azI4r25gE7g:jIqDOdb0ptI:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=azI4r25gE7g:jIqDOdb0ptI:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?i=azI4r25gE7g:jIqDOdb0ptI:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=azI4r25gE7g:jIqDOdb0ptI:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GandjustasBlog/~4/azI4r25gE7g" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/GandjustasBlog/~3/azI4r25gE7g/blog-post.html</link><author>noreply@blogger.com (Станислав Выщепан)</author><thr:total>4</thr:total><feedburner:origLink>http://gandjustas.blogspot.com/2011/11/blog-post.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-1302241583051203640.post-5798238045998188581</guid><pubDate>Mon, 24 Oct 2011 15:17:00 +0000</pubDate><atom:updated>2011-10-24T19:17:02.142+04:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">SharePoint</category><title>Октябрьская встреча Russian SharePoint User Group</title><description>&lt;p&gt;Ссылка на анонс на сайте RUSUG: &lt;a href="http://rusug.net/News/Lists/Posts/Post.aspx?ID=64"&gt;http://rusug.net/News/Lists/Posts/Post.aspx?ID=64&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Один из докладов на встрече, про использование поиска в приложениях SharePoint, буду читать я. Чтобы больше удовлетворить интерес и потребности аудитории предлагаю выбрать интересующие темы из списка ниже:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Настройка поиска SharePoint Server&lt;/li&gt;    &lt;li&gt;Out-of-box возможности поиска&lt;/li&gt;    &lt;li&gt;Кастомизация out-of-box функциональности&lt;/li&gt;    &lt;li&gt;Использование серверного API поиска&lt;/li&gt;    &lt;li&gt;Использование клиентского API поиска в sandboxed решениях&lt;/li&gt;    &lt;li&gt;Архитектура поиска SharePoint Server&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;Все темы в пределах одного доклада не смогу рассказать, но 3-4 из них вполне могу пройти достаточно подробно.&lt;/p&gt;  &lt;p&gt;Пишите пожелания в комментах. Приходите на встречу. Но даже если вы не сможете прийти на встречу, то будет видеозапись докладов и ваши пожелания будут учтены.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1302241583051203640-5798238045998188581?l=gandjustas.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=hvkBctoK1Pk:GvwItSsJm-E:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=hvkBctoK1Pk:GvwItSsJm-E:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?i=hvkBctoK1Pk:GvwItSsJm-E:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=hvkBctoK1Pk:GvwItSsJm-E:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GandjustasBlog/~4/hvkBctoK1Pk" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/GandjustasBlog/~3/hvkBctoK1Pk/russian-sharepoint-user-group.html</link><author>noreply@blogger.com (Станислав Выщепан)</author><thr:total>1</thr:total><feedburner:origLink>http://gandjustas.blogspot.com/2011/10/russian-sharepoint-user-group.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-1302241583051203640.post-8673852237915925973</guid><pubDate>Mon, 17 Oct 2011 06:00:00 +0000</pubDate><atom:updated>2011-10-17T10:00:03.858+04:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">SharePoint</category><title>Почему вам нужен SharePoint</title><description>&lt;p&gt;Последнее время на различных конференциях я слышу один и тот же вопрос:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;А зачем мне нужен SharePoint?&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Вопрос банальный, частота его задавания связана с тем что очень много крупных и не очень компаний получают SharePoint вместе с различными пакетами программ Microsoft. Но вразумительных ответов на этот вопрос я пока не слышал.&lt;/p&gt;  &lt;p&gt;Ниже “краткий” ответ, может быть вы найдете что-нибудь для себя.&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Если вы &lt;em&gt;руководитель или ИТ-директор&lt;/em&gt; компании более 5 человек и       &lt;ul&gt;       &lt;li&gt;Используете&amp;#160; MS Office &lt;/li&gt;        &lt;li&gt;Отправляете документы по электронной почте &lt;/li&gt;        &lt;li&gt;Использует расшаренные папки для хранения документов в электронном виде &lt;/li&gt;        &lt;li&gt;У вас есть процессы согласования и утверждения документов &lt;/li&gt;        &lt;li&gt;Храните данные в Excel или Access &lt;/li&gt;        &lt;li&gt;Хотите создать базу знаний &lt;/li&gt;        &lt;li&gt;Хотите развернуть helpdesk &lt;/li&gt;        &lt;li&gt;Пользуетесь средствами средствами Microsoft BI &lt;/li&gt;        &lt;li&gt;Хотите отображать данные из разных источников в одном месте &lt;/li&gt;        &lt;li&gt;Хотите развернуть корпоративный портал для сотрудников &lt;/li&gt;     &lt;/ul&gt;      &lt;br /&gt;если одно из вышеперечисленного верно, то вам однозначно нужен SharePoint. Он поможет вам создать единое хранилище документов и табличных данных с богатыми возможностями отображения и поиска. SharePoint позволяет создавать решения без среды разработки и написания кода. Продвинутые пользователи самостоятельно смогут создавать и улучшать решения в SharePoint.       &lt;br /&gt;      &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;Если вы &lt;em&gt;менеджер проектов&lt;/em&gt;, то вам должны быть знакомы продукты MS Project и Project Server. Последний является надстройкой над SharePoint.       &lt;br /&gt;Но даже без Projet Server вы можете:       &lt;ul&gt;       &lt;li&gt;Создавать отдельные сайты для проектов несколькими кликами мыши, где можно будет размещать и согласовывать документы &lt;/li&gt;        &lt;li&gt;Хранить и отображать на портале списки задач из Microsoft Project &lt;/li&gt;        &lt;li&gt;Отслеживать риски и проблемы &lt;/li&gt;        &lt;li&gt;При необходимости вывести создать на портале SharePoint интерфейс к другим системам управления проектами &lt;/li&gt;        &lt;li&gt;Получать сводку по вашим проектами &lt;/li&gt;        &lt;li&gt;Получать отчеты и KPI на портале          &lt;br /&gt;&lt;/li&gt;     &lt;/ul&gt;      &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;Если вы &lt;em&gt;архитектор или ведущий разработчик&lt;/em&gt; и разрабатываете корпоративный софт, то вам нужен SharePoint потому что:       &lt;ul&gt;       &lt;li&gt;он включает в себя возможности управления документами &lt;/li&gt;        &lt;li&gt;он имеет надежную систему разграничения доступа &lt;/li&gt;        &lt;li&gt;он интегрируется с MS Office &lt;/li&gt;        &lt;li&gt;он позволяет искать по всему содержимому &lt;/li&gt;        &lt;li&gt;он поддерживает длительные рабочие процессы, которые могут продолжаться больше чем время непрерывной работы серверов &lt;/li&gt;        &lt;li&gt;он имеет модульный пользовательский интерфейс &lt;/li&gt;        &lt;li&gt;он позволяет интегрироваться с другими системами &lt;/li&gt;        &lt;li&gt;он поддерживает масштабируемость всех своих компонент &lt;/li&gt;        &lt;li&gt;он почти весь функционал SharePoint поддается расширению и кастомизации &lt;/li&gt;        &lt;li&gt;содержит систему установки и удаления приложений, гораздо проще, чем написание инсталляторов вручную &lt;/li&gt;     &lt;/ul&gt;      &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;Если вы &lt;em&gt;IT-специалист&lt;/em&gt;, то вам обязательно нужен SharePoint. Он вам позволит:       &lt;ul&gt;       &lt;li&gt;Собирать в одном месте данные из различных систем &lt;/li&gt;        &lt;li&gt;Отображать таблицы, графики, отчеты, KPI на портале &lt;/li&gt;        &lt;li&gt;Автоматизировать процессы процессы организации с помощью простых инструментов &lt;/li&gt;        &lt;li&gt;Размещать веб-контент не имея навыков веб-разработки &lt;/li&gt;        &lt;li&gt;Управлять множеством сервисов со сложной топологией с помощью простого графического интерфейса &lt;/li&gt;        &lt;li&gt;Заскриптовать любые действия с помощью PowerShell &lt;/li&gt;     &lt;/ul&gt;      &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;Если вы &lt;em&gt;рядовой .NET разработчик&lt;/em&gt;, то вы сможете в SharePoint:       &lt;ul&gt;       &lt;li&gt;Применить уже имеющиеся навыки          &lt;ul&gt;           &lt;li&gt;для разработки интерфейса &lt;/li&gt;            &lt;li&gt;для создания решений по интеграции с другими системами &lt;/li&gt;            &lt;li&gt;для создания рабочих процессов &lt;/li&gt;         &lt;/ul&gt;       &lt;/li&gt;        &lt;li&gt;Изучив платформу более детально вы сможете создавать любые решения и превратитесь из рядового разработчика в высокооплачиваемого специалиста :) &lt;/li&gt;     &lt;/ul&gt;      &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;Если вы &lt;em&gt;веб-разработчик&lt;/em&gt;, то ваши навыки будут очень востребованы в среде SharePoint       &lt;ul&gt;       &lt;li&gt;для брендинга портала, это сейчас очень востребованная тема &lt;/li&gt;        &lt;li&gt;для разработки макетов веб-страниц для размещения контента          &lt;br /&gt;(html + css + js) &lt;/li&gt;        &lt;li&gt;для создания представлений данных и результатов поиска          &lt;br /&gt;(xslt + html + css + js) &lt;/li&gt;        &lt;li&gt;для приложений на javascript или silverlight, большая часть функциональности SharePoint доступна на клиентской стороне &lt;/li&gt;     &lt;/ul&gt;   &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;Несмотря на богатые возможности примеров успешных внедрений не так много, как хотелось бы. Это связано с тем что платформа SharePoint сложна, а специалистов не хватает. По большей части не хватает именно разработчиков, которые хорошо владеют функционалом и могут собрать из него решение.&lt;/p&gt;  &lt;p&gt;Если у вас будут возникать вопросы по SharePoint , то присоединяйтесь с сообществу &lt;a href="http://area51.stackexchange.com/proposals/35899/sharepoint-in-russian"&gt;http://area51.stackexchange.com/proposals/35899/sharepoint-in-russian&lt;/a&gt;, поддержите его развитие и вы сможете получать много полезной информации.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1302241583051203640-8673852237915925973?l=gandjustas.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=iqs7f2fRfyo:mdlpcW0oqSo:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=iqs7f2fRfyo:mdlpcW0oqSo:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?i=iqs7f2fRfyo:mdlpcW0oqSo:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=iqs7f2fRfyo:mdlpcW0oqSo:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GandjustasBlog/~4/iqs7f2fRfyo" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/GandjustasBlog/~3/iqs7f2fRfyo/sharepoint_17.html</link><author>noreply@blogger.com (Станислав Выщепан)</author><thr:total>4</thr:total><feedburner:origLink>http://gandjustas.blogspot.com/2011/10/sharepoint_17.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-1302241583051203640.post-2516194930425709393</guid><pubDate>Fri, 14 Oct 2011 06:00:00 +0000</pubDate><atom:updated>2011-10-14T10:00:01.754+04:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">js</category><category domain="http://www.blogger.com/atom/ns#">SharePoint</category><title>Решение задач. Задача таймера, совсем конец.</title><description>&lt;p&gt;Посты в этой серии:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;&lt;a href="http://gandjustas.blogspot.com/2011/08/sharepoint.html"&gt;Список задач для проверки навыков&lt;/a&gt;. &lt;/li&gt;    &lt;li&gt;&lt;a href="http://gandjustas.blogspot.com/2011/10/blog-post.html"&gt;Создание задачи таймера&lt;/a&gt;. &lt;/li&gt;    &lt;li&gt;&lt;a href="http://gandjustas.blogspot.com/2011/10/blog-post_11.html"&gt;Использование подходящих классов&lt;/a&gt;. &lt;/li&gt;    &lt;li&gt;&lt;a href="http://gandjustas.blogspot.com/2011/10/blog-post_13.html" target="_blank"&gt;Передача команд задаче таймера&lt;/a&gt;. &lt;/li&gt;    &lt;li&gt;Взаимодействие веб-фронтэнда с задачами таймера (&lt;em&gt;этот пост&lt;/em&gt;). &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;В &lt;a href="http://gandjustas.blogspot.com/2011/10/blog-post_13.html" target="_blank"&gt;прошлом посте&lt;/a&gt; был показан способ создать worker, который будет получать сообщения от frontend и обрабатывать их.&lt;/p&gt;  &lt;p&gt;Теперь необходимо придумать как оправлять эти сообщения. В 2010 есть почти универсальный способ для размещения функционала – Ribbon. Даже если у вас возникает непреодолимое желание сделать ссылку\кнопку\пункт меню в другом месте, то скорее всего для целей usability надо будет повторить его в ribbon.&lt;/p&gt;  &lt;p&gt;Для того чтобы добавить кнопку в ribbon надо создать custom action, я для этого использую &lt;a href="http://cksdev.codeplex.com/" target="_blank"&gt;cks:dev&lt;/a&gt;. Руководство по расширению риббона можно найти как &lt;a href="http://msdn.microsoft.com/en-us/library/gg552606.aspx" target="_blank"&gt;на MSDN&lt;/a&gt;, так и в &lt;a href="http://www.wictorwilen.se/Post/Creating-a-SharePoint-2010-Ribbon-extension-part-1.aspx" target="_blank"&gt;блогах&lt;/a&gt; &lt;a href="http://www.sharemuch.com/2009/12/16/extending-sharepoint-2010-ribbon/" target="_blank"&gt;глубоко&lt;/a&gt;&amp;#160;&lt;a href="http://www.sharemuch.com/2009/12/16/extending-sharepoint-2010-ribbon/" target="_blank"&gt;уважаемых&lt;/a&gt; &lt;a href="http://www.sharepointnutsandbolts.com/2010/01/customizing-ribbon-part-1-creating-tabs.html" target="_blank"&gt;людей&lt;/a&gt; (надеюсь меня не забанят за такое количество ссылок).&lt;/p&gt;  &lt;p&gt;Мой код получился такой:&lt;/p&gt;  &lt;pre class="brush: xml;"&gt;&amp;lt;CustomAction Id=&amp;quot;CleanupLibraryButton&amp;quot;
              Title=&amp;quot;Cleanup&amp;quot;
              RegistrationType=&amp;quot;List&amp;quot;
              RegistrationId=&amp;quot;101&amp;quot;
              Location=&amp;quot;CommandUI.Ribbon&amp;quot;&amp;gt;
  &amp;lt;CommandUIExtension&amp;gt;
    &amp;lt;CommandUIDefinitions&amp;gt;
      &amp;lt;CommandUIDefinition Location=&amp;quot;Ribbon.Library.Settings.Controls._children&amp;quot;&amp;gt;
        &amp;lt;Button Id=&amp;quot;Ribbon.Library.Settings.Cleanup&amp;quot;
                Command=&amp;quot;CleanupLibraryCommand&amp;quot;
                TemplateAlias=&amp;quot;o2&amp;quot;
                LabelText=&amp;quot;Cleanup&amp;quot;
                Sequence=&amp;quot;100&amp;quot;
                Image16by16=&amp;quot;/_layouts/images/warning16by16.gif&amp;quot;
                Image32by32=&amp;quot;/_layouts/images/CRIT_32.GIF&amp;quot;                   
                /&amp;gt;
      &amp;lt;/CommandUIDefinition&amp;gt;
    &amp;lt;/CommandUIDefinitions&amp;gt;
    &amp;lt;CommandUIHandlers&amp;gt;
      &amp;lt;CommandUIHandler Command=&amp;quot;CleanupLibraryCommand&amp;quot; 
                        CommandAction=&amp;quot;???&amp;quot;/&amp;gt;
    &amp;lt;/CommandUIHandlers&amp;gt;
  &amp;lt;/CommandUIExtension&amp;gt;
&amp;lt;/CustomAction&amp;gt;&lt;/pre&gt;

&lt;p&gt;Немного громоздко, но если разобраться в &lt;a href="http://msdn.microsoft.com/en-us/library/ff458369.aspx" target="_blank"&gt;схеме&lt;/a&gt;, то довольно очевидно. Кнопка добавляется в последнюю группу кнопок на закладке “Билиотека”.&lt;/p&gt;

&lt;p&gt;Теперь самый интересный вопрос, что написать в CommandAction, где я в схеме поставил вопросы. &lt;/p&gt;

&lt;p&gt;Самый прямолинейный способ – указать ссылку на application page, который вызовет site.AddWorkItem, но это кардинально противоречит самой идее ribbon.&lt;/p&gt;

&lt;p&gt;&amp;#160;&lt;/p&gt;

&lt;h5&gt;SharePoint 2007 way&lt;/h5&gt;

&lt;p&gt;В SharePoint 2007 часто применялся delegate control, и действия в интерфейсе выполняли postback, а этот самый контрол обрабатывал форму и производил действия.&lt;/p&gt;

&lt;p&gt;Для начала добавлю еще один cutom action с location равным ScriptLink. В таком custom action можно размещать ссылку на javascript или блок кода, который будет выведен на каждой странице.&lt;/p&gt;

&lt;pre class="brush: xml;"&gt;&amp;lt;CustomAction 
    Location=&amp;quot;ScriptLink&amp;quot;
    ScriptSrc=&amp;quot;~site/_layouts/CleanupTimerJob/script.js&amp;quot; /&amp;gt;&lt;/pre&gt;

&lt;p&gt;Команда для ribbon будет выглядеть так:&lt;/p&gt;

&lt;pre class="brush: xml;"&gt;&amp;lt;CommandUIHandler 
    Command=&amp;quot;CleanupLibraryCommand&amp;quot; 
    CommandAction=&amp;quot;javascript:submitLibraryToCleanup()&amp;quot;/&amp;gt;&lt;/pre&gt;

&lt;p&gt;В самом же js файле будет простой код:&lt;/p&gt;

&lt;pre class="brush: js;"&gt;function submitLibraryToCleanup() {
    var listId = SP.ListOperation.Selection.getSelectedList();
    if (listId) {
        __doPostBack('CleanupPostBackEvent', listId);
    }
}&lt;/pre&gt;

&lt;p&gt;SP.ListOperation.Selection – класс, позволяющий получать данные о текущем представлении списка. Представлений списка на странице может быть больше одного, а ribbon – один.&lt;/p&gt;

&lt;p&gt;Функция __doPostBack создается веб-формами, в первом параметре обычно указывается id элемента управления, которому адресуется postback, а во втором передаются параметры. Но узнать id нашего delegate control (который будет ниже) не представляется возможным, поэтому требуется заранее определенная константа и ручной анализ в теле контрола.&lt;/p&gt;

&lt;p&gt;Сам контрол:&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;[Guid(&amp;quot;0a3b8df1-0a43-485d-be83-983c1df8b30d&amp;quot;)]
public class PostbackHandler : WebControl
{
    protected override void OnLoad(EventArgs e)
    {
        if (this.Page.Request[&amp;quot;__EVENTTARGET&amp;quot;] == Constants.CleanupPostBackEvent)
        {
            var listId = new Guid(this.Page.Request[&amp;quot;__EVENTARGUMENT&amp;quot;]);
            var web = SPContext.Current.Web;
                            
            SPSecurity.RunWithElevatedPrivileges(() =&amp;gt;
                {
                    using (var site = new SPSite(SPContext.Current.Site.ID))
                    {
                        site.AddWorkItem(
                            new Guid(), DateTime.UtcNow, Constants.WorkItemType,
                            web.ID, listId, -1,
                            true, new Guid(), web.ID,
                            web.CurrentUser.ID, null, null, new Guid());
                    }
                });
        }
    }
}&lt;/pre&gt;

&lt;p&gt;Основное внимание стоит уделить функции &lt;a href="http://msdn.microsoft.com/en-us/library/ms476803.aspx" target="_blank"&gt;AddWorkItem&lt;/a&gt;. Во-первых её необходимо вызывать с правами администратора семейства сайтов, иначе будет ошибка. Во-вторых при указании времени доставки WorkItem необходимо указывать время в формате UTC. В-третьих, даже если не используются параметры listItemId и&amp;#160; userId, то все равно надо указывать ненулевые значения иначе SharePoint попытается записать в базу null, что приведет к ошибке так как эти поля not null. И в-четвертых лучше всего указывать batchId, такой же как у webId, если нет других соображений. Судя по документации msdn и реализации &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.office.server.utilities.timerjobutility.aspx" target="_blank"&gt;TimerJobUtility&lt;/a&gt; это может сэкономить ресурсы.&lt;/p&gt;

&lt;p&gt;Чтобы ваш delegate control заработал необходимо добавить элемент в фичу. Здесь также поможет &lt;a href="http://cksdev.codeplex.com/"&gt;cks:dev&lt;/a&gt;, в нем уже есть шаблон. &lt;/p&gt;

&lt;pre class="brush: xml;"&gt;&amp;lt;Control Id=&amp;quot;AdditionalPageHead&amp;quot; 
         Sequence=&amp;quot;1000&amp;quot; 
         ControlAssembly=&amp;quot;$SharePoint.Project.AssemblyFullName$&amp;quot; 
         ControlClass=&amp;quot;$SharePoint.Type.0a3b8df1-0a43-485d-be83-983c1df8b30d.FullName$&amp;quot; /&amp;gt;&lt;/pre&gt;

&lt;p&gt;Но сразу после развертывания этого элемента контрол не заработает, нужно еще добавить класс в safecontrols в web.config. Ни в коем случае не надо писать модификацию конфига самостоятельно, для этого уже есть инструменты в студии.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://lh4.ggpht.com/-KRUJ2AlXwVk/Tpe6zVULmcI/AAAAAAAAAFw/4fc2Tm78egw/s1600-h/image%25255B3%25255D.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://lh5.ggpht.com/-DPcegq02HtU/Tpe60bwUtWI/AAAAAAAAAF4/4rPd_tRfJ14/image_thumb%25255B1%25255D.png?imgmax=800" width="452" height="390" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ну вот и все…&lt;/p&gt;

&lt;h5&gt;SharePoint 2010 way&lt;/h5&gt;

&lt;p&gt;Если сделать как написано выше, то нажатие на кнопку приведет к постбеку, то есть перезагрузке всей странице. Хотя, учитывая архитектуру, пользователь сразу не увидит изменений в любом случае. Поэтому необходим ajax, обязательно с feedback пользователю.&lt;/p&gt;

&lt;p&gt;Для этого нужно сделать: веб-сервис в sharepoint, который обработает запрос и поменять script.js, чтобы он вызывал этот веб-сервис. &lt;/p&gt;

&lt;p&gt;Чтобы сделать веб-сервис снова нужно воспользоваться &lt;a href="http://cksdev.codeplex.com/"&gt;cks:dev&lt;/a&gt; с готовым шаблоном. В готов шаблоне надо поменять в svc файле тип фабрики с MultipleBaseAddressBasicHttpBindingServiceHostFactory на MultipleBaseAddressWebServiceHostFactory чтобы можно было вызывать методы сервиса из js.&lt;/p&gt;

&lt;p&gt;Выкинув все лишнее можно получим до безобразия простой сервис:&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;[Guid(&amp;quot;36471285-d168-49ea-b191-6c83cfe1fe3e&amp;quot;)]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
[ServiceContract]
public class CleanupService
{
    [OperationContract]
    [WebInvoke(
        UriTemplate = &amp;quot;/SubmitLibraryToCleanup({listId})&amp;quot;,
        BodyStyle = WebMessageBodyStyle.Bare,
        RequestFormat = WebMessageFormat.Json,
        ResponseFormat = WebMessageFormat.Json)]
    public void SubmitLibraryToCleanup(string listId)
    {
        CleanupUtility.AddCleanupWorkitem(SPContext.Current.Web, new Guid(listId));
    }
}&lt;/pre&gt;

&lt;p&gt;Как видите, большую часть сервиса занимают атрибуты. Метод вызывается с помощью POST, параметры передаются прямо в строке запроса.&lt;/p&gt;

&lt;p&gt;Далее необходимо в script.js файле поправить метод &lt;em&gt;submitLibraryToCleanup&lt;/em&gt; чтобы он вызывал веб-сервис.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ВНИМАНИЕ.&lt;/strong&gt; Приведенный далее код работает, но крайне не рекомендую использовать его в своих решениях. Используйте более человеческие библиотеки, вроде jQuery.&lt;/p&gt;

&lt;pre class="brush: js;"&gt;function submitLibraryToCleanup(isOldSchool) {
    var listId = SP.ListOperation.Selection.getSelectedList();
    if (listId) {
        var notification = null;
        var request = new Sys.Net.WebRequest();
        request.set_url(GetWebUrl() + &amp;quot;_vti_bin/CleanupTimerJob/CleanupService.svc/SubmitLibraryToCleanup(&amp;quot;+listId+&amp;quot;)&amp;quot;);
        request.set_httpVerb(&amp;quot;POST&amp;quot;);
        request.add_completed(function(executor, eventArgs) {
            SP.UI.Notify.removeNotification(notification);
            SP.UI.Notify.addNotification(&amp;quot;Done&amp;quot;,false);
        });             
        notification = SP.UI.Notify.addNotification(&amp;quot;Submitting library to cleanup&amp;quot;,true);
        request.invoke();
    }
}&lt;/pre&gt;

&lt;p&gt;Этот код использует так называемую Microsoft Ajax Library для выполнения запроса на сервер, и методы Client OM SharePoint для отображения оповещений.&lt;/p&gt;

&lt;p&gt;Функция GetWebUrl скопирована &lt;a href="http://blog.tedpattison.net/Lists/Posts/Post.aspx?ID=12" target="_blank"&gt;отсюда&lt;/a&gt;.&lt;/p&gt;

&lt;h5&gt;&lt;/h5&gt;

&lt;h5&gt;Заключение&lt;/h5&gt;

&lt;p&gt;Надеюсь вы все таки дочитали до сюда и узнали что-то новое. Весь код можно найти на &lt;a href="http://spsamples.codeplex.com" target="_blank"&gt;spsamples.codeplex.com&lt;/a&gt;&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1302241583051203640-2516194930425709393?l=gandjustas.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=e-W7PsvrEM8:JpVl3cCenyk:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=e-W7PsvrEM8:JpVl3cCenyk:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?i=e-W7PsvrEM8:JpVl3cCenyk:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=e-W7PsvrEM8:JpVl3cCenyk:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GandjustasBlog/~4/e-W7PsvrEM8" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/GandjustasBlog/~3/e-W7PsvrEM8/blog-post_14.html</link><author>noreply@blogger.com (Станислав Выщепан)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://lh5.ggpht.com/-DPcegq02HtU/Tpe60bwUtWI/AAAAAAAAAF4/4rPd_tRfJ14/s72-c/image_thumb%25255B1%25255D.png?imgmax=800" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://gandjustas.blogspot.com/2011/10/blog-post_14.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-1302241583051203640.post-3437261107957191321</guid><pubDate>Thu, 13 Oct 2011 06:00:00 +0000</pubDate><atom:updated>2011-10-14T11:50:15.492+04:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">SharePoint</category><title>Решение задачи. Задача таймера, почти конец.</title><description>&lt;p&gt;Посты в этой серии:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;&lt;a href="http://gandjustas.blogspot.com/2011/08/sharepoint.html" target="_blank"&gt;Список задач для проверки навыков&lt;/a&gt;. &lt;/li&gt;    &lt;li&gt;&lt;a href="http://gandjustas.blogspot.com/2011/10/blog-post.html" target="_blank"&gt;Создание задачи таймера&lt;/a&gt;. &lt;/li&gt;    &lt;li&gt;&lt;a href="http://gandjustas.blogspot.com/2011/10/blog-post_11.html" target="_blank"&gt;Использование подходящих классов&lt;/a&gt;. &lt;/li&gt;    &lt;li&gt;Передача команд задаче таймера (&lt;em&gt;этот пост&lt;/em&gt;). &lt;/li&gt;    &lt;li&gt;&lt;a href="http://gandjustas.blogspot.com/2011/10/blog-post_14.html" target="_blank"&gt;Взаимодействие веб-фронтэнда с задачами таймера&lt;/a&gt;. &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;Задачи таймера сами по себе очень важны для многих решений, но не менее важно взаимодействие задач таймера с другими частями решения.&lt;/p&gt;  &lt;p&gt;Например, задача таймера, которая выполняет очистку папок, требует большого количества ресурсов для работы, фактически просмотр всех баз контента для того чтобы удалить папки. Даже если надо удалить ровно одну папку, то задача таймера все равно будет бегать по всей контентной базе данных. Такое поведение крайне расточительно.&lt;/p&gt;  &lt;p&gt;Надо как-то ограничить задачу таймера чтобы она бегала только по тем сайтам где разрешит администратор.&lt;/p&gt;  &lt;p&gt;Проще всего этого добиться создав фичу уровня Web, которая выставляет свойства узла при активации и модифицировать задачу таймера, чтобы она проверяла свойство.&lt;/p&gt;  &lt;p&gt;Код Feature Receiver&lt;/p&gt;  &lt;pre class="brush: csharp;"&gt;public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
    SetCleanupFlag(properties, true);
}

public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
    SetCleanupFlag(properties, false);
}

private void SetCleanupFlag(SPFeatureReceiverProperties properties, bool flag)
{
    var web = properties.Feature.Parent as SPWeb;
    web.Properties[Constants.FlagPropertyName] = flag.ToString();
    web.Properties.Update();
}&lt;/pre&gt;

&lt;p&gt;Изменения в Timer Job:&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;private void ProcessWeb(SPWeb web)
{
    if (Convert.ToBoolean(web.Properties[Constants.FlagPropertyName]))
    {
        tju.ProcessLists(web.Lists, ProcessList, null);
    }
}&lt;/pre&gt;

&lt;p&gt;Вот теперь задача таймера будет обрабатывать только то что укажет администратор. Для своих задач передачи данных из контекста веб-приложения в&amp;#160; timer job вы можете также использовать списки, лучше скрытые.&lt;/p&gt;

&lt;p&gt;Но если задача таймера запускается нечасто, а результат пользователю нужен в короткое время, то эти способы не подойдут.&lt;/p&gt;

&lt;h5&gt;WorkItems&lt;/h5&gt;

&lt;p&gt;Про механизм “очередей” в SharePoint я &lt;a href="http://gandjustas.blogspot.com/2011/03/sharepoint.html" target="_blank"&gt;писал ранее&lt;/a&gt;, но тогда не было подходящей задачи для иллюстрации.&lt;/p&gt;

&lt;p&gt;Используем простую идею: некоторое действие пользователя ставит в очередь задачу (&lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spworkitem.aspx" target="_blank"&gt;SPWorkItem&lt;/a&gt;), а задача таймера (worker) анализирует очередь на наличие новых задач и обрабатывает их.&lt;/p&gt;

&lt;p&gt;Для создания worker необходимо создать класс наследник &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.administration.spworkitemjobdefinition.aspx"&gt;SPWorkItemJobDefinition&lt;/a&gt;, в котором переопределить два метода &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.administration.spworkitemjobdefinition.workitemtype.aspx"&gt;WorkItemType&lt;/a&gt; и &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.administration.spworkitemjobdefinition.processworkitem.aspx"&gt;ProcessWorkItem&lt;/a&gt;. Документация на MSDN утверждает что вам необходимо переопределить один из методов &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.administration.spworkitemjobdefinition.processworkitems.aspx"&gt;ProcessWorkItems&lt;/a&gt; (с буквой s в конце) – не верьте.&lt;/p&gt;

&lt;p&gt;При обработке work item есть много тонкостей, но я не буду их тут описывать, а просто использую уже знакомый класс &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.office.server.utilities.timerjobutility.aspx"&gt;TimerJobUtility&lt;/a&gt;, в котором уже сделана вся “грязная” работа.&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;public class CleaupWorker : SPWorkItemJobDefinition
{
    TimerJobUtility tju;
    WorkItemTimerJobState wiJobState = new WorkItemTimerJobState(true);

    public CleaupWorker() : base() { }

    public CleaupWorker(SPWebApplication webApp)
        : base(Constants.WorkerJobName, webApp)
    {
        this.Title = &amp;quot;Folder cleanup worker&amp;quot;;
    }

    public override Guid WorkItemType()
    {
        return Constants.WorkItemType;
    }

    protected override bool ProcessWorkItem(SPContentDatabase contentDatabase, SPWorkItemCollection workItems, SPWorkItem workItem, SPJobState jobState)
    {
        tju = new TimerJobUtility(Constants.TimerJobName, jobState);
        return tju.ProcessWorkItem(workItems, workItem, wiJobState, ProcessWorkItemCore);
    }

    private void ProcessWorkItemCore(SPWorkItem wi, WorkItemTimerJobState timerJobstate)
    {
        var list = timerJobstate.Web.Lists[wi.ParentId];
        CleanupUtility.CleanupList(list);
    }
}&lt;/pre&gt;

&lt;p&gt;Тем, кто занимается разработкой для SharePoint Foundation повезло гораздо меньше, им придется все подводные камни обходить самостоятельно. Крайне рекомендую для этого посмотреть реализацию &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.office.server.utilities.timerjobutility.aspx"&gt;TimerJobUtility&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Чтобы поместить WorkItem в очередь необходимо вызывать метод &lt;a href="http://msdn.microsoft.com/en-us/library/ms476803.aspx" target="_blank"&gt;SPSite.AddWorkItem&lt;/a&gt;. Остается самый интересный вопрос: где вызывать этот метод. Об этом, и многом другом в следующей статье.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1302241583051203640-3437261107957191321?l=gandjustas.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=5yW5OFwkzk0:yO0pZc7jF40:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=5yW5OFwkzk0:yO0pZc7jF40:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?i=5yW5OFwkzk0:yO0pZc7jF40:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=5yW5OFwkzk0:yO0pZc7jF40:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GandjustasBlog/~4/5yW5OFwkzk0" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/GandjustasBlog/~3/5yW5OFwkzk0/blog-post_13.html</link><author>noreply@blogger.com (Станислав Выщепан)</author><thr:total>0</thr:total><feedburner:origLink>http://gandjustas.blogspot.com/2011/10/blog-post_13.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-1302241583051203640.post-1113506270685028881</guid><pubDate>Wed, 12 Oct 2011 13:48:00 +0000</pubDate><atom:updated>2011-10-12T23:54:52.324+04:00</atom:updated><title>Убрать левую панель в SharePoint</title><description>&lt;p&gt;Именно с этим вопросом ко мне обратились трое за последнюю неделю. &lt;/p&gt;  &lt;p&gt;Сделать это довольно просто c помощью CSS&lt;/p&gt;  &lt;pre class="brush: css;"&gt;#s4-leftpanel
{
    display:none;
}

.s4-ca
{
    margin-left:auto;
}&lt;/pre&gt;

&lt;p&gt;Но вот где написать этот стиль…&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Если вам нужно получить такой эффект на одной странице, то необходимо добавить в контент AdditionalPageHead.&lt;/li&gt;

  &lt;li&gt;Если нужно для всего сайта, то поправить MasterPage или CSS, который используется на мастер-странице.&lt;/li&gt;

  &lt;li&gt;Если нужно такое на странице веб-частей, то ничего не нужно делать. Уже существует шаблон страницы веб-частей где убрана левая панель.&lt;/li&gt;

  &lt;li&gt;Если нужно убрать левую панель во всей коллекции сайтов, то можно создать в корневом сайте файл и подключить его с помощью &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spweb.alternatecssurl.aspx" target="_blank"&gt;SPWeb.AlternateCssUrl&lt;/a&gt;. При включении фич публикации этой настройкой можно управлять из админки сайта.&lt;/li&gt;
&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1302241583051203640-1113506270685028881?l=gandjustas.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=oFF3wzvMWO8:1r1cBbf1L88:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=oFF3wzvMWO8:1r1cBbf1L88:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?i=oFF3wzvMWO8:1r1cBbf1L88:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=oFF3wzvMWO8:1r1cBbf1L88:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GandjustasBlog/~4/oFF3wzvMWO8" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/GandjustasBlog/~3/oFF3wzvMWO8/sharepoint.html</link><author>noreply@blogger.com (Станислав Выщепан)</author><thr:total>0</thr:total><feedburner:origLink>http://gandjustas.blogspot.com/2011/10/sharepoint.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-1302241583051203640.post-678030169536118699</guid><pubDate>Tue, 11 Oct 2011 06:00:00 +0000</pubDate><atom:updated>2011-10-14T11:51:23.122+04:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">SharePoint</category><title>Решение задачи. Задача таймера, продолжение.</title><description>&lt;ol&gt;   &lt;li&gt;&lt;a href="http://gandjustas.blogspot.com/2011/08/sharepoint.html"&gt;Список задач для проверки навыков&lt;/a&gt;. &lt;/li&gt;    &lt;li&gt;&lt;a href="http://gandjustas.blogspot.com/2011/10/blog-post.html"&gt;Создание задачи таймера&lt;/a&gt;. &lt;/li&gt;    &lt;li&gt;Использование подходящих классов (&lt;em&gt;этот пост&lt;/em&gt;). &lt;/li&gt;    &lt;li&gt;&lt;a href="http://gandjustas.blogspot.com/2011/10/blog-post_13.html" target="_blank"&gt;Передача команд задаче таймера&lt;/a&gt;. &lt;/li&gt;    &lt;li&gt;&lt;a href="http://gandjustas.blogspot.com/2011/10/blog-post_14.html"&gt;Взаимодействие веб-фронтэнда с задачами таймера&lt;/a&gt;. &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;В прошлый раз я писал о том &lt;a href="http://gandjustas.blogspot.com/2011/10/blog-post.html" target="_blank"&gt;как создать задачу таймера в SharePoint&lt;/a&gt;. Такой код писать не нужно. Надеюсь никто не успел скопипастить код к себе в проект.&lt;/p&gt;  &lt;p&gt;В SharePoint 2010 основной класс для создания задач таймера – &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.administration.sppausablejobdefinition.aspx" target="_blank"&gt;SPPausableJobDefinition&lt;/a&gt;. В отличии от обычного &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.administration.spjobdefinition.aspx" target="_blank"&gt;SPJobDefinition&lt;/a&gt;, как вы можете догадаться из названия, SPPausableJobDefinition можно останавливать.&lt;/p&gt;  &lt;p&gt;Чтобы создать приостанавливаемую задачу таймера надо переопределить метод &lt;a href="http://msdn.microsoft.com/en-us/library/ee541400.aspx" target="_blank"&gt;Execute с параметром SPJobState&lt;/a&gt;. Объект класса &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.administration.spjobstate.aspx" target="_blank"&gt;SPJobState&lt;/a&gt; содержат как свойства, говорящие о том что задача должна остановиться, так и методы сохранения текущего состояния задачи таймера для продолжения работы.&lt;/p&gt;  &lt;p&gt;С одной стороны возможность приостанавливать выполнение задачи таймера ведет только к усложнению кода, но нету необходимости писать код, он уже есть в сборке Microsoft.SharePoint.&lt;/p&gt;  &lt;p&gt;Класс, который больше всего подходит к нашей задаче – &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.administration.spallsitesjobdefinition.aspx" target="_blank"&gt;SPAllSitesJobDefinition&lt;/a&gt;. Задачи таймера, наследующиеся от этого класса должны переопределить только один метод &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.administration.spallsitesjobdefinition.processsite.aspx" target="_blank"&gt;ProcessSite&lt;/a&gt;.&lt;/p&gt;  &lt;pre class="brush: csharp;"&gt;public class TimerJob : SPAllSitesJobDefinition
{
    public TimerJob()
        : base()
    {

    }

    public TimerJob(SPWebApplication webApp)
        : base(Constants.TimerJobName, webApp)
    {
        this.Title = &amp;quot;Folder cleanup job&amp;quot;;
    }

    public override void ProcessSite(SPSite site, SPJobState jobState)
    {            
        foreach (SPWeb web in site.AllWebs)
        {
            try
            {
                ProcessWeb(web);
            }
            finally
            {
                web.Dispose();
            }
        }
    }

    private void ProcessWeb(SPWeb web)
    {
        //omited for clarity
    }
}&lt;/pre&gt;

&lt;p&gt;Кода получилось даже меньше чем в первом варианте, при этом он поддерживает приостановку и запуск с места остановки, а также обновляет значения прогресса выполнения (это новая фича SharePoint 2010).&lt;/p&gt;

&lt;p&gt;В прошлом посте писал о выборе значения SPJobLockType. В SharePoint 2010 нет такой необходимости. Разные варианты запуска задач таймера реализованы разными классами в сборке Microsoft.SharePoint. 
  &lt;br /&gt;Например: &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.administration.spcontentdatabasejobdefinition.aspx"&gt;SPContentDatabaseJobDefinition&lt;/a&gt;, &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.administration.spfirstavailableservicejobdefinition.aspx"&gt;SPFirstAvailableServiceJobDefinition&lt;/a&gt;, &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.administration.spserverjobdefinition.aspx"&gt;SPServerJobDefinition&lt;/a&gt;, &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.administration.spservicejobdefinition.aspx"&gt;SPServiceJobDefinition&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Далее можно модифицировать код ProcessSite и ProcessWeb чтобы поддерживать перезапуск задачи с конкретной библиотеки документов. Но если вы разрабатываете для SharePoint Server 2010 (платной версии), то вам и это не надо делать. Код уже написан.&lt;/p&gt;

&lt;h5&gt;TimerJobUtility&lt;/h5&gt;

&lt;p&gt;&lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.office.server.utilities.timerjobutility.aspx" target="_blank"&gt;TimerJobUtility&lt;/a&gt; – класс из сборки Microsoft.Office.Server. Он позволяет обходить содержимое SharePoint учитывая возможность остановки и перезапуска задачи таймера.&lt;/p&gt;

&lt;p&gt;С использованием TimerJobUtility методы будут выглядеть так:&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;TimerJobUtility tju;
public override void ProcessSite(SPSite site, SPJobState jobState)
{
    tju = new TimerJobUtility(Constants.TimerJobName, jobState);
    tju.DisableEventFiring = false;
    tju.CancellationGranularity = IterationGranularity.List;
    tju.ResumeGranularity = IterationGranularity.List;
    tju.ProcessSite(site, s =&amp;gt; tju.ProcessSite(s, ProcessWeb, null));
}

private void ProcessWeb(SPWeb web)
{
    tju.ProcessLists(web.Lists, ProcessList, null);
}

private void ProcessList(SPList list)
{
    if (!list.Hidden &amp;amp;&amp;amp; list is SPDocumentLibrary)
    {
        DeleteEmptyFolders(list.RootFolder.SubFolders);
    }
}

private void DeleteEmptyFolders(SPFolderCollection folders)
{
    //omited for clarity
}&lt;/pre&gt;

&lt;p&gt;С таким кодом в случае остановки и перезапуска задачи она продолжит выполнять начиная с того списка, который не был обработан. Кроме того есть возможность блокировать срабатывание обработчиков событий установкой флага DisableEventFiring.&lt;/p&gt;

&lt;h5&gt;SPFolderHierarchy&lt;/h5&gt;

&lt;p&gt;5000… Для всех разработчиков SharePoint 2010 это магическая цифра. Если у вас запрос должен обработать более 5000 строк, то выпадает &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spquerythrottledexception.aspx" target="_blank"&gt;SPQueryThrottledException&lt;/a&gt;. Причем даже если реально будет возвращено мало строк, но для их вычисления придется просмотреть более 5000 элементов, то будет ошибка. В таких случаях помогает индекс или постраничное разбиение.&lt;/p&gt;

&lt;p&gt;Нетрудно догадаться что стандартная реализация свойства SubFolders считывает все элементы из папки, которых может оказаться более 5000., что вызовет ошибку. Чтобы обрабатывать такие случаи в SharePoint Server 2010 (платной версии) есть класс &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.office.server.utilities.spfolderhierarchy.aspx" target="_blank"&gt;SPFolderHierarchy&lt;/a&gt;, который помогает избежать проблем и содержит множество эвристик для максимального быстродействия навигации по папкам.&lt;/p&gt;

&lt;p&gt;В итоге, используя все вышеперечисленные возможности, код будет такой:&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;public class TimerJob : SPAllSitesJobDefinition
{
    TimerJobUtility tju;

    public TimerJob(): base() { }

    public TimerJob(SPWebApplication webApp)
        : base(Constants.TimerJobName, webApp)
    {
        this.Title = &amp;quot;Folder cleanup job&amp;quot;;
    }

    public override void ProcessSite(SPSite site, SPJobState jobState)
    {
        tju = new TimerJobUtility(Constants.TimerJobName, jobState);
        tju.DisableEventFiring = false;
        tju.CancellationGranularity = IterationGranularity.List;
        tju.ResumeGranularity = IterationGranularity.List;
        tju.ProcessSite(site, s =&amp;gt; tju.ProcessSite(s, ProcessWeb, null));
    }

    private void ProcessWeb(SPWeb web)
    {
        tju.ProcessLists(web.Lists, ProcessList, null);
    }

    private void ProcessList(SPList list)
    {
        if (!list.Hidden &amp;amp;&amp;amp; list is SPDocumentLibrary)
        {
            DeleteEmptyFolders(new SPFolderHierarchy(list));
        }
    }

    private void DeleteEmptyFolders(SPFolderHierarchy h)
    {
        foreach (SPFolder folder in (h as IEnumerable&amp;lt;SPFolder&amp;gt;))
        {
            if (folder.Item != null)
            {
                DeleteEmptyFolders(h.GetSubFolders(folder.ServerRelativeUrl));
                if (folder.ItemCount == 0)
                {
                    folder.Delete();
                }
            }
        }
    }
}&lt;/pre&gt;

&lt;p&gt;Кроме повышенной надежности такого кода есть еще одно преимущество: классы TimerJobUtility и SPFolderHierarchy очень активно пишут диагностические сообщения в ULS. Таким образом вам гораздо будет легче отлаживать такой код на удаленной машине.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1302241583051203640-678030169536118699?l=gandjustas.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=HFSYnw5i4m0:87c9vKXiDMQ:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=HFSYnw5i4m0:87c9vKXiDMQ:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?i=HFSYnw5i4m0:87c9vKXiDMQ:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=HFSYnw5i4m0:87c9vKXiDMQ:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GandjustasBlog/~4/HFSYnw5i4m0" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/GandjustasBlog/~3/HFSYnw5i4m0/blog-post_11.html</link><author>noreply@blogger.com (Станислав Выщепан)</author><thr:total>0</thr:total><feedburner:origLink>http://gandjustas.blogspot.com/2011/10/blog-post_11.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-1302241583051203640.post-8671663412358280366</guid><pubDate>Mon, 10 Oct 2011 06:00:00 +0000</pubDate><atom:updated>2011-10-14T11:52:38.992+04:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">SharePoint</category><title>Решение задачи. Задача таймера.</title><description>&lt;ol&gt;   &lt;li&gt;&lt;a href="http://gandjustas.blogspot.com/2011/08/sharepoint.html"&gt;Список задач для проверки навыков&lt;/a&gt;. &lt;/li&gt;    &lt;li&gt;Создание задачи таймера (&lt;em&gt;этот пост&lt;/em&gt;). &lt;/li&gt;    &lt;li&gt;&lt;a href="http://gandjustas.blogspot.com/2011/10/blog-post_11.html" target="_blank"&gt;Использование подходящих классов&lt;/a&gt;. &lt;/li&gt;    &lt;li&gt;&lt;a href="http://gandjustas.blogspot.com/2011/10/blog-post_13.html"&gt;Передача команд задаче таймера&lt;/a&gt;. &lt;/li&gt;    &lt;li&gt;&lt;a href="http://gandjustas.blogspot.com/2011/10/blog-post_14.html"&gt;Взаимодействие веб-фронтэнда с задачами таймера&lt;/a&gt;. &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;Ранее я приводил &lt;a href="http://gandjustas.blogspot.com/2011/08/sharepoint.html" target="_blank"&gt;список задач для проверки навыков программирования для SharePoint&lt;/a&gt;. Сегодня напишу о решении четвертой задачи про задачу таймера для очистки библиотек документов от пустых папок.&lt;/p&gt;  &lt;h5&gt;Задача&lt;/h5&gt;  &lt;p&gt;Создать задачу таймера (Timer Job), которая буде находить в библиотеках документов пустые папки, в которых нет файлов и которые содержат пустые папки, и удалять их.&lt;/p&gt; &lt;cut title="Читать дальше..."&gt;  &lt;h5&gt;Класс задачи таймера&lt;/h5&gt;  &lt;p&gt;Чтобы создать задачу таймера необходимо создать класс, унаследованный от &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.administration.spjobdefinition.aspx" target="_blank"&gt;&lt;strong&gt;Microsoft.SharePoint.Administration.SPJobDefinition&lt;/strong&gt;&lt;/a&gt;. Этот класс недоступен в sandbox, поэтому вам нужен farm solution.&lt;/p&gt;  &lt;p&gt;В этом классе необходимо переопределить метод Execute и конструктор с параметрами.&lt;/p&gt;  &lt;pre class="brush: csharp;"&gt;public TimerJob(SPWebApplication webApp)
    : base(Constants.TimerJobName, webApp, null,
           SPJobLockType.ContentDatabase)
{
    this.Title = &amp;quot;Folder cleanup job&amp;quot;;
}&lt;/pre&gt;

&lt;p&gt;Первым параметром конструктора передается строка, которая будет отличать задачу таймера от других. Title задает отображаемое в админке имя задачи таймера.&lt;/p&gt;

&lt;p&gt;Так как ферма SharePoint может состоять из нескольких серверов, то появляется интересный вопрос – где и сколько раз будет запускаться задача таймера. Особое внимание надо уделить параметру SPJobLockType. Детальное описание &lt;a href="http://msdn.microsoft.com/en-us/library/cc406686(v=office.12).aspx#WSSCustomTimerJobs_CreatingCustomTimerJobs" target="_blank"&gt;по ссылке&lt;/a&gt;. Возможные варианты:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;SPJobLockType.ContentDatabase&lt;em&gt;&amp;#160; &lt;/em&gt;&lt;/strong&gt;- задача таймера запускается для каждой контентной базы данных родительского приложения. &lt;/li&gt;

  &lt;li&gt;&lt;strong&gt;SPJobLockType.Job &lt;/strong&gt;– задача таймера выполняется один раз на всю ферму. В данном режиме учитывается параметр SPServer конструктора, позволяющий указать конкретный сервер для запуска Timer Job. &lt;/li&gt;

  &lt;li&gt;&lt;strong&gt;SPJobLockType.None &lt;/strong&gt;– запускается на каждом сервере в ферме, где развернут родительский сервис. Очень полезно если вам надо запустить некоторый некоторый процесс на каждом сервере в ферме. &lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;Добавление и удаление задачи таймера&lt;/h5&gt;

&lt;p&gt;Для добавления задачи таймера удобно использовать фичу уровня фермы или веб-приложения с флагом Activate On Default равным true. При разветрывании решения с такой фичей она автоматически активируется в указанной области действия. &lt;/p&gt;

&lt;p&gt;Код feature receiver:&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
    var webApp = properties.Feature.Parent as SPWebApplication;
    var job = new TimerJob(webApp);
    job.Schedule = new SPHourlySchedule()
    {
        BeginMinute = 0,
        EndMinute = 59
    };

    job.Update();
}


public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
    var webApp = properties.Feature.Parent as SPWebApplication;
    var job = webApp.JobDefinitions.GetValue&amp;lt;TimerJob&amp;gt;(Constants.TimerJobName);
    job.Delete();
}&lt;/pre&gt;

&lt;p&gt;Класс &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.sphourlyschedule.aspx" target="_blank"&gt;SPHourlySchedule&lt;/a&gt; является наследником &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spschedule.aspx" target="_blank"&gt;SPSchedule&lt;/a&gt; и позволяет задавать расписание запуска задачи таймера.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Важно.&lt;/strong&gt; Если вы попытаетесь добавить задачу таймера в фиче уровня Site или Web, то при деплое из Visual Studio оно сработает, а при попытке активировать фичу из веб-интерфейса упадет. Это новое ограничение SharePoint 2010, не позволяющее делать Update для классов наследников &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.administration.sppersistedobject.aspx" target="_blank"&gt;SPPersistedObject&lt;/a&gt; из контекста веб-приложения.&lt;/p&gt;

&lt;p&gt;Если же у вас есть унаследованный код, который вы не можете поправить, то вам скорее всего пригодится &lt;a href="http://unclepaul84.blogspot.com/2010/06/sppersistedobject-xxxxxxxxxxx-could-not.html" target="_blank"&gt;эта статья&lt;/a&gt;.&lt;/p&gt;

&lt;h5&gt;Обработка элементов списков&lt;/h5&gt;

&lt;p&gt;Теперь перейдем к основной функции задачи таймера – очистка папок. &lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;public override void Execute(Guid targetInstanceId)
{
    SPWebApplication webApplication = this.Parent as SPWebApplication;
    SPContentDatabase contentDb = webApplication.ContentDatabases[targetInstanceId];

    ProcessDatabase(contentDb);
}&lt;/pre&gt;

&lt;p&gt;Так как при создании Timer Job был указан SPJobLockType.ContentDatabase, то в качестве параметра targetInstanceId будет ID базы данных контента.&lt;/p&gt;

&lt;p&gt;Далее циклы по &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spsite.aspx" target="_blank"&gt;SPSite&lt;/a&gt; и &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spweb.aspx" target="_blank"&gt;SPWeb&lt;/a&gt;:&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;private void ProcessDatabase(SPContentDatabase contentDb)
{
    foreach (SPSite site in contentDb.Sites)
    {
        try
        {
            ProcessSite(site);
        }
        finally
        {
            site.Dispose();
        }
    }
}

private void ProcessSite(SPSite site)
{
    foreach (SPWeb web in site.AllWebs)
    {
        try
        {
            ProcessWeb(web);
        }
        finally
        {
            web.Dispose();
        }
    }
}&lt;/pre&gt;

&lt;p&gt;Вот такие конструкции необходимо использовать чтобы пройтись по всем SPSite и SPWeb в базе данных. Если не напишите Dispose в циклах, то на сервере очень быстро закончится память. &lt;/p&gt;

&lt;p&gt;При разработке задач таймера надо быть очень аккуратным и освобождать все ресурсы. Для задач таймера почти не существует средств мониторинга потребления ресурсов, поэтому будет сложно определить кто поедает всю память.&lt;/p&gt;

&lt;p&gt;Для того чтобы обезопасить себя и ваших пользователей необходимо использовать утилиту &lt;a href="http://archive.msdn.microsoft.com/SPDisposeCheck" target="_blank"&gt;SPDisposeCheck&lt;/a&gt;. Она вам подскажет где надо совободить объекты.&lt;/p&gt;

&lt;p&gt;Ну и наконец удаление папок:&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;private void ProcessWeb(SPWeb web)
{
    foreach (var lib in web.Lists.OfType&amp;lt;SPDocumentLibrary&amp;gt;())
    {
        if (!lib.Hidden)
        {
            DeleteEmptyFolders(lib.RootFolder.SubFolders);
        }
    }
}

private void DeleteEmptyFolders(SPFolderCollection folders)
{
    foreach (var folder in folders.OfType&amp;lt;SPFolder&amp;gt;().ToList())
    {
        DeleteEmptyFolder(folder);
    }

}

private void DeleteEmptyFolder(SPFolder folder)
{
    if (folder.Item != null)
    {
        DeleteEmptyFolders(folder.SubFolders);

        if (folder.ItemCount == 0)
        {
            folder.Delete();
        }
    }
}&lt;/pre&gt;

&lt;p&gt;Не копипастите код из статьи до того как прочитаете &lt;a href="http://gandjustas.blogspot.com/2011/10/blog-post_11.html" target="_blank"&gt;следующую часть&lt;/a&gt;.&lt;/p&gt;
&lt;/cut&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1302241583051203640-8671663412358280366?l=gandjustas.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=OVQSodghlZg:3PbCzoq8gIM:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=OVQSodghlZg:3PbCzoq8gIM:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?i=OVQSodghlZg:3PbCzoq8gIM:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=OVQSodghlZg:3PbCzoq8gIM:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GandjustasBlog/~4/OVQSodghlZg" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/GandjustasBlog/~3/OVQSodghlZg/blog-post.html</link><author>noreply@blogger.com (Станислав Выщепан)</author><thr:total>0</thr:total><feedburner:origLink>http://gandjustas.blogspot.com/2011/10/blog-post.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-1302241583051203640.post-2733340403076795436</guid><pubDate>Thu, 06 Oct 2011 21:58:00 +0000</pubDate><atom:updated>2011-10-07T02:00:52.964+04:00</atom:updated><title>MVP</title><description>&lt;p align="left"&gt;Получил статус Microsoft MVP.&lt;/p&gt;  &lt;p align="center"&gt;&lt;a href="https://mvp.support.microsoft.com/profile/Stanislav.Vyschepan"&gt;&lt;img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="MVP_FullColor_ForScreen" border="0" alt="MVP_FullColor_ForScreen" src="http://lh5.ggpht.com/-PovBUULJa0w/To4kl4o_tcI/AAAAAAAAAFs/o-fFNYDIbNM/MVP_FullColor_ForScreen%25255B4%25255D.png?imgmax=800" width="103" height="162" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Теперь признанный кем-то эксперт и все такое.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1302241583051203640-2733340403076795436?l=gandjustas.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=fMMla1eymCo:xvOwkliuW7U:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=fMMla1eymCo:xvOwkliuW7U:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?i=fMMla1eymCo:xvOwkliuW7U:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=fMMla1eymCo:xvOwkliuW7U:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GandjustasBlog/~4/fMMla1eymCo" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/GandjustasBlog/~3/fMMla1eymCo/mvp.html</link><author>noreply@blogger.com (Станислав Выщепан)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://lh5.ggpht.com/-PovBUULJa0w/To4kl4o_tcI/AAAAAAAAAFs/o-fFNYDIbNM/s72-c/MVP_FullColor_ForScreen%25255B4%25255D.png?imgmax=800" height="72" width="72" /><thr:total>4</thr:total><feedburner:origLink>http://gandjustas.blogspot.com/2011/10/mvp.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-1302241583051203640.post-8243499237757422713</guid><pubDate>Thu, 25 Aug 2011 06:00:00 +0000</pubDate><atom:updated>2011-08-25T10:00:09.279+04:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">Unity</category><category domain="http://www.blogger.com/atom/ns#">IoC</category><category domain="http://www.blogger.com/atom/ns#">AOP</category><title>Статьи про IoC, AOP и контейнер Unity</title><description>&lt;p&gt;Первые посты про IoC я написал более двух лет назад, но судя по статистике блога люди часто к ним обращаются.&lt;/p&gt;  &lt;p&gt;В этом после приведу индекс статей в порядке чтения для облегчения поиска и навигации&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;&lt;a href="http://gandjustas.blogspot.com/2009/01/ioc.html" target="_blank"&gt;Введение в IoC&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;&lt;a href="http://gandjustas.blogspot.com/2009/01/ioc_20.html" target="_blank"&gt;Основная задача IoC-контейнеров&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;&lt;a href="http://gandjustas.blogspot.com/2009/01/ioc-unity.html" target="_blank"&gt;Первые шаги с Unity&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;&lt;a href="http://gandjustas.blogspot.com/2009/01/legacy-unity.html" target="_blank"&gt;Рефакторинг legacy-кода для использования Unity&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;&lt;a href="http://gandjustas.blogspot.com/2009/01/generic-unity.html" target="_blank"&gt;Использование generic-классов с Unity&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;&lt;a href="http://gandjustas.blogspot.com/2009/01/unity.html" target="_blank"&gt;Инъекция массивов в Unity&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;&lt;a href="http://gandjustas.blogspot.com/2009/01/unity_22.html"&gt;Unity LifetimeManager&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;&lt;a href="http://gandjustas.blogspot.com/2009/01/unity_8026.html" target="_blank"&gt;Конфигурация Unity&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;&lt;a href="http://gandjustas.blogspot.com/2009/01/unity_24.html" target="_blank"&gt;Инъекция самого контейнера Unity в объекты&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;&lt;a href="http://gandjustas.blogspot.com/2009/01/aop-unity.html" target="_blank"&gt;AOP в Unity&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;&lt;a href="http://gandjustas.blogspot.com/2009/01/unity_26.html" target="_blank"&gt;Фабрики в Unity&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;&lt;a href="http://gandjustas.blogspot.com/2010/02/blog-post.html" target="_blank"&gt;Практика AOP: аудит изменений данных в EF с учетом котнекста операций&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;&lt;a href="http://gandjustas.blogspot.com/2010/12/unity-20.html" target="_blank"&gt;Unity 2.0&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;&lt;a href="http://gandjustas.blogspot.com/2011/04/unity-20-interception.html" target="_blank"&gt;AOP в Unity 2.0&lt;/a&gt;&lt;/li&gt; &lt;/ol&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1302241583051203640-8243499237757422713?l=gandjustas.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=4rpPQT6GERs:LtnliJFM6ck:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=4rpPQT6GERs:LtnliJFM6ck:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?i=4rpPQT6GERs:LtnliJFM6ck:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=4rpPQT6GERs:LtnliJFM6ck:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GandjustasBlog/~4/4rpPQT6GERs" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/GandjustasBlog/~3/4rpPQT6GERs/ioc-aop-unity.html</link><author>noreply@blogger.com (Станислав Выщепан)</author><thr:total>0</thr:total><feedburner:origLink>http://gandjustas.blogspot.com/2011/08/ioc-aop-unity.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-1302241583051203640.post-797666860667933456</guid><pubDate>Wed, 24 Aug 2011 06:00:00 +0000</pubDate><atom:updated>2011-08-24T10:00:03.174+04:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">архитектура</category><category domain="http://www.blogger.com/atom/ns#">ООП</category><category domain="http://www.blogger.com/atom/ns#">SOLID</category><title>SOLID</title><description>&lt;p&gt;Эта аббревиатура является самой известной (после ООП), она говорит нам о 5 принципах “хорошего дизайна ПО”. При этом является самой бесполезной, потому что однозначно никто не может обозначить критерий для того или иного принципа. Часто на форумах приходится видеть споры о том у кого программа SOLIDнее. &lt;/p&gt;&lt;p&gt;Про SOLID пишут часто и много, но большинство пишущих не читали или мало читали &lt;a href="http://www.objectmentor.com/resources/articles/Principles_and_Patterns.pdf" target="_blank"&gt;первоисточник&lt;/a&gt; (признайтесь, вы читали?). Автор аббревиатуры &lt;a href="http://en.wikipedia.org/wiki/SOLID_(object-oriented_design)" target="_blank"&gt;SOLID&lt;/a&gt; - &lt;a href="http://en.wikipedia.org/wiki/Robert_C._Martin" target="_blank"&gt;Роберт Мартин&lt;/a&gt;, он придумал саму аббревиатуру и &lt;em&gt;описал&lt;/em&gt; 5 принципов. На самом деле он описал больше, но звучных буквосочетаний не придумал, многие вещи остались забытыми. Заметьте что Мартин именно описал принципы, он не является их автором. Зачастую объяснения &lt;strike&gt;на пальцах&lt;/strike&gt; на примерах сложно перенести в свой код.&lt;/p&gt;&lt;a name='more'&gt;&lt;/a&gt;  &lt;br /&gt;
&lt;cut title="Читать дальше..."&gt;&lt;br /&gt;
&lt;h4&gt;Who is mister SOLID?&lt;/h4&gt;&lt;p&gt;Аббревиатура (длинное и неприятное слово) SOLID состоит из:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Single Responsibility Principle &lt;/strong&gt;(&lt;em&gt;SRP&lt;/em&gt;) – принцип единственной отвественности &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Open\Close Principle&lt;/strong&gt; (&lt;em&gt;OCP&lt;/em&gt;) – принцип открытости\закрытости &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Liskov Substitution Principle&lt;/strong&gt; (&lt;em&gt;LSP&lt;/em&gt;) – принцип подстановки Лисков (это фамилия) &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interface Segregation Principle&lt;/strong&gt; (&lt;em&gt;ISP&lt;/em&gt;) – принцип изоляции интерфейсов &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dependency Inversion Principle&lt;/strong&gt; (&lt;em&gt;DIP&lt;/em&gt;) – принцип инверсии зависимостей &lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Далее буду пользоваться только акронимами, указанными в скобках.&lt;/p&gt;&lt;h4&gt;Критика&lt;/h4&gt;&lt;p&gt;Как связаны между собой вышеуказанные принципы никто не говорит, какой из них важнее, а какой нет – тоже никто не в курсе. &lt;/p&gt;&lt;p&gt;Разберем по отдельности все 5 принципов, для описания буду брать &lt;a href="http://ru.wikipedia.org/wiki/SOLID_(%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D0%BD%D0%BE-%D0%BE%D1%80%D0%B8%D0%B5%D0%BD%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)" target="_blank"&gt;из википедии&lt;/a&gt;. Вероятнее всего именно это описание найдет человек.&lt;/p&gt;&lt;h5&gt;SRP&lt;/h5&gt;&lt;blockquote&gt;&lt;p&gt;На каждый объект должна быть возложена одна единственная обязанность.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Первое же определение взрывает мозг. Что такое &lt;em&gt;обязанность&lt;/em&gt;? Мартин определяет обязанность как &lt;em&gt;причину изменения&lt;/em&gt;. Стало понятнее? Мне не очень.&lt;/p&gt;&lt;h5&gt;OCP&lt;/h5&gt;&lt;blockquote&gt;&lt;p&gt;Программные сущности должны быть открыты для расширения, но закрыты для изменения.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Это как вообще? Открыты для расширения – еще куда ни шло, а что значит закрыты для изменения? Скомпилированный код и так поменять нельзя, а если правятся исходники, то какая разница?&lt;/p&gt;&lt;h5&gt;LSP&lt;/h5&gt;&lt;blockquote&gt;&lt;p&gt;Объекты в программе могут быть заменены их наследниками без изменения свойств программы.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Тут немного лучше, потому что принцип LSP предельно формален, про него подробнее напишу ниже.&lt;/p&gt;&lt;h5&gt;ISP&lt;/h5&gt;&lt;blockquote&gt;&lt;p&gt;Много специализированных интерфейсов лучше, чем один универсальный.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Это Мартин&amp;#160; решил поиграть в Капитана Очевидность.&lt;/p&gt;&lt;h5&gt;DIP&lt;/h5&gt;&lt;blockquote&gt;&lt;p&gt;Зависимости внутри системы строятся на основе абстракций. Модули верхнего уровня не зависят от модулей нижнего уровня. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Это просто бред, написанный для красного словца. Если все зависит от абстракций, то как понять где модули верхнего уровня, а где нижнего?&lt;/p&gt;&lt;p&gt;У Мартина, кстати, не такое определение.&lt;/p&gt;&lt;h4&gt;Терминология&lt;/h4&gt;&lt;p&gt;Чтобы навести формализм во всей этой кухне необходимо ввести определения. большинство горячих споров происходят как раз из-за разных определений.&lt;/p&gt;&lt;h5&gt;Итак определения&lt;/h5&gt;&lt;p&gt;&lt;em&gt;Интерфейс – &lt;/em&gt;некоторый набор функций (их параметров и возвращаемых значений), с помощью которого одна часть программы обращается к другой.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Контракт &lt;/em&gt;– надмножество интерфейса, описывающее также поведение функций, ограничения на входные\выходные параметры, инварианты, последовательность вызовов итд. Контракт обычно присутствует в программе неявно, но есть средства, позволяющие часть его описать явно. Например навороченные системы типов как в haskell, внешние средства вроде Code Contracts в .NET. &lt;em&gt;Даже если контракт не определен явно, то в программе он неявно присутствует.&lt;/em&gt; &lt;/p&gt;&lt;p&gt;&lt;em&gt;Абстрактный интерфейс&lt;/em&gt; – некоторый тип данных,состоящий из набора методов без реализации. Всегда соответствует некоторому &lt;em&gt;интерфейсу.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;Класс&lt;/em&gt; – некоторый тип данных, который состоит из данных и методов. Классы могут наследоваться один от другого, могут реализовывать несколько &lt;em&gt;абстрактных интерфейсов.&lt;/em&gt; Класс всегда имеет некоторый &lt;em&gt;интерфейс&lt;/em&gt; и &lt;em&gt;контракт&lt;/em&gt;, зачастую больше одного.&lt;em&gt;&amp;#160;&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;Модуль&lt;/em&gt; – некоторое множество функций и, возможно, данных, объединенные для решения задачи. Модулем могут быть как &lt;em&gt;классы&lt;/em&gt; в ОО-языках, так и другие средства группирования кода. Модуль также имеет &lt;em&gt;интерфейс&lt;/em&gt; и &lt;em&gt;контракт&lt;/em&gt;, скорее всего один.&lt;/p&gt;&lt;h4&gt;Принципы&lt;/h4&gt;&lt;p&gt;Для начала стоит сказать что многие принципы не являются прерогативой ООП, а применимы для широкого класса парадигм.&lt;/p&gt;&lt;h5&gt;Начнем с &lt;em&gt;SRP&lt;/em&gt;&lt;/h5&gt;&lt;blockquote&gt;&lt;p&gt;Если часть некоторого &lt;em&gt;модуля&lt;/em&gt; не имеет никаких ссылок на другую часть этого &lt;em&gt;модуля&lt;/em&gt;, то эти части можно разделить на разные модули. Если модули могут меняться независимо, то разделить нужно.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Начинать стоит с простого: если можно отделить – надо отделить. Необходимо чтобы внутри одного модуля весь функционал был связан между собой (high cohesion, такое словосочетание вы наверное слышали). Принцип работает только в одну сторону: если подмножество A некоторого модуля не имеет ссылок на подмножество B, то это не значит что B не имеет ссылок на A, причем скорее всего именно B будет ссылаться на A.&lt;/p&gt;&lt;p&gt;Следуя данному принципу весь код будет распадаться на множество маленьких модулей, многие из которых выродятся до одной функции. Это нормально, даже хорошо. Функции потом можно группировать в модули по логической связности, добиваясь все того же high cohesion.&lt;/p&gt;&lt;p&gt;Модули будут зависеть друг от друга, они будут выстраиваться в ориентированный граф. Расположив зависимости сверху вниз можно условно разделить модули на &lt;em&gt;верхне-&lt;/em&gt; и &lt;em&gt;нижне-&lt;/em&gt; &lt;em&gt;уровневые&lt;/em&gt;. На самом “пространство”, в котором мы пытаемся упорядочить модули, многомерно. Придумать одно отношение порядка для всего этого пространства невозможно. Но для двух модулей, между которыми есть путь, можно сказать какой из них верхнеуровневый, а какой нижнеуровневый. &lt;/p&gt;&lt;p&gt;Зависимость между модулями может быть:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;em&gt;Ссылочной&lt;/em&gt;, когда модуль A непосредственно обращается к модулю B, его функциям и данным. &lt;/li&gt;
&lt;li&gt;&lt;em&gt;Наследованием&lt;/em&gt;, когда модуль A является частным случаем B. &lt;/li&gt;
&lt;li&gt;&lt;em&gt;Зависимостью по состоянию&lt;/em&gt;, когда два модуля оперируют одним внешним состоянием (глобальные переменные, файлы, БД) и влияют на работу друг друга. Это плохая зависимость, от нее надо избавляться. &lt;/li&gt;
&lt;li&gt;&lt;em&gt;Зависимостью по времени&lt;/em&gt;. Когда для работы требуется одного модуля требуется вызов функций другого модуля в нужные моменты. Это самый плохой вид зависимости, он него надо избавляться однозначно всеми возможными способами. &lt;/li&gt;
&lt;/ol&gt;&lt;h5&gt;&amp;#160;&lt;/h5&gt;&lt;h5&gt;Далее &lt;em&gt;ISP&lt;/em&gt;&lt;/h5&gt;&lt;p&gt;Как я уже писал выше, если следовать &lt;em&gt;SRP, &lt;/em&gt;то программа распадется на множество мелких модулей. &lt;em&gt;ISP&lt;/em&gt; говорит нам что это хорошо. Далее когда занимаетесь объединением отдельных функций в некоторые модули по смысловой связности, то учитывайте также &lt;em&gt;ISP&lt;/em&gt;, не создавая модулей с очень жирным интерфейсом.&lt;/p&gt;&lt;p&gt;&amp;#160;&lt;/p&gt;&lt;h5&gt;Перейдем к &lt;em&gt;DIP&lt;/em&gt;&lt;/h5&gt;&lt;p&gt;Этот принцип говорит нам что для двух &lt;em&gt;ссылочно&lt;/em&gt; связанных &lt;em&gt;модулей&lt;/em&gt; надо создавать &lt;em&gt;абстрактный интерфейс&lt;/em&gt;. Оформлять &lt;em&gt;модуль,&lt;/em&gt; от которого зависят другие &lt;em&gt;модули,&lt;/em&gt; в виде &lt;em&gt;класса&lt;/em&gt;, реализующего данный &lt;em&gt;абстрактный интерфейс&lt;/em&gt;. Зависимый модуль должен обращаться к &lt;em&gt;абстрактному интерфейсу&lt;/em&gt;, а не к конкретному &lt;em&gt;классу&lt;/em&gt;. (low coupling, тоже слышали)&lt;/p&gt;&lt;p&gt;Есть проблема в том что надо создавать экземпляры &lt;em&gt;классов&lt;/em&gt;, чтобы потом их передавать в зависимые &lt;em&gt;модули&lt;/em&gt;. Эту проблему решают IoC-контейнеры, о которых я &lt;a href="http://gandjustas.blogspot.com/2009/01/ioc.html" target="_blank"&gt;писал ранее&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;&amp;#160;&lt;/p&gt;&lt;h5&gt;Теперь рассмотрим &lt;em&gt;LSP&lt;/em&gt;&lt;/h5&gt;&lt;p&gt;Принцип подстановки Барбары Лисков сформулирован предельно формально и говорит вообще о любых типа, а не только классах ООП.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Если A является подтипом B, то в любом месте программы (функции), где требуется объект типа B, можно подставить объект типа A и поведение программы (функции) при этом не изменится.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;В ООП если &lt;em&gt;класс&lt;/em&gt; A унаследован от &lt;em&gt;класса&lt;/em&gt; B, равно как &lt;em&gt;класс&lt;/em&gt; A реализует &lt;em&gt;абстрактный интерфейс&lt;/em&gt; B, то A является &lt;em&gt;подтипом&lt;/em&gt; B, а B является &lt;em&gt;супертипом&lt;/em&gt; A.&amp;#160; Кроме того некоторые языки программирования поддерживают &lt;a href="http://gandjustas.blogspot.com/2009/11/blog-post.html" target="_blank"&gt;вариантность типов&lt;/a&gt;, для них тоже надо применять &lt;em&gt;LSP&lt;/em&gt;, но там помогает компилятор.&lt;/p&gt;&lt;p&gt;Принцип LSP надо использовать максимально широко, надо ориентироваться на весь &lt;em&gt;контракт&lt;/em&gt;, в том числе пред- и пост-условия, а также то что не описано в самой программе.&lt;/p&gt;&lt;p&gt;Для контрактов правила простые:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Предусловия в &lt;em&gt;подтипе&lt;/em&gt; должны быть &lt;strong&gt;не сильнее,&lt;/strong&gt; чем в &lt;em&gt;супертипе&lt;/em&gt;. &lt;/li&gt;
&lt;li&gt;Постусловия в &lt;em&gt;подтипе&lt;/em&gt; должны быть &lt;strong&gt;не слабее&lt;/strong&gt;, чем в &lt;em&gt;супертипе&lt;/em&gt;. &lt;/li&gt;
&lt;li&gt;Перечень выбрасываемых исключений в &lt;em&gt;подтипе&lt;/em&gt; должен быть &lt;strong&gt;не шире&lt;/strong&gt;, чем в &lt;em&gt;супертипе&lt;/em&gt;. (хотя часто на это не обращают внимания) &lt;/li&gt;
&lt;li&gt;Остальные детали контракта, которые нельзя проверить статически, должны проверяться тестами, и &lt;em&gt;подтипы&lt;/em&gt; должны проходить все тесты, которые проходят &lt;em&gt;супертипы&lt;/em&gt;. &lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;&amp;#160;&lt;/p&gt;&lt;h5&gt;Напоследок &lt;em&gt;OCP&lt;/em&gt;&lt;/h5&gt;&lt;p&gt;Хорошо понимая &lt;em&gt;LSP&lt;/em&gt; легко сообразить о чем говорит OCP.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Все не-sealed классы должны быть спроектированы таким образом, чтобы наследники не могли нарушить &lt;em&gt;LSP&lt;/em&gt;.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Ну вот и все.&lt;/p&gt;&lt;h4&gt;Что не вошло в пятерку&lt;/h4&gt;&lt;h5&gt;Принцип бритвы Оккма&lt;/h5&gt;&lt;blockquote&gt;&lt;p&gt;Не плодите сущности без нужды&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;После активного применения SRP у вас будет много маленьких модулей, вплоть до одиночных функций. Тривиальные функции можно непосредственно инлайнить в вызывающий код, простые функции также можно перенести, если они используются только в одном месте. Отдельные функции можно объединять в модули по смысловому назначению, а также по параметрам, задавая их на уровне модуля, а не отдельных функций.&lt;/p&gt;&lt;h5&gt;Do not repeat yourself (&lt;em&gt;DRY&lt;/em&gt;)&lt;/h5&gt;&lt;p&gt;Одинаковый или похожий код должен быть вынесен в отдельный модуль и использован другими. Для этого сильно помогают инструменты &lt;a href="http://gandjustas.blogspot.com/2011/04/unity-20-interception.html" target="_blank"&gt;вроде IoC-контейнеров с возможностью AOP.&lt;/a&gt;&lt;/p&gt;&lt;h5&gt;Command-Query Separation (&lt;em&gt;CQS&lt;/em&gt;)&lt;/h5&gt;&lt;p&gt;Все функции некоторого &lt;em&gt;интерфейса &lt;/em&gt;должны быть или некоторыми &lt;strong&gt;запросами&lt;/strong&gt;, возвращающими ответ, или &lt;strong&gt;командами&lt;/strong&gt;, изменяющими состояние системы, &lt;em&gt;но не одновременно&lt;/em&gt;. Никогда функция не должна возвращать ответ и изменять состояние системы одновременно.&lt;/p&gt;&lt;p&gt;Пожалуйста не путайте этот принцип с модным нынче CQRS, который является гипертрофированным CQS для непонятно каких целей.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Keep it simple, stupid! (&lt;em&gt;KISS&lt;/em&gt;)&lt;/strong&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Делайте все настолько простыми, насколько можно, но не проще.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Создавайте ровно те модули, которые необходимы для решения задач, если модуль не решает задачу – не создавайте его. Если вы хотите создать модуль, но толком не представляете какую задачу он решает – не создавайте его. Если при переделке программы модули стали ненужным – удалите их.&amp;#160; Если в некоторых поведение системы не описано для некоторых входных данных - не делайте частные случаи, опишите явно контракт, который запретит неверные данные.&lt;/p&gt;&lt;p&gt;&lt;font size="2"&gt;&lt;/font&gt;&lt;/p&gt;&lt;h4&gt;Заключение&lt;/h4&gt;&lt;p&gt;Самыми важными принципами в начале проектирования частей программы являются &lt;em&gt;KISS&lt;/em&gt; и &lt;em&gt;SRP.&lt;/em&gt; После того как появился некоторый граф модулей надо сразу применять &lt;em&gt;Бритву Оккама&lt;/em&gt;,&lt;em&gt; LSP&lt;/em&gt;,&lt;em&gt; ISP&lt;/em&gt;. Когда начинаете писать код, то применяйте &lt;em&gt;DRY&lt;/em&gt;, &lt;em&gt;DIP&lt;/em&gt;, &lt;em&gt;CQS&lt;/em&gt; и &lt;em&gt;OCP&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;Ну вот теперь совсем все.&lt;/p&gt;&lt;/cut&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1302241583051203640-797666860667933456?l=gandjustas.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=qaAm2L4_df0:D-E816mSW2E:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=qaAm2L4_df0:D-E816mSW2E:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?i=qaAm2L4_df0:D-E816mSW2E:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=qaAm2L4_df0:D-E816mSW2E:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GandjustasBlog/~4/qaAm2L4_df0" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/GandjustasBlog/~3/qaAm2L4_df0/solid.html</link><author>noreply@blogger.com (Станислав Выщепан)</author><thr:total>10</thr:total><feedburner:origLink>http://gandjustas.blogspot.com/2011/08/solid.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-1302241583051203640.post-8063431029953438640</guid><pubDate>Mon, 22 Aug 2011 06:00:00 +0000</pubDate><atom:updated>2011-08-22T13:46:52.969+04:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">SharePoint</category><category domain="http://www.blogger.com/atom/ns#">ASP.NET</category><category domain="http://www.blogger.com/atom/ns#">web parts</category><title>Решение задач. Соединенные веб-части, продолжение.</title><description>&lt;p&gt;&lt;a href="http://gandjustas.blogspot.com/2011/08/blog-post.html" target="_blank"&gt;В прошлый&lt;/a&gt; раз я рассказывал как сделать простые соединенные веб-части. Сегодня буду рассказывать как улучшить решение и сделать его более полезным в реальной жизни.&lt;/p&gt;&lt;h4&gt;План действий&lt;/h4&gt;&lt;ol&gt;&lt;li&gt;Фильтрация в SPGridView &lt;/li&gt;
&lt;li&gt;Оптимизация передачи данных между веб-частями &lt;/li&gt;
&lt;li&gt;Добавление параметров для веб-частей &lt;/li&gt;
&lt;li&gt;Асинхронная загрузка дерева &lt;/li&gt;
&lt;li&gt;Bin deployment &lt;/li&gt;
&lt;/ol&gt;&lt;a name='more'&gt;&lt;/a&gt;  &lt;br /&gt;
&lt;cut title="Читать дальше..."&gt;&lt;br /&gt;
&lt;h4&gt;Фильтрация в SPGridView&lt;/h4&gt;&lt;p&gt;Разрабатывая UI на SharePoint мы имеем всю мощь библиотеки контролов ASP.NET, об этом не стоит забывать. Вместо того чтобы реализовывать функционал который уже есть лучше потратить время на изучение существующих возможностей.&lt;/p&gt;&lt;p&gt;Например можно перехватывать события SPGridView для обработки фильтрации, а можно использовать существующий инструментарий контролов Data Source для этих целей.&lt;/p&gt;&lt;p&gt;Подробно описано &lt;a href="http://www.reversealchemy.net/blog/2009/05/01/building-a-spgridview-control-part-1-introducing-the-spgridview/" target="_blank"&gt;в статье&lt;/a&gt;, здесь приведу вкратце код для достижения цели.&lt;/p&gt;&lt;p&gt;Создание дочерних элементов веб-части:&lt;/p&gt;&lt;pre class="brush: csharp;"&gt;this.ds = new ObjectDataSource(typeof(GridTableConsumer).AssemblyQualifiedName, &amp;quot;SelectData&amp;quot;)
    {
        ID = &amp;quot;gridDS&amp;quot;,
        EnableCaching = false
    };
this.ds.ObjectCreating += (sender, e) =&amp;gt; e.ObjectInstance = this;
this.Controls.Add(this.ds);

this.grid = new SPGridView
    {
        ID = &amp;quot;grid&amp;quot;,
        AutoGenerateColumns = false,
        DataSourceID = this.ds.ID,
        AllowFiltering = true,
        FilteredDataSourcePropertyName = &amp;quot;FilterExpression&amp;quot;,
        FilteredDataSourcePropertyFormat = &amp;quot;{1} = '{0}'&amp;quot;,
    };
this.Controls.Add(this.grid);&lt;/pre&gt;&lt;p&gt;Для тех кто не знаком с классом &lt;a href="http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.objectdatasource.aspx" target="_blank"&gt;ObjectDataSource&lt;/a&gt; рекомендую изучить его возможности. Для создания веб-частей очень хорошо подходит.&lt;/p&gt;&lt;p&gt;В данном коде применяется хитрость, чтобы контрол ObjectDataSource не создавал экземпляр класса, в данном случае веб-части, в обработчике события ObjectCreating подсовывается текущий экземпляр.&lt;/p&gt;&lt;p&gt;Далее метод SelectData&lt;/p&gt;&lt;p&gt;&amp;#160;&lt;/p&gt;&lt;p&gt;Чтобы работали фильтры SPGridView лучше всего возвращать объект DataTable.&lt;/p&gt;&lt;pre class="brush: csharp;"&gt;IWebPartTable provider;
PropertyDescriptorCollection schema;
ICollection data;

public DataTable SelectData()
{
    EnsureDataAndSchema();

    DataTable result = null;
    if (this.data != null &amp;amp;&amp;amp; this.schema != null)
    {
        result = this.data.ToDataTable(this.schema);
    }
    return result;
}

private void EnsureDataAndSchema()
{
    if (this.data == null)
    {
        this.provider.GetTableData((d, s) =&amp;gt; 
        {
            this.data = d;
            this.schema = s;
        });
    }
}&lt;/pre&gt;&lt;p&gt;В данном случае используется простые extension-метод GetTableData и ToDataTable. Их код можно посмотреть в исходниках.&lt;/p&gt;&lt;p&gt;Так как автоматически колонки для SPGridView не создаются, то необходимо это делать в коде, причем на наиболее позднем этапе жизненного цикла.&lt;/p&gt;&lt;pre class="brush: csharp;"&gt;protected override void OnPreRender(EventArgs e)
{
    base.OnPreRender(e);

    EnsureDataAndSchema();
    GenerateGridColumns(this.grid, this.schema);
    this.grid.DataBind();
}

private static void GenerateGridColumns(SPGridView grid, PropertyDescriptorCollection properties)
{
    grid.Columns.Clear();
    if (properties != null)
    {
        var fields = properties.OfType&amp;lt;PropertyDescriptor&amp;gt;()
                               .Select(p =&amp;gt; new SPBoundField
                               {
                                   DataField = p.Name,
                                   HeaderText = p.DisplayName,
                                   SortExpression = p.Name
                               })
                               .ToList();
        fields.ForEach(grid.Columns.Add);
        grid.FilterDataFields = string.Join(&amp;quot;,&amp;quot;, fields.Select(f =&amp;gt; f.DataField).ToArray());
    }
}&lt;/pre&gt;&lt;p&gt;Последняя строка в GenerateGridColumns необходима чтобы указать по каким колонкам можно фильтровать.&lt;/p&gt;&lt;p&gt;Ну собственно этого достаточно чтобы работала фильтрация в SPGridView. Все это выглядит как стандартное представление для списка.&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh6.ggpht.com/-UAwSWxNfLV8/TlFzMC0gsoI/AAAAAAAAAE0/FfA9nK9cvfU/s1600-h/image%25255B3%25255D.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://lh5.ggpht.com/-JM-t7GPAvZg/TlFzM9NkRTI/AAAAAAAAAE4/94cQnLHYTXo/image_thumb%25255B1%25255D.png?imgmax=800" width="517" height="649" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;h4&gt;Оптимизация&lt;/h4&gt;&lt;p&gt;Теперь получается что веб-часть дерева организаций создает из профилей DataTable, передает его веб-части представления в виде таблицы и там еще раз создается DataTable. Можно оптимизировать и создавать таблицу один раз.&lt;/p&gt;&lt;p&gt;Мы можем передавать с помощью интерфейса IWebPartTable сам список сотрудников в выбранном подразделении, а в Schema передавать PropertyDescriptorCollection, которые позволят вытаскивать данные из профилей пользователей.&lt;/p&gt;&lt;p&gt;Для класса UserProfile таких дескрипторов нет, но никто не мешает их написать&lt;/p&gt;&lt;pre class="brush: csharp; collapse: true;"&gt;public class UserProfilePropertyDescriptor: PropertyDescriptor
{
    public UserProfilePropertyDescriptor(ProfileSubtypeProperty propery)
        :base(propery.Name, new Attribute[] { new DisplayNameAttribute(propery.DisplayName)})
    {
    }

    public override bool CanResetValue(object component)
    {
        return false;
    }

    public override Type ComponentType
    {
        get { return typeof(UserProfile); }
    }

    public override object GetValue(object component)
    {
        return Convert.ToString((component as UserProfile)[this.Name].Value);
    }

    public override bool IsReadOnly
    {
        get { return false; }
    }

    public override Type PropertyType
    {
        get { return typeof(string); }
    }

    public override void ResetValue(object component)
    {
        throw new NotImplementedException();
    }

    public override void SetValue(object component, object value)
    {
        (component as UserProfile)[this.Name].Value = value;
    }

    public override bool ShouldSerializeValue(object component)
    {
        return false;
    }
}&lt;/pre&gt;&lt;p&gt;Теперь нужно выкинуть код формирования таблицы, заменив его гораздо более простым&lt;/p&gt;&lt;pre class="brush: csharp; collapse: true;"&gt;public void GetTableData(TableCallback callback)
{
    if (callback != null)
    {
        EnsureChildControls();
        if (this.tree.SelectedNode != null)
        {
            long recordId = 0;

            if (long.TryParse(this.tree.SelectedValue, out recordId))
            {
                var profiles = this.OrganizationProfileManager
                                   .GetOrganizationProfile(recordId)
                                   .GetImmediateMembers();
                callback(profiles);
            }
        }
    }
}&lt;/pre&gt;&lt;p&gt;И в качестве Schema возвращать набор свойств профиля&lt;/p&gt;&lt;pre class="brush: csharp; collapse: true;"&gt;public PropertyDescriptorCollection Schema
{
    get
    {
        if (this.schema == null)
        {
            var upm = new UserProfileManager(SPServiceContext.Current);
            //Filter section headers from property list
            var props = from prop in upm.DefaultProfileSubtypeProperties
                        where !prop.IsSection
                        orderby prop.DisplayOrder
                        select new UserProfilePropertyDescriptor(prop);
            this.schema = new PropertyDescriptorCollection(props.ToArray());
        }
        return this.schema;
    }
}&lt;/pre&gt;&lt;h4&gt;Параметры веб-частей&lt;/h4&gt;&lt;p&gt;Веб-часть дерева организаций все время возвращает все свойства профиля, в реальном случае такой сценарий бесполезен. Надо научить веб-часть возвращать только нужные свойства.&lt;/p&gt;&lt;p&gt;Для этого надо создать параметр, который будет хранить список свойств. &lt;/p&gt;&lt;pre class="brush: csharp;"&gt;[WebBrowsable(false)]
[Personalizable(PersonalizationScope.Shared)]
public string ProfileProperties { get; set; }

public HashSet&amp;lt;string&amp;gt; ProfilePropertyNames
{
    get
    {
        return new HashSet&amp;lt;string&amp;gt;((ProfileProperties ?? &amp;quot;&amp;quot;).Split(new[] { PropertyNamesDelimeter }, StringSplitOptions.None));
    }
    set
    {
        ProfileProperties = string.Join(PropertyNamesDelimeter, value.ToArray());
    }
}&lt;/pre&gt;&lt;p&gt;Атрибут WebBrowsable с параметром false говорит что не надо генерировать интерфейс для ввода параметра, а Personalizable говорит что значение этого свойствах надо хранить в свойствах веб-части.&lt;/p&gt;&lt;p&gt;Чтобы редактировать параметр нужен Custom Tool Part, сделать его довольно легко. Надо переопределить всего два метода, один для создания дочерних контролов, как у обычной веб-части, а второй для сохранения настроек.&lt;/p&gt;&lt;pre class="brush: csharp; collapse: true;"&gt;public class OrganizationTreeToolPart : ToolPart
{
    ListBox list;
    public OrganizationTreeToolPart()
    {
        this.Title = &amp;quot;Profile properties&amp;quot;;
    }

    protected OrganizationTree WebPart
    {
        get
        {
            return this.ParentToolPane.SelectedWebPart as OrganizationTree;
        }
    }

    protected override void CreateChildControls()
    {
        this.list = new ListBox()
        {
            SelectionMode = ListSelectionMode.Multiple,
            Height = Unit.Pixel(200)
        };
        var names = this.WebPart.ProfilePropertyNames;
        var upm = new UserProfileManager(SPServiceContext.Current);
        var items = from p in upm.DefaultProfileSubtypeProperties
                    where !p.IsSection
                    orderby p.DisplayOrder
                    select new ListItem(
                        string.Format(&amp;quot;{0} ({1})&amp;quot;, p.DisplayName, p.Name),
                        p.Name)
                        {
                            Selected = names.Contains(p.Name)
                        };
        this.list.Items.AddRange(items.ToArray());
        this.Controls.Add(this.list);
    }

    public override void ApplyChanges()
    {
        var set = new HashSet&amp;lt;string&amp;gt;(this.list
                    .GetSelectedIndices()
                    .Select(i =&amp;gt; this.list.Items[i].Value));
        this.WebPart.ProfilePropertyNames = set;
    }
}&lt;/pre&gt;&lt;p&gt;В этом Tool Part создается список с возможностью множественного выбора, который заполняется свойствами профилей пользователей.&lt;/p&gt;&lt;p&gt;Чтобы отображать этот Custom Tool Part необходимо веб-часть унаследовать от Microsoft.SharePoint.WebParts.WebPart и переопределить метод GetToolParts&lt;/p&gt;&lt;pre class="brush: csharp;"&gt;public override ToolPart[] GetToolParts()
{
    var tps = base.GetToolParts().ToList();
    tps.Add(new OrganizationTreeToolPart());
    return tps.ToArray();
}&lt;/pre&gt;&lt;p&gt;Далее чтобы использовать это свойство достаточно слегка изменить возвращаемую схему&lt;/p&gt;&lt;pre class="brush: csharp;"&gt;public PropertyDescriptorCollection Schema
{
    get
    {
        if (this.schema == null)
        {
            var names = this.ProfilePropertyNames; //(1)
            var upm = new UserProfileManager(SPServiceContext.Current);
            //Filter section headers from property list
            var props = from prop in upm.DefaultProfileSubtypeProperties
                        where !prop.IsSection
                        where names.Contains(prop.Name) //(2)
                        orderby prop.DisplayOrder
                        select new UserProfilePropertyDescriptor(prop);
            this.schema = new PropertyDescriptorCollection(props.ToArray());
        }
        return this.schema;
    }
}&lt;/pre&gt;&lt;p&gt;Изменения выделены комментариями.&lt;/p&gt;&lt;p&gt;Теперь пользователь сможет самостоятельно выбирать какие свойства профиля отправлять веб-части получателю.&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh4.ggpht.com/-2ZYNwIgb1wI/TlFzNLdWGxI/AAAAAAAAAE8/oXEeayaZI_c/s1600-h/image%25255B7%25255D.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://lh3.ggpht.com/-9deC-xrBGUc/TlFzNnbDDLI/AAAAAAAAAFA/1aIu9wd9qss/image_thumb%25255B3%25255D.png?imgmax=800" width="452" height="312" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;h4&gt;Асинхронная загрузка дерева организаций по требованию&lt;/h4&gt;&lt;p&gt;В первоначальном варианте дерево строилось при загрузке веб-части, но необходимости в этом нет. Достаточно построить дерево, из части узлов, а остальное загружать по требованию.&lt;/p&gt;&lt;p&gt;И снова нам на помощь приходит ASP.NET. Стандартный контрол TreeView умеет загружать узлы по требованию с помощь AJAX. Нам для этого надо сделать совсем мало:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Установить свойство &lt;a href="http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.treeview.populatenodesfromclient.aspx" target="_blank"&gt;TreeView.PopulateNodesFromClient&lt;/a&gt; = true &lt;/li&gt;
&lt;li&gt;Указывать &lt;a href="http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.treenode.populateondemand.aspx" target="_blank"&gt;TreeNode .PopulateOnDemand&lt;/a&gt; = true, если требуется загрузка узлов по требованию &lt;/li&gt;
&lt;li&gt;Обрабатывать событие &lt;a href="http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.treeview.treenodepopulate.aspx" target="_blank"&gt;TreeView.TreeNodePopulate&lt;/a&gt;, в обработчике которого и заниматься загрузкой узлов. &lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;Код для загрузки по требованию&lt;/p&gt;&lt;pre class="brush: csharp; collapse: true;"&gt;protected override void CreateChildControls()
{
    this.tree = new TreeView();
    this.tree.EnableClientScript = true;
    this.tree.PopulateNodesFromClient = true;
    this.tree.TreeNodePopulate += new TreeNodeEventHandler(tree_TreeNodePopulate);
    this.tree.Nodes.Add(ToTreeNode(this.OrganizationProfileManager.RootOrganization));
    Controls.Add(this.tree);
}


void tree_TreeNodePopulate(object sender, TreeNodeEventArgs e)
{
    var recordId = int.Parse(e.Node.Value);
    var profile = this.OrganizationProfileManager.GetOrganizationProfile(recordId);
    foreach (var child in profile.GetChildren())
    {
        e.Node.ChildNodes.Add(ToTreeNode(child));
    }

}

private static TreeNode ToTreeNode(OrganizationProfile child)
{
    return new TreeNode(child.DisplayName, child.RecordId.ToString())
           {
               PopulateOnDemand = child.HasChildren,
           };
}&lt;/pre&gt;&lt;h4&gt;Bin deployment &lt;/h4&gt;&lt;p&gt;Сборки решений уровня фермы в SharePoint можно развертывать как в GAC, так и в каталог Bin веб-приложений SharePoint. Второй вариант подходит только для веб-частей, но в данном случае больше и не нужно.&lt;/p&gt;&lt;p&gt;Чтобы развернуть сборку в Bin надо поменять одну настройку в свойствах проекта&lt;/p&gt;&lt;p&gt;&lt;a href="http://lh6.ggpht.com/-MAQOOyVT7Vo/TlFzOB6EIUI/AAAAAAAAAFE/NcWLzl5NOiA/s1600-h/image%25255B11%25255D.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://lh4.ggpht.com/-3QqPXU1WZq0/TlFzO5GT6LI/AAAAAAAAAFI/LyaFE2HlYcw/image_thumb%25255B5%25255D.png?imgmax=800" width="410" height="405" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Но после этого решение откажется работать. Для сборок, развертываемых в Bin, необходимо вручную прописывать требуемый уровень разрешений для работы. Это можно сделать руками, но можно воспользоваться расширением &lt;a href="http://spsf.codeplex.com/" target="_blank"&gt;SPSF&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;В результате в solution manifest (package) должен появиться раздел&lt;/p&gt;&lt;pre class="brush: xml;"&gt;&amp;lt;CodeAccessSecurity&amp;gt;
  &amp;lt;PolicyItem&amp;gt;
    &amp;lt;PermissionSet class=&amp;quot;NamedPermissionSet&amp;quot; version=&amp;quot;1&amp;quot;&amp;gt;
      &amp;lt;IPermission class=&amp;quot;Microsoft.SharePoint.Security.SharePointPermission, Microsoft.SharePoint.Security, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c&amp;quot; version=&amp;quot;1&amp;quot; ObjectModel=&amp;quot;True&amp;quot; /&amp;gt;
      &amp;lt;IPermission class=&amp;quot;SecurityPermission&amp;quot; version=&amp;quot;1&amp;quot; Flags=&amp;quot;Execution&amp;quot; /&amp;gt;
      &amp;lt;IPermission class=&amp;quot;AspNetHostingPermission&amp;quot; version=&amp;quot;1&amp;quot; Level=&amp;quot;Minimal&amp;quot; /&amp;gt;
    &amp;lt;/PermissionSet&amp;gt;
    &amp;lt;Assemblies&amp;gt;
      &amp;lt;Assembly
        Name=&amp;quot;$SharePoint.Project.AssemblyName$&amp;quot;
        Version=&amp;quot;$SharePoint.Project.AssemblyVersion$&amp;quot;
        PublicKeyBlob=&amp;quot;$SharePoint.Project.AssemblyPublicKeyBlob$&amp;quot; /&amp;gt;
    &amp;lt;/Assemblies&amp;gt;
  &amp;lt;/PolicyItem&amp;gt;
&amp;lt;/CodeAccessSecurity&amp;gt;&lt;/pre&gt;&lt;p&gt;Кстати SPSF генерирует переносы строк, но с ними не работает.&lt;/p&gt;&lt;h4&gt;Заключение&lt;/h4&gt;&lt;p&gt;Если вы дочитали до этого момента и ничего не поняли, то рекомендую более внимательно изучать ASP.NET Controls и .NET FW в частности компонентную модель, вопросы работы с данными и безопасностью, а также внимательно изучить ссылки, которые я привел в &lt;a href="http://gandjustas.blogspot.com/2011/08/blog-post.html" target="_blank"&gt;первом посте&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Исходный код можно найти на CodePlex:&amp;#160; &lt;a href="http://spsamples.codeplex.com/"&gt;http://spsamples.codeplex.com/&lt;/a&gt;&lt;/p&gt;&lt;/cut&gt;&lt;br /&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1302241583051203640-8063431029953438640?l=gandjustas.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=zI919mLbSF4:450_nqXokww:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=zI919mLbSF4:450_nqXokww:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?i=zI919mLbSF4:450_nqXokww:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=zI919mLbSF4:450_nqXokww:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GandjustasBlog/~4/zI919mLbSF4" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/GandjustasBlog/~3/zI919mLbSF4/blog-post_22.html</link><author>noreply@blogger.com (Станислав Выщепан)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://lh5.ggpht.com/-JM-t7GPAvZg/TlFzM9NkRTI/AAAAAAAAAE4/94cQnLHYTXo/s72-c/image_thumb%25255B1%25255D.png?imgmax=800" height="72" width="72" /><thr:total>2</thr:total><feedburner:origLink>http://gandjustas.blogspot.com/2011/08/blog-post_22.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-1302241583051203640.post-7327984666011735444</guid><pubDate>Wed, 10 Aug 2011 03:00:00 +0000</pubDate><atom:updated>2011-08-11T19:55:32.545+04:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">SharePoint</category><category domain="http://www.blogger.com/atom/ns#">ASP.NET</category><category domain="http://www.blogger.com/atom/ns#">web parts</category><title>Решение задач. Соединенные веб-части.</title><description>&lt;p&gt;Ранее я приводил &lt;a href="http://gandjustas.blogspot.com/2011/08/sharepoint.html" target="_blank"&gt;список задач для проверки навыков программирования для SharePoint&lt;/a&gt;. Сегодня напишу о решении второй задачи про соединенные веб-части.&lt;/p&gt;  &lt;h4&gt;Задача&lt;/h4&gt;  &lt;ol&gt;   &lt;li&gt;Создать веб-часть дерева организаций (подразделений) &lt;/li&gt;    &lt;li&gt;Сделать её провайдером IWebPartTable &lt;/li&gt;    &lt;li&gt;При выборе узла дерева веб-часть должна отправлять&amp;#160;&amp;#160; профили пользователей в организации &lt;/li&gt;    &lt;li&gt;Создать веб-часть потребитель IWebPartTable с помощью SPGridView &lt;/li&gt; &lt;/ol&gt;  &lt;h4&gt;&lt;/h4&gt;  &lt;a name='more'&gt;&lt;/a&gt;  &lt;h4&gt;Подготовка&lt;/h4&gt;  &lt;p&gt;Разработка SharePoint полна тонких моментов, поэтому критически необходимо &lt;em&gt;внимательно&lt;/em&gt; читать документацию на MSDN и то что пишут в блогах &lt;em&gt;до написания кода&lt;/em&gt;.&lt;/p&gt;  &lt;p&gt;Для решения задачи с соединенными веб частями понадобятся следующие ссылки:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;&lt;a href="http://platinumdogs.wordpress.com/2008/10/14/sharepoint-webpart-lifecycle-events/" target="_blank"&gt;Жизненный цикл веб-частей в SharePoint&lt;/a&gt;. Не уверен что сведения в этой статье точные, но они достаточно полные (помните правило: &lt;em&gt;если о чем-то пишут, то не значит что оно так и работает&lt;/em&gt;). &lt;/li&gt;    &lt;li&gt;&lt;a href="http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.webparts.iwebparttable.aspx" target="_blank"&gt;Описание IWebPartTable&lt;/a&gt;. В конце статьи есть пример реализации, все довольно просто. &lt;/li&gt;    &lt;li&gt;Описание SPGridView: &lt;a href="http://www.reversealchemy.net/blog/2009/05/01/building-a-spgridview-control-part-1-introducing-the-spgridview/" target="_blank"&gt;часть 1&lt;/a&gt;, &lt;a href="http://www.reversealchemy.net/blog/2009/05/24/building-a-spgridview-control-part-2-filtering/" target="_blank"&gt;часть 2&lt;/a&gt;, &lt;a href="http://www.reversealchemy.net/blog/2009/11/24/building-a-spgridview-control-part-3-spmenufield/" target="_blank"&gt;часть 3&lt;/a&gt;, &lt;a href="http://www.reversealchemy.net/blog/2010/02/01/building-a-spgridview-control-%E2%80%93-part-4-filtering-multiple-columns/" target="_blank"&gt;часть 4&lt;/a&gt;. Очень хорошая серия если будете использовать SPGridView в своих проектах. &lt;/li&gt;    &lt;li&gt;Классы &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.office.server.userprofiles.organizationprofilemanager.aspx" target="_blank"&gt;OrganizationProfileManager&lt;/a&gt; и &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.office.server.userprofiles.userprofilemanager.aspx" target="_blank"&gt;UserProfileManager&lt;/a&gt; для работы с профилями. &lt;/li&gt; &lt;/ol&gt;  &lt;h4&gt;Создание проекта&lt;/h4&gt;  &lt;p&gt;Выбираем Empty SharePoint Project, создаем его как Farm Solution&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh5.ggpht.com/--PbNnUjjang/TkED8idxE_I/AAAAAAAAAEU/MPps7Y77dUY/s1600-h/image%25255B7%25255D.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://lh4.ggpht.com/-B2xkKBxJ_Eo/TkED9C9SrrI/AAAAAAAAAEY/mojZ3e9ETVM/image_thumb%25255B3%25255D.png?imgmax=800" width="596" height="167" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Добавляем две веб-части&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh4.ggpht.com/-uA7VY9Y5Lws/TkED9qkYjiI/AAAAAAAAAEc/pbPE4Up_F8s/s1600-h/image%25255B12%25255D.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://lh4.ggpht.com/-kaQ8d4MZU24/TkED-YixUAI/AAAAAAAAAEg/E8eTIXuAddo/image_thumb%25255B6%25255D.png?imgmax=800" width="596" height="168" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;По-умолчанию Visual Studio 2010 прописывает в .webpart файлах имена классов. это крайне неудобно, так как необходимо вручную править .webpart файлы при переименовании классов\изменении пространства имен.&lt;/p&gt;  &lt;p&gt;Используя сведения &lt;a href="http://msdn.microsoft.com/en-us/library/ee231545%28VS.100%29.aspx" target="_blank"&gt;отсюда&lt;/a&gt;, добавляем атрибуты классам веб-частей и изменяем .webpart файлы.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh5.ggpht.com/-J-d3MzJ-Iaw/TkED-8IMA5I/AAAAAAAAAEk/ArAARdFAdjk/s1600-h/image%25255B20%25255D.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://lh5.ggpht.com/--7tOF9SiaXU/TkED_kINGII/AAAAAAAAAEo/6L7AGcAyV54/image_thumb%25255B10%25255D.png?imgmax=800" width="599" height="81" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh3.ggpht.com/--PUa-KESPsQ/TkEEAWmM9xI/AAAAAAAAAEs/kDv8KXzNbwU/s1600-h/image%25255B25%25255D.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://lh3.ggpht.com/-apHlZrKklEo/TkEEA8fCkLI/AAAAAAAAAEw/VwI-Uu8mnsA/image_thumb%25255B13%25255D.png?imgmax=800" width="598" height="97" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;h4&gt;Создание дерева организаций&lt;/h4&gt;  &lt;p&gt;Для вывода дерева будем использовать контрол TreeView.&lt;/p&gt;  &lt;pre class="brush: csharp;"&gt;private TreeView tree;

protected override void CreateChildControls()
{
    this.tree = new TreeView();
    Controls.Add(this.tree);
}&lt;/pre&gt;

&lt;p&gt;Заполнение дерева организаций делается так:&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);

    EnsureChildControls();
    var opm = new OrganizationProfileManager(SPServiceContext.Current);
    this.tree.Nodes.Clear();
    AddChildNodesRecursive(tree.Nodes, opm.RootOrganization);
}

private void AddChildNodesRecursive(TreeNodeCollection treeNodeCollection, 
                                    OrganizationProfile organizationProfile)
{
    var node = new TreeNode(organizationProfile.DisplayName, 
                            organizationProfile.RecordId.ToString());
    foreach (var child in organizationProfile.GetChildren())
    {
        AddChildNodesRecursive(node.ChildNodes, child);
    }

    treeNodeCollection.Add(node);
}&lt;/pre&gt;

&lt;p&gt;Те кто много писал кода для asp.net webforms сразу пытаются написать в методе OnLoad что-то вроде&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;if(!this.Page.IsPostBack)
{
    //....
}&lt;/pre&gt;
&lt;font face="Georgia"&gt;Такой код верный если контрол находится в разметке страницы и его метод OnLoad выполняется при первой загрузке страницы. Для веб-частей в общем случае это неверно. Веб-части могут быть добавлены на страницу после её первой загрузки и код внутри if не выполнится. В таких случаях рекомендую анализировать состояние контролов и выполнять загрузку по необходимости. Но об этом в следующих постах.&lt;/font&gt; 

&lt;h4&gt;Провайдер IWebPartTable&lt;/h4&gt;

&lt;p&gt;Чтобы создать веб-часть провайдер вам необходимо в классе веб-части определить метод, который возвращает интерфейс соединения и пометить его атрибутом.&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;[ConnectionProvider(&amp;quot;Users&amp;quot;)]
public IWebPartTable SendUsersFromSelectedNode()
{
    return this;
}&lt;/pre&gt;

&lt;p&gt;Название метода не имеет значения. Текст указанный в атрибуте используется в UI для формирования пункта меню, если создаете многоязычное приложение, то необходимо позаботиться о локализации. Сопоставление провайдера и потребителя осуществляется только по типу интерфейса.&lt;/p&gt;

&lt;p&gt;Почти всегда методы провайдеров возвращают this, а необходимый интерфейс реализуется веб-частью. Таким образом методы интерфейса могут обращаться к состоянию веб-части для получения значений.&lt;/p&gt;

&lt;p&gt;Теперь необходимо при запросе данных потребителем формировать некоторый объект, точнее коллекцию объектов (обычно используют DataTable) и схему этих данных в виде PropertyDescriptorCollection. Тут очень важны два момента:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;em&gt;Формировать данные при запросе потребителем, а не при изменении состояния веб-части&lt;/em&gt;. Многие, кто выполнял данную задачу, пытались перехватывать событие дерева об изменении выбранного узла, формировали DataTable и записывали в поле класса. Естественно после PostBack значение терялось. А если попытаться сохранить эти данные во ViewState, то начнет пухнуть страница. &lt;/li&gt;

  &lt;li&gt;&lt;em&gt;Формировать данные только тогда, когда потребитель запросит их&lt;/em&gt;, потому что веб-части могут быть не соединены и работа будет делаться впустую. &lt;/li&gt;
&lt;/ol&gt;

&lt;pre class="brush: csharp;"&gt;private DataTable usersTable;

public void GetTableData(TableCallback callback)
{
    if (callback != null)
    {
        EnsureUsersTable();
        if (this.usersTable != null)
        {
            callback(this.usersTable.DefaultView);
        }
    }
}

public PropertyDescriptorCollection Schema
{
    get 
    {
        EnsureUsersTable();
        if (this.usersTable != null &amp;amp;&amp;amp; this.usersTable.Rows.Count &amp;gt; 0)
        {                    
            return TypeDescriptor.GetProperties(this.usersTable.DefaultView[0]);
        }
        return null;
    }
}&lt;/pre&gt;

&lt;p&gt;Интефейс IWebPartTable не говорит о том в каком порядке будут вызываться члены Schema и GetTableData, поэтому для устойчивости решения необходимо поддерживать любой сценарий. &lt;/p&gt;

&lt;p&gt;Сама загрузка данных происходит в методе EnsureUsersTable:&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;private void EnsureUsersTable()
{
    EnsureChildControls();
    if (this.usersTable == null)
    {
        if (this.tree.SelectedNode != null)
        {
            long recordId = 0;

            if (long.TryParse(this.tree.SelectedValue, out recordId))
            {
                this.usersTable = LoadOrganizationMembers(recordId);
            }
        }
    }
}&lt;/pre&gt;

&lt;p&gt;Приведенный выше код реализует паттерн Ensure\Create, который часто используется в asp.net webforms. Жизненный цикл страницы и контролов довольно сложен и может изменяться в зависимости от различных условий. Некоторые события могут не выполняться вообще, и поэтому необходимо удостовериться что данные загружены (Ensure). &lt;/p&gt;

&lt;p&gt;Сам код LoadOrganizationMembers довольно очевидный. Первая часть метода формирует колонки DataTable, вторая часть метода заполняет строки DataTable.&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;private DataTable LoadOrganizationMembers(long recordId)
{
    var opm = new OrganizationProfileManager(SPServiceContext.Current);
    var org = opm.GetOrganizationProfile(recordId);

    var upm = new UserProfileManager(SPServiceContext.Current);

    //Filter section headers from property list
    var props = from prop in upm.DefaultProfileSubtypeProperties
                where !prop.IsSection
                orderby prop.DisplayOrder
                select prop;

    var columns = from p in props
                  select new DataColumn
                  {
                      ColumnName = p.Name,
                      Caption = p.DisplayName                              
                  };

    var result = new DataTable();
    result.Columns.AddRange(columns.ToArray());

    //Load profile properties to table
    foreach (var member in org.GetImmediateMembers())
    {
        var row = result.NewRow();
        foreach (var p in props)
        {
            row[p.Name] = Convert.ToString(member[p.Name].Value);
        }
        result.Rows.Add(row);
    }
    return result;
}&lt;/pre&gt;

&lt;p&gt;Обратите внимание на использование Convert, класс незаслуженно забытый программистами на C#. В SharePoint часто туда-сюда передаются object, которые могут быть внутри любыми типами. Наиболее устойчивый способ преобразовать к нужному типу – с использованием класса &lt;a href="http://msdn.microsoft.com/en-us/library/system.convert.aspx" target="_blank"&gt;Convert&lt;/a&gt;. Данный класс выполняет приведение типов или парсинг в зависимости от типа входного значения, кроме того он наиболее корректно учитывает null.&lt;/p&gt;

&lt;h4&gt;Создание веб-части потребителя&lt;/h4&gt;

&lt;p&gt;Для создания веб-части потребителя требуется определить один метод и пометить его атрибутом&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;private IWebPartTable provider;

[ConnectionConsumer(&amp;quot;Table&amp;quot;)]
public void GetTableProvider(IWebPartTable provider)
{
    this.provider = provider;
}&lt;/pre&gt;
Как и в случае с провайдером название метода не имеет значения, а текст в атрибуте используется в UI. Сопоставление производится по типу аргумента. 

&lt;p&gt;Далее создание контролов&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;protected override void CreateChildControls()
{
    this.grid = new SPGridView();
    grid.AutoGenerateColumns = false;
    this.Controls.Add(grid);
}&lt;/pre&gt;

&lt;p&gt;Как написано в &lt;a href="http://platinumdogs.wordpress.com/2008/10/14/sharepoint-webpart-lifecycle-events/" target="_blank"&gt;статье&lt;/a&gt;, хорошее место для вызова методов получения данных от провайдера метод OnPreRender&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;protected override void OnPreRender(EventArgs e)
{
    base.OnPreRender(e);
    this.provider.GetTableData(d =&amp;gt;
    {
        if (d != null &amp;amp;&amp;amp; d.Count &amp;gt; 0)
        {
            this.grid.DataSource = d;
            GenerateColumns(this.grid, this.provider.Schema);
            this.grid.DataBind();
        }
    });
}&lt;/pre&gt;

&lt;p&gt;Тут важно помнить что callback, переданный в GetTableData может быть не вызван вообще. Поэтому все действия по генерации колонок в гриде и связыванию с данными выполняются внутри callback. &lt;/p&gt;

&lt;p&gt;Сам метод генерации колонок&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;private void GenerateColumns(SPGridView grid, PropertyDescriptorCollection schema)
{
    grid.Columns.Clear();
    if (schema != null)
    {
        foreach (PropertyDescriptor property in schema)
        {
            grid.Columns.Add(
                new SPBoundField 
                { 
                    DataField = property.Name, 
                    HeaderText = property.DisplayName, 
                    SortExpression = property.Name 
                });
        }
    }
}&lt;/pre&gt;

&lt;p&gt;Этого достаточно для вывода статической таблицы.&lt;/p&gt;

&lt;p&gt;Суммарно весь код занимает не более 100 строк с точки зрения Visual Studio. Его вполне можно написать за час. &lt;/p&gt;

&lt;p&gt;Следующий раз расскажу про то как оптимизировать передачу данных о профилях, как добавить сортировку с фильтрацией в таблицу, о Bin deployment и других интересных вещах, а также где взять исходники этого проекта.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1302241583051203640-7327984666011735444?l=gandjustas.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=fPyg9eG7MJg:n7LvyYZnv_Y:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=fPyg9eG7MJg:n7LvyYZnv_Y:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?i=fPyg9eG7MJg:n7LvyYZnv_Y:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=fPyg9eG7MJg:n7LvyYZnv_Y:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GandjustasBlog/~4/fPyg9eG7MJg" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/GandjustasBlog/~3/fPyg9eG7MJg/blog-post.html</link><author>noreply@blogger.com (Станислав Выщепан)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://lh4.ggpht.com/-B2xkKBxJ_Eo/TkED9C9SrrI/AAAAAAAAAEY/mojZ3e9ETVM/s72-c/image_thumb%25255B3%25255D.png?imgmax=800" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://gandjustas.blogspot.com/2011/08/blog-post.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-1302241583051203640.post-4773508752322684225</guid><pubDate>Mon, 08 Aug 2011 04:00:00 +0000</pubDate><atom:updated>2011-08-08T08:00:18.193+04:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">SharePoint</category><category domain="http://www.blogger.com/atom/ns#">Silverlight</category><title>Создание Silverlight приложений для SharePoint. Часть 1.</title><description>&lt;h4&gt;Зачем?&lt;/h4&gt;  &lt;p&gt;Как ни странно, но этот вопрос обычно не задают. Многие хотят получить интерактивные интерфейсы SharePoint, но при этом не хотят изучать особенности объектной модели и пользоваться знакомыми инструментами.&lt;/p&gt;  &lt;p&gt;Если все же подумать, то причины для создания Silverlight приложения могу быть следующие:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Необходимость создания высокоинтерактивого интерфейса в SharePoint.      &lt;br /&gt;Стандартный интерфейс sharepoint хоть построен с помощью ajax, но все же не является настолько отзывчивым, насколько этого хотят пользователи. &lt;/li&gt;    &lt;li&gt;Необходимость производить тяжелые расчеты в окружении с ограниченным доверием.      &lt;br /&gt;Например при развертывании приложений в Office365 вы ограничены sandbox для серверной объектной модели. Ресурсы, потребленные серверным кодом, будут считаться и ваше решение может быть отключено. Кроме того существует тайм-аут в 30 секунда на выполнение кода в sandbox. Silverlight позволяет преодолеть эти ограничения, переложив вычисления на клиентский компьютер. &lt;/li&gt;    &lt;li&gt;Расширение возможностей пользовательского интерфейса SharePoint.      &lt;br /&gt;Один из примеров таких расширений – возможность использовать drag-and-drop файлов с локального компьютера в библиотеку документов (&lt;a href="https://www.nothingbutsharepoint.com/sites/devwiki/articles/Pages/DesktopDragAndDropUploading.aspx" target="_blank"&gt;ссылка на статью&lt;/a&gt;). &lt;/li&gt;    &lt;li&gt;Создание бизнес-приложения, которое в дальнейшем может быть установлено для автономной работы (out-of-browser). &lt;/li&gt;    &lt;li&gt;Hi-end media-сценарий. Без Silverlight пока что невозможен smooth streaming.&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;В указанных выше случаях Silverlight – практически единственная возможность решить задачу.    &lt;br /&gt;Кроме того Silverlight может использоваться для преодоления ограничений sandbox, связанных с вызовом внешних сервисов, для создания графических элементов управления, итд. Но все это с таким же успехом может быть реализовано с помощью HTML5\javascript.&lt;/p&gt;  &lt;h4&gt;Почему не стоит использовать Silverlight&lt;/h4&gt;  &lt;p&gt;Приложения на Silverlight имеют достаточно много ограничений чтобы задуматься об их использовании.&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;&lt;em&gt;Мобильные устройства&lt;/em&gt;. На мобильных устройствах Silverlight нет, если ваше приложение не предусматривает fallback к обычному html+js, то вы потеряете функционал на мобильных устройствах. &lt;/li&gt;    &lt;li&gt;&lt;em&gt;Брендинг&lt;/em&gt;. Чтобы приложение Silvelight выглядело как остальной портал необходимо приложить довольно много усилий. &lt;/li&gt;    &lt;li&gt;&lt;em&gt;UX&lt;/em&gt;. Несмотря на богатую графику, простая операция вроде “выделить кусок текста и скопировать” его в Silverlight доступна только в специальных контролах. &lt;/li&gt;    &lt;li&gt;&lt;em&gt;Время загрузки&lt;/em&gt;. Silverlight приложения загружаются после отрисовки страницы, это всегда заметно глазом человека. &lt;/li&gt; &lt;/ol&gt;  &lt;h4&gt;Если вы все таки собираетесь использовать Silverlight в SharePoint&lt;/h4&gt;  &lt;p&gt;Посмотрите как сам SharePoint использует Silverlight. Меню создания сайта\списка сделано на Silverlight, это скорее неудачное решение. Данное меню сильно таращит на русском языке. При возникновении ошибки в процессе создания элемента появляется popup с Corellation ID, но этот Corellation ID оттуда нельзя скопировать.    &lt;br /&gt;Другой, гораздо более удачный, пример – множественная загрузка файлов в библиотеку. Это почти незаметный компонент, который тем не менее может сильно упростить жизнь пользователям.     &lt;br /&gt;Еще один хороший пример использования Silverligth в SharePoint – инструмент, называемый decomposition tree, из Performane Point Services. С его помощью можно формировать декомпозицию мер по измерениями в кубе SSAS.     &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;Посмотрите также проект &lt;a href="http://peoplesearchpivot.codeplex.com/"&gt;SharePoint People Search Pivot Viewer WebPart&lt;/a&gt;. Очень показательно какие возможности имеет Silverlight для визуализации данных SharePoint.&lt;/p&gt;  &lt;p&gt;Чтобы научиться создавать приложения на Silverlight для SharePoint можно пройти &lt;a href="http://msdn.microsoft.com/en-us/gg981430" target="_blank"&gt;учебный курс&lt;/a&gt;.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1302241583051203640-4773508752322684225?l=gandjustas.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=uH0SGJ5W3ZI:i5lwL1ZuFUw:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=uH0SGJ5W3ZI:i5lwL1ZuFUw:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?i=uH0SGJ5W3ZI:i5lwL1ZuFUw:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=uH0SGJ5W3ZI:i5lwL1ZuFUw:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GandjustasBlog/~4/uH0SGJ5W3ZI" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/GandjustasBlog/~3/uH0SGJ5W3ZI/silverlight-sharepoint-1.html</link><author>noreply@blogger.com (Станислав Выщепан)</author><thr:total>2</thr:total><feedburner:origLink>http://gandjustas.blogspot.com/2011/08/silverlight-sharepoint-1.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-1302241583051203640.post-8895791857367764109</guid><pubDate>Fri, 05 Aug 2011 16:00:00 +0000</pubDate><atom:updated>2011-08-05T20:00:09.526+04:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">.NET</category><category domain="http://www.blogger.com/atom/ns#">SharePoint</category><title>Задачи для проверки навыков разработки SharePoint</title><description>&lt;p&gt;Когда &lt;a href="http://www.specialist.ru/track/t-shpprog" target="_blank"&gt;я читаю курс 10175&lt;/a&gt;, то из положенных 5 дней лекции удается уложить в 4, а в последний день обучающиеся выполняют лабораторную работу, чтобы проверить полученные знания и навыки разработки приложений.&lt;/p&gt;  &lt;p&gt;Ниже приведены 4 задачи, из&amp;#160; которых обучающиеся в парах выполняют одну. Звездочками отмечены задачи, которые не рассматриваются в курсе и требуют самостоятельного изучения.&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Создать систему резервирования ресурсов &lt;/li&gt;    &lt;ul&gt;     &lt;li&gt;Создать список ресурсов &lt;/li&gt;      &lt;li&gt;Создать список (стандартный шаблон списка “Календарь”) со ссылкой на список ресурсов &lt;/li&gt;      &lt;li&gt;Создать обработчик события добавления и изменения, который проверяет непересекаемость интервалов резервирования для ресурса и отменяет действие в случае пересечения &lt;/li&gt;      &lt;li&gt; * Создать веб-часть, отображающую свободные&amp;#160; ресурсы в заданном интервале времени &lt;/li&gt;      &lt;li&gt;Код должен работать в sandbox &lt;/li&gt;   &lt;/ul&gt;    &lt;li&gt;Создать соединенные веб-части&lt;/li&gt;    &lt;ul&gt;     &lt;li&gt;Создать веб-часть отображающую дерево организаций (подразделений) &lt;/li&gt;      &lt;li&gt;Сделать её провайдером IWebPartTable &lt;/li&gt;      &lt;ul&gt;       &lt;li&gt;При выборе узла дерева веб-часть должна отправлять&amp;#160;&amp;#160; профили пользователей в организации &lt;/li&gt;     &lt;/ul&gt;      &lt;li&gt;Создать веб-часть потребитель IWebPartTable с помощью SPGridView &lt;/li&gt;      &lt;li&gt;* Реализовать поддержку фильтрации и сортировки в SPGridView как в представлениях SharePoint&lt;/li&gt;   &lt;/ul&gt;    &lt;li&gt;Создать рабочий процесс обработки инцидентов&lt;/li&gt;    &lt;ul&gt;     &lt;li&gt;Создать список инцидентов (стандартный шаблон списка &amp;quot;”Списко Инцидентов”) &lt;/li&gt;      &lt;li&gt;Создать State Machine Worflow с 3 состояниями: Открыт, Закрыт, Проверен&lt;/li&gt;      &lt;li&gt; В каждом состоянии необходимо назначать пользователю (для простоты администратору) задачу, после выполнения задачи удалять её.&lt;/li&gt;      &lt;li&gt;После после исполнения задачи процесс должен изменять состояние инцидента&lt;/li&gt;      &lt;li&gt;* Сделать график времени закрытия инцидентов по типам с помощью Performace Point Services&lt;/li&gt;   &lt;/ul&gt;    &lt;li&gt;Создать задачу таймера очистки библиотек документов&lt;/li&gt;    &lt;ul&gt;     &lt;li&gt;Задача должна находить и удалять пустые папки в библиотеках документов&amp;#160;&amp;#160;&amp;#160; &lt;/li&gt;      &lt;li&gt;* Доработать задачу таймера чтобы она работала только на заданных узлах (SPWeb) &lt;/li&gt;      &lt;li&gt;* Создать Custom Action в Ribbon и SiteMenu для того чтобы вызвать задачу таймера.       &lt;br /&gt;&lt;/li&gt;   &lt;/ul&gt; &lt;/ol&gt;  &lt;p&gt;ИМХО хороший программист SharePoint должен уметь выполнить любую задачу в течение дня.&lt;/p&gt;  &lt;p&gt;Ваши комментарии?&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1302241583051203640-8895791857367764109?l=gandjustas.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=3iMdAjRI3DU:wzJwofm_x8I:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=3iMdAjRI3DU:wzJwofm_x8I:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?i=3iMdAjRI3DU:wzJwofm_x8I:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=3iMdAjRI3DU:wzJwofm_x8I:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GandjustasBlog/~4/3iMdAjRI3DU" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/GandjustasBlog/~3/3iMdAjRI3DU/sharepoint.html</link><author>noreply@blogger.com (Станислав Выщепан)</author><thr:total>5</thr:total><feedburner:origLink>http://gandjustas.blogspot.com/2011/08/sharepoint.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-1302241583051203640.post-1144731554432001335</guid><pubDate>Fri, 15 Jul 2011 13:26:00 +0000</pubDate><atom:updated>2011-07-15T17:26:24.095+04:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">SharePoint</category><category domain="http://www.blogger.com/atom/ns#">PowerShell</category><title>SharePoint Administrator Roadmap</title><description>&lt;blockquote&gt;   &lt;p&gt;&lt;em&gt;Снова по просьбам &lt;/em&gt;&lt;a href="http://www.gotdotnet.ru/forums/5/138471/651718/#post651718" target="_blank"&gt;&lt;em&gt;читателей&lt;/em&gt;&lt;/a&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;ol&gt;   &lt;li&gt;Для изучения администрирования SharePoint понадобятся базовые знания об администрировании следующих компонент и приложений:&lt;/li&gt;    &lt;ol&gt;     &lt;li&gt;Active Directory Domain Services&lt;/li&gt;      &lt;li&gt;SQL Server 2008&lt;/li&gt;      &lt;li&gt;IIS Weberver&lt;/li&gt;   &lt;/ol&gt;    &lt;li&gt;Для начала просмотреть видеокурс &lt;a href="http://technet.microsoft.com/en-us/sharepoint/ee518660" target="_blank"&gt;Getting Started for IT Pros&lt;/a&gt;. &lt;/li&gt;    &lt;li&gt;Затем курс &lt;a href="http://technet.microsoft.com/en-US/sharepoint/ff420396.aspx" target="_blank"&gt;Advanced IT Professional Training&lt;/a&gt;. Обязательно выполните лабораторки в этом курсе.&lt;/li&gt;    &lt;li&gt;Далее прочитайте &lt;a href="http://gandjustas.blogspot.com/2011/03/sharepoint_31.html" target="_blank"&gt;цикл статей про развертывание SharePoint в организации&lt;/a&gt;.&lt;/li&gt;    &lt;li&gt;Администраторы обычно выполняют&amp;#160; функции Power Users, занимаются небольшими кастомизациями и обучением пользователей. Поэтому крайне необходимо изучать все материалы на сайте &lt;a title="http://office.microsoft.com/ru-ru/training/" href="http://office.microsoft.com/ru-ru/training/"&gt;http://office.microsoft.com/ru-ru/training/&lt;/a&gt;.&lt;/li&gt;    &lt;li&gt;Если вы хотите качественно настроить поиск SharePoint, то вам может пригодиться видеокурс &lt;a href="http://technet.microsoft.com/enterprisesearch/ff960998.aspx"&gt;Enterprise Search IT professional training&lt;/a&gt;. Обязательно выполняйте лабораторки.&lt;/li&gt;    &lt;li&gt;Справку и различные гайды по интересующим вас аспектами SharePoint вы сможете найти в &lt;a href="http://technet.microsoft.com/en-us/sharepoint/ff465365" target="_blank"&gt;Resource Centers&lt;/a&gt;.&lt;/li&gt;    &lt;li&gt;Для повышения квалификации выполняйте &lt;a href="http://technet.microsoft.com/en-US/virtuallabs/bb512933.aspx" target="_blank"&gt;лабораторные работы&lt;/a&gt;. Они отнимают немного времени и позволяют изучить различные аспекты с которыми вы (пока) не столкнулись в работе.&lt;/li&gt;    &lt;li&gt;SharePoint активно использует PowerShell для администрирования. &lt;a href="http://technet.microsoft.com/en-us/powershell" target="_blank"&gt;Изучайте PowerShell&lt;/a&gt;, это поможет вам решать задачи, которые требуют программирования.&lt;/li&gt;    &lt;li&gt;Ответы почти на все вопросы можно найти на &lt;a href="http://technet.microsoft.com/en-us/library/ee428287.aspx" target="_blank"&gt;TechNet&lt;/a&gt;. Зачастую достаточно пошагово выполнить руководство чтобы все заработало как надо.&lt;/li&gt; &lt;/ol&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1302241583051203640-1144731554432001335?l=gandjustas.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=jBN4ldueeKQ:eoKA7FsL-E0:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=jBN4ldueeKQ:eoKA7FsL-E0:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?i=jBN4ldueeKQ:eoKA7FsL-E0:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=jBN4ldueeKQ:eoKA7FsL-E0:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GandjustasBlog/~4/jBN4ldueeKQ" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/GandjustasBlog/~3/jBN4ldueeKQ/sharepoint-administrator-roadmap.html</link><author>noreply@blogger.com (Станислав Выщепан)</author><thr:total>0</thr:total><feedburner:origLink>http://gandjustas.blogspot.com/2011/07/sharepoint-administrator-roadmap.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-1302241583051203640.post-1547185307612587168</guid><pubDate>Wed, 22 Jun 2011 10:34:00 +0000</pubDate><atom:updated>2011-07-06T22:34:35.859+04:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">SharePoint</category><category domain="http://www.blogger.com/atom/ns#">PowerShell</category><title>Развертывание решений SharePoint с помощью PowerShell</title><description>&lt;p&gt;После &lt;a href="http://gandjustas.blogspot.com/2011/06/sharepoint-developer-roadmap.html" target="_blank"&gt;прочтения кучи книг, статей и просмотра видео&lt;/a&gt;, обойдя все &lt;a href="http://gandjustas.blogspot.com/2011/05/blog-post.html" target="_blank"&gt;подводные камни&lt;/a&gt;, и сделав наконец свое &lt;a href="http://ru.wikipedia.org/wiki/Hello,_world!" target="_blank"&gt;решение на SharePoint&lt;/a&gt; у вас появляется вопрос: “а как его запустить у заказчика?” Visual Studio предательски сама активирует фичи решения по F5 и деактивирует при завершении отладки. А как тоже сделать у заказчика? А если у вас доступа на машину заказчика нет?&lt;/p&gt;&lt;p&gt;Для начала стоит подробнее рассмотреть что же требуется для развертывания вашего решения. Обычно развертывание состоит из нескольких шагов:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Добавление WSP-решений уровня фермы &lt;/li&gt;
&lt;li&gt;Добавление sandboxed решений &lt;/li&gt;
&lt;li&gt;Активация фич &lt;/li&gt;
&lt;li&gt;Выполнение дополнительных действий &lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;В идеале дополнительные действия должны совершаться при активации фич, поэтому вычеркнем их из данного сценария.&lt;/p&gt;&lt;p&gt;Вроде все просто. Вооружившись справочником по командам PowerShell&amp;#160; для SharePoint попробуем написать скрипт&lt;/p&gt;&lt;pre class="brush: ps;"&gt;Add-SPSolution -LiteralPath &amp;quot;SomePath\MySolution.wsp&amp;quot;
Install-SPSolution -Identity &amp;quot;mysolution.wsp&amp;quot; -GACDeployment
Enable-SPFeature MyFeature -Url http://localhost/&lt;/pre&gt;&lt;p&gt;Сразу куча проблем:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Работает только из SharePoint Management Shell &lt;/li&gt;
&lt;li&gt;Add-SPSolution&amp;#160; не понимает относительных путей &lt;/li&gt;
&lt;li&gt;На localhost не будет нужного сайта или надо будет разворачивать в другое место &lt;/li&gt;
&lt;li&gt;Sandboxed решения так добавить не получится &lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;Чтобы не заставлять пользователя запускать Management Shell можно написать в начале &lt;/p&gt;&lt;pre class="brush: ps;"&gt;Add-PSSnapin Microsoft.SharePoint.Powershell&lt;/pre&gt;&lt;p&gt;Но после этого появляется ошибка при запуске из Management shell…&lt;/p&gt;&lt;p&gt;Чтобы обойти проблему нужно проверять загружено ли расширение&lt;/p&gt;&lt;pre class="brush: ps;"&gt;if(!(Get-PSSnapin Microsoft.SharePoint.Powershell -ErrorAction:SilentlyContinue))
{
    Add-PSSnapin Microsoft.SharePoint.Powershell
}&lt;/pre&gt;&lt;p&gt;Вместо hardcoded параметра сайта можно запрашивать параметр в скрипте с помощью оператора param.&amp;#160; А вот чтобы получить текущий путь скрипта надо использовать &lt;a href="http://weblogs.asp.net/soever/archive/2007/02/06/powershell-regerencing-files-relative-to-the-currently-executing-script.aspx" target="_blank"&gt;хак&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Второй вариант скрипта:&lt;/p&gt;&lt;pre class="brush: ps;"&gt;param($siteUrl= $(Read-Host &amp;quot;siteUrl&amp;quot;))

$dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
$name = &amp;quot;mysolution.wsp&amp;quot;
$path = &amp;quot;$dir\$name&amp;quot;

if(!(Get-PSSnapin Microsoft.SharePoint.Powershell -ErrorAction:SilentlyContinue))
{
    Add-PSSnapin Microsoft.SharePoint.Powershell
}

Add-SPSolution -LiteralPath $path
Install-SPSolution $name -GACDeployment
Enable-SPFeature MyFeature -Url $siteUrl&lt;/pre&gt;&lt;p&gt;Так уже лучше, но тут есть подводный камень. Install-SPSolution в многосерверной конфигурации не сразу развертывает решение, фичи становится доступны когда развертывание завершится на всех серверах. Поэтому надо писать так:&lt;/p&gt;&lt;pre class="brush: ps;"&gt;$solution = Install-SPSolution $name -GACDeployment
while($solution.Deployed –eq $false)
{
    sleep -s 1
}&lt;/pre&gt;&lt;p&gt;Кроме того очень важно делать установочные (и удаляющие) скрипты &lt;a href="http://ru.wikipedia.org/wiki/%D0%98%D0%B4%D0%B5%D0%BC%D0%BF%D0%BE%D1%82%D0%B5%D0%BD%D1%82%D0%BD%D0%BE%D1%81%D1%82%D1%8C" target="_blank"&gt;идемпотентными&lt;/a&gt;, чтобы запуск скрипта много раз давал такой же эффект как запуск скрипта один раз. Для этого можно применить тот же подход, что и при загрузке командлетов SharePoint.&lt;/p&gt;&lt;pre class="brush: ps;"&gt;param($siteUrl= $(Read-Host &amp;quot;siteUrl&amp;quot;))

$dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
$name = &amp;quot;MySolution.wsp&amp;quot;
$path = &amp;quot;$dir\$name&amp;quot;
$featureId = &amp;quot;6F9619FF-8B86-D011-B42D-00CF4FC964FF&amp;quot; #GUID from Wikipedia

if(!(Get-PSSnapin Microsoft.SharePoint.Powershell -ErrorAction:SilentlyContinue))
{
    Add-PSSnapin Microsoft.SharePoint.Powershell
}

$solution = Get-SPSolution $name -ErrorAction:SilentlyContinue

if(!$solution)
{    
    $solution = Add-SPSolution -LiteralPath $path
}

if($solution.Deployed –eq $false)
{
    Install-SPSolution $solution -GACDeployment
    while($solution.Deployed –eq $false)
    {
        sleep -s 1
    }
}

if(!(Get-SPFeature $featureId -Web $siteUrl -ErrorAction:SilentlyContinue)) #or -Site, -WebApplication, -Farm
{
    Enable-SPFeature $featureId -Url $siteUrl
}&lt;/pre&gt;&lt;p&gt;И подобный код повторить для каждого решения и фичи. &lt;/p&gt;&lt;p&gt;Для sandboxed решений будут использоваться другие командлеты &lt;a href="http://technet.microsoft.com/ru-ru/library/ff607715.aspx" target="_blank"&gt;&lt;em&gt;Add-SPUserSolution&lt;/em&gt;&lt;/a&gt;, &lt;a href="http://technet.microsoft.com/ru-ru/library/ff607653.aspx" target="_blank"&gt;&lt;em&gt;Install-SPUserSolution&lt;/em&gt;&lt;/a&gt; и &lt;a href="http://technet.microsoft.com/ru-ru/library/ff607898.aspx" target="_blank"&gt;&lt;em&gt;Get-SPUserSolution&lt;/em&gt;&lt;/a&gt;. Им необходимо дополнительным параметром передать url коллекции сайтов.&lt;/p&gt;&lt;p&gt;Полученный таким образом скрипт можно запускать как из SharePoint Management Shell, так и из explorer. Главное чтобы необходимые wsp файлы лежали рядом с самим скриптом.&lt;/p&gt;&lt;p&gt;UPD. На dev wiki появилась &lt;a href="https://www.nothingbutsharepoint.com/sites/devwiki/articles/Pages/Deploying-your-solution-with-PowerShell-Part-1.aspx"&gt;статья&lt;/a&gt; на эту тему &lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1302241583051203640-1547185307612587168?l=gandjustas.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=_nIqKyyL1Lk:jK7jGJE96wc:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=_nIqKyyL1Lk:jK7jGJE96wc:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?i=_nIqKyyL1Lk:jK7jGJE96wc:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=_nIqKyyL1Lk:jK7jGJE96wc:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GandjustasBlog/~4/_nIqKyyL1Lk" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/GandjustasBlog/~3/_nIqKyyL1Lk/sharepoint-powershell.html</link><author>noreply@blogger.com (Станислав Выщепан)</author><thr:total>2</thr:total><feedburner:origLink>http://gandjustas.blogspot.com/2011/06/sharepoint-powershell.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-1302241583051203640.post-7662118318010012164</guid><pubDate>Fri, 17 Jun 2011 19:33:00 +0000</pubDate><atom:updated>2011-06-17T23:33:41.327+04:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">SharePoint</category><category domain="http://www.blogger.com/atom/ns#">OData</category><title>SharePoint 2010 и OData</title><description>&lt;p&gt;Наверное все знают что SharePoint 2010 имеет &lt;a href="http://odata.org" target="_blank"&gt;OData&lt;/a&gt;-сервис, к которому можно обратиться по адресу &lt;a href="http://{siteUrl}/_vti_bin/listdata.svc"&gt;http://{siteUrl}/_vti_bin/listdata.svc&lt;/a&gt;. Также SharePoint имеет очень мощную службу бизнес-данных (Business Connectivity Services), с помощью которой можно обращаться к различным источникам данных. К сожалению OData-сервисы не входят в число этих источников.&lt;/p&gt;  &lt;p&gt;Чтобы исправить это досадное упущение, был создан Custom Connector для BCS, который умеет работать с OData-сервисами. А также был создан генератор модели подключения к внешним данными, чтобы упростить подключение OData источников к BCS. &lt;/p&gt;  &lt;p&gt;Найти все это можно по адресу &lt;a href="http://vega-soft.com/spodata/"&gt;http://vega-soft.com/spodata/&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;Фичи, реализованные на данный момент:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Генератор модели&lt;/li&gt;    &lt;li&gt;CRUD-операции с данными&lt;/li&gt;    &lt;li&gt;BCS фильтры: Limit, Paging, Comparison, LastId&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;Фичи, планирующиеся в ближайшее время:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Ассоциации (связи между сущностями)&lt;/li&gt;    &lt;li&gt;Поддержка ad-hoc запросов&lt;/li&gt;    &lt;li&gt;Аутентификация&lt;/li&gt;    &lt;li&gt;Улучшенный генератор модели&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;PS. Для тех кто не в курсе что такое OData – рекомендую детально изучить сайт &lt;a href="http://www.odata.org/"&gt;http://www.odata.org/&lt;/a&gt;.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1302241583051203640-7662118318010012164?l=gandjustas.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=4XKhRwvKNCc:SYjmlCVNvoA:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=4XKhRwvKNCc:SYjmlCVNvoA:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?i=4XKhRwvKNCc:SYjmlCVNvoA:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=4XKhRwvKNCc:SYjmlCVNvoA:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GandjustasBlog/~4/4XKhRwvKNCc" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/GandjustasBlog/~3/4XKhRwvKNCc/sharepoint-2010-odata.html</link><author>noreply@blogger.com (Станислав Выщепан)</author><thr:total>0</thr:total><feedburner:origLink>http://gandjustas.blogspot.com/2011/06/sharepoint-2010-odata.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-1302241583051203640.post-1722320664667844217</guid><pubDate>Fri, 03 Jun 2011 09:19:00 +0000</pubDate><atom:updated>2011-06-03T13:19:36.120+04:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">SharePoint</category><title>SharePoint Developer Roadmap</title><description>&lt;blockquote&gt;   &lt;p&gt;&lt;em&gt;По просьбам &lt;a href="http://www.gotdotnet.ru/forums/5/137829/648446/#post648446"&gt;читателей&lt;/a&gt;&lt;/em&gt;&lt;/p&gt; &lt;/blockquote&gt;  &lt;ol&gt;   &lt;li&gt;SharePoint это в первую очередь ASP.NET приложение, если не владеете им, то начать можно отсюда: &lt;a href="http://www.asp.net/web-forms"&gt;http://www.asp.net/web-forms&lt;/a&gt;. &lt;/li&gt;    &lt;li&gt;Далее Get Started: &lt;a href="http://msdn.microsoft.com/en-US/sharepoint/ee513147.aspx"&gt;http://msdn.microsoft.com/en-US/sharepoint/ee513147.aspx&lt;/a&gt;, обязательно выполните лабораторные работы.&lt;/li&gt;    &lt;li&gt;Затем сразу Advanced training: &lt;a href="http://msdn.microsoft.com/en-US/sharepoint/ff420377.aspx"&gt;http://msdn.microsoft.com/en-US/sharepoint/ff420377.aspx&lt;/a&gt;, обязательно выполните лабораторные работы.&lt;/li&gt;    &lt;li&gt;В дальнейшем ответы на вопросы по использованию отдельных модулей шарика можно найти в Resource Centers: &lt;a href="http://msdn.microsoft.com/en-US/sharepoint/bb964529.aspx"&gt;http://msdn.microsoft.com/en-US/sharepoint/bb964529.aspx&lt;/a&gt;.&lt;/li&gt;    &lt;li&gt;Далее обязательно прочитать книгу &lt;a href="http://www.amazon.com/Real-World-SharePoint-2010-Indispensable/dp/0470597135"&gt;Real World SharePoint 2010: Indispensable Experiences from 22 MVPs&lt;/a&gt;, узнаете много нового &lt;/li&gt;    &lt;li&gt;Если будете заниматься брендингом (созданием уникального внешнего вида) SharePoint, то вам также понадобится книга &lt;a href="http://www.amazon.com/Professional-SharePoint-Branding-Interface-Programmer/dp/0470584645"&gt;Professional SharePoint 2010 Branding and User Interface Design&lt;/a&gt;.&lt;/li&gt;    &lt;li&gt;Далее обязательно изучите &lt;a href="http://msdn.microsoft.com/en-us/library/ff770300.aspx"&gt;SharePoint Guidance 2010&lt;/a&gt;, причем как примеры кода (он там, чуть более чем образцовый), так и подходы к разработке. Кстати там тоже есть лабораторные работы, которые также надо выполнить.&lt;/li&gt;    &lt;li&gt;SharePoint это не только &lt;strike&gt;ценный мех&lt;/strike&gt; серверная платформа, но и мощная клиентская часть в виде Office 2010. Для изучения Office и его интеграции с SharePoint можно посмотреть онлайн курс: &lt;a href="http://msdn.microsoft.com/en-us/gg605831"&gt;http://msdn.microsoft.com/en-us/gg605831&lt;/a&gt;, и снова обязательно выполнить все лабораторки.&lt;/li&gt;    &lt;li&gt;Из того же комплекта обучающих курсов стоит пройти курсы по &lt;a href="http://msdn.microsoft.com/en-us/Office365TrainingCourse"&gt;Office365&lt;/a&gt;, &lt;a href="http://msdn.microsoft.com/en-us/SharePointAndSilverlightTrainingCourse"&gt;SharePoint and Silverlight&lt;/a&gt;.&lt;/li&gt;    &lt;li&gt;Со временем вы поймете что 90% задач в SharePoint\Office можно решить с помощью существующих средств. Чтобы в совершенстве овладеть всеми этими средствами надо внимательно изучить контент на сайте &lt;a href="http://office.microsoft.com/ru-ru/training/"&gt;http://office.microsoft.com/ru-ru/training/&lt;/a&gt;&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;Чтобы ускорить процесс можете прослушать &lt;a href="http://www.specialist.ru/track/t-shpprog"&gt;курсы, которые я читаю&lt;/a&gt;. &lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1302241583051203640-1722320664667844217?l=gandjustas.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=7Vr1o_Plnu8:QXctGuhIpgQ:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=7Vr1o_Plnu8:QXctGuhIpgQ:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?i=7Vr1o_Plnu8:QXctGuhIpgQ:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=7Vr1o_Plnu8:QXctGuhIpgQ:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GandjustasBlog/~4/7Vr1o_Plnu8" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/GandjustasBlog/~3/7Vr1o_Plnu8/sharepoint-developer-roadmap.html</link><author>noreply@blogger.com (Станислав Выщепан)</author><thr:total>6</thr:total><feedburner:origLink>http://gandjustas.blogspot.com/2011/06/sharepoint-developer-roadmap.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-1302241583051203640.post-8057398186487707969</guid><pubDate>Wed, 01 Jun 2011 15:06:00 +0000</pubDate><atom:updated>2011-06-01T19:06:32.364+04:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">SharePoint</category><title>Об обработчиках событий элементов списка, часть 2</title><description>&lt;p&gt;В &lt;a href="http://gandjustas.blogspot.com/2011/05/blog-post.html" target="_blank"&gt;прошлом посте&lt;/a&gt; я писал о подводных камнях при создании обработчиков элементов списка. Теперь хочу рассмотреть реальный сценарий создания такого обработчика.&lt;/p&gt;&lt;p&gt;Наиболее часто программисты SharePoint сталкиваются с необходимостью реализовать field-level security, так как встроенных механизмов в SharePoint нет (зато в Dynamics CRM есть, если что).&amp;#160; Можно написать кастомные формы, которые на уровне интерфейса блокируют возможности поправить значения элементов, но учитывая богатые клиентские возможности SharePoint 2010 такие ограничения легко обойти.&lt;/p&gt;&lt;p&gt;Поэтому необходимо написать код, который в который при событии &lt;em&gt;Updating&lt;/em&gt; сможет определить изменилось поле или нет. Ну и желательно чтобы код был универсальным и работал для любого списка и библиотеки.&lt;/p&gt;&lt;p&gt;Первое приближение:&lt;/p&gt;&lt;pre class="brush: csharp;"&gt;public static bool IsFieldChanged(this SPItemEventProperties properties, 
                                       SPField field)
{
    var after = (string)properties.AfterProperties[field.InternalName];            
    var before = Convert.ToString(properties.ListItem[field.Id]);
    
    return after != before;
}&lt;/pre&gt;&lt;p&gt;Этот код обрабатывает довольно малое число use cases:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;AfterProperties может быть null если свойство не менялось (хотя при отправке формы в AfterProperties&amp;#160; попадают все поля)&lt;/li&gt;
&lt;li&gt;AfterProperties содержит пустую строку если значения нет, SPListItem может при этом возвращать как пустую строку, так и null&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;Второе приближение:&lt;/p&gt;&lt;pre class="brush: csharp;"&gt;public static bool IsFieldChanged(this SPItemEventProperties properties, SPField field)
{
    var after = (string)properties.AfterProperties[field.InternalName];            
    var before = Convert.ToString(properties.ListItem[field.Id]);
    

    //AfterProperties[fieldname] == null - field not changed
    if (after == null)
    {
        return false;
    }

    //AfterProperties[fieldname] == &amp;quot;&amp;quot; - field set to null
    if (after == &amp;quot;&amp;quot; &amp;amp;&amp;amp; string.IsNullOrEmpty(before))
    {
        return false;
    }

    //AfterProperties[fieldname] != &amp;quot;&amp;quot;, old value is null or empty - field changed
    if (string.IsNullOrEmpty(before))
    {
        return true;
    }
    
    return after != before
}&lt;/pre&gt;&lt;p&gt;Уже лучше, НО:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Не сработает на Lookup полях, так как форма передает только Id, без Display Value&lt;/li&gt;
&lt;li&gt;Не сработает на булевых полях, так как слишком разные значения попадают в SPListItem и AfterProperties (&lt;em&gt;интересно почему так?&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Не сработает на датах, так как используется разное форматирование&lt;/li&gt;
&lt;li&gt;Не сработает на множественных значениях, так как они эквивалентны с точностью до порядка&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;Выписывать ифы на каждый тип поля или делать стратегии как-то не хочется, хочется универсальный способ. Ведь он существует, как-то сам SharePoint обрабатывает входящие текстовые значения и судя по всему без магии, так как можно создавать свои поля. MSDN быстро дает ответ – &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spfield.getfieldvalue.aspx" target="_blank"&gt;SPField.GetFieldValue&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Третье приближение:&lt;/p&gt;&lt;pre class="brush: csharp;"&gt;public static bool IsFieldChanged(this SPItemEventProperties properties, SPField field)
{
    var after = (string)properties.AfterProperties[field.InternalName];            
    var before = Convert.ToString(properties.ListItem[field.Id]);
    

    //AfterProperties[fieldname] == null - field not changed
    if (after == null)
    {
        return false;
    }

    //AfterProperties[fieldname] == &amp;quot;&amp;quot; - field set to null
    if (after == &amp;quot;&amp;quot; &amp;amp;&amp;amp; string.IsNullOrEmpty(before))
    {
        return false;
    }

    //AfterProperties[fieldname] != &amp;quot;&amp;quot;, old value is null or empty - field changed
    if (string.IsNullOrEmpty(before))
    {
        return true;
    }

    var afterValue = field.GetFieldValue(after);
    var beforeValue = field.GetFieldValue(before);

    if (afterValue.Equals(beforeValue))
    {
        return false;
    }

    return after != before;
}&lt;/pre&gt;&lt;p&gt;Еще лучше, будут обрабатываться даты и другие поля, для типов значений которых, реализована метод Equals. Но в SharePoint это не так:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spfieldlookupvalue.aspx" target="_blank"&gt;SPFieldLookupValue&lt;/a&gt; и &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spfielduservalue.aspx" target="_blank"&gt;SPFiledUserValue&lt;/a&gt; не реализуют метод Equals(&lt;em&gt;похоже что&amp;#160; авторы и не рассматривали сценарии сравнения значений этих типов&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Для сравнения множественных значений надо написать дополнительный код&lt;/li&gt;
&lt;/ol&gt;&lt;pre class="brush: csharp;"&gt;public static bool IsFieldChanged(this SPItemEventProperties properties, SPField field)
{
    var after = (string)properties.AfterProperties[field.InternalName];            
    var before = Convert.ToString(properties.ListItem[field.Id]);
    

    //AfterProperties[fieldname] == null - field not changed
    if (after == null)
    {
        return false;
    }

    //AfterProperties[fieldname] == &amp;quot;&amp;quot; - field set to null
    if (after == &amp;quot;&amp;quot; &amp;amp;&amp;amp; string.IsNullOrEmpty(before))
    {
        return false;
    }

    //AfterProperties[fieldname] != &amp;quot;&amp;quot;, old value is null or empty - field changed
    if (string.IsNullOrEmpty(before))
    {
        return true;
    }

    var afterValue = field.GetFieldValue(after);
    var beforeValue = field.GetFieldValue(before);

    if (afterValue.Equals(beforeValue))
    {
        return false;
    }

    //Compare SPFieldLookupValue and SPFieldUserValue
    if (afterValue is SPFieldLookupValue)
    {
        return (afterValue as SPFieldLookupValue).LookupId != (beforeValue as SPFieldLookupValue).LookupId;
    }

    //Compare SPFieldLookupValueCollection and SPFieldUserValueCollection
    if (field is SPFieldLookup &amp;amp;&amp;amp; (field as SPFieldLookup).AllowMultipleValues)
    {
        var hsa = new HashSet&amp;lt;int&amp;gt;((afterValue as SPFieldLookupValueCollection).OfType&amp;lt;SPFieldLookupValue&amp;gt;().Select(l =&amp;gt; l.LookupId));
        var hsb = new HashSet&amp;lt;int&amp;gt;((beforeValue as SPFieldLookupValueCollection).OfType&amp;lt;SPFieldLookupValue&amp;gt;().Select(l =&amp;gt; l.LookupId));
        return !hsa.SetEquals(hsb);
    }

    return  after != before;
}&lt;/pre&gt;&lt;p&gt;Почти хорошо, осталось два момента:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Не работает сравнение для &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spfieldmultichoicevalue.aspx"&gt;SPFieldMultiChoiceValue&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Для RichText значений в AfterProperties попадают теги все в верхнем регистре, а в SPListItem – в нижнем&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;Полный код для SharePoint Foundation доступен по ссылке &lt;a title="http://pastebin.com/8vijzvxV" href="http://pastebin.com/8vijzvxV"&gt;http://pastebin.com/8vijzvxV&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Если найдете сценарии, которые не обрабатывает данный код – пишите.&lt;/p&gt;&lt;p&gt;PS. Для SharePoint Server необходимо обрабатывать дополнительные поля, такие как TaxonomyField.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1302241583051203640-8057398186487707969?l=gandjustas.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=yNOXq90kEKI:uBdU8lG-9og:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=yNOXq90kEKI:uBdU8lG-9og:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?i=yNOXq90kEKI:uBdU8lG-9og:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=yNOXq90kEKI:uBdU8lG-9og:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GandjustasBlog/~4/yNOXq90kEKI" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/GandjustasBlog/~3/yNOXq90kEKI/2.html</link><author>noreply@blogger.com (Станислав Выщепан)</author><thr:total>0</thr:total><feedburner:origLink>http://gandjustas.blogspot.com/2011/06/2.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-1302241583051203640.post-4749049714414570390</guid><pubDate>Mon, 30 May 2011 21:15:00 +0000</pubDate><atom:updated>2011-05-31T01:20:16.396+04:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">SharePoint</category><title>Об обработчиках событий элементов списка</title><description>&lt;p&gt;Обработчики событий элементов списка&amp;nbsp; это классы, унаследованные от &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spitemeventreceiver.aspx" target="_blank"&gt;SPItemEventReceiver&lt;/a&gt;. В классе содержится много методов, почти все они делятся на две группы: Pre-события – методы, оканчивающиеся на -ing, и Post-события – методы, оканчивающиеся на -ed. Все события принимают один аргумент экземпляр класса &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spitemeventproperties.aspx" target="_blank"&gt;SPItemEventProperties&lt;/a&gt;. &lt;/p&gt; &lt;p&gt;Это теоретический минимум, который необходимо знать для начала.&lt;/p&gt; &lt;h4&gt;Коварные AfterProperties и BeforeProperties&lt;/h4&gt; &lt;p&gt;Класс &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spitemeventproperties.aspx"&gt;SPItemEventProperties&lt;/a&gt; содержит пару свойств: &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spitemeventproperties.afterproperties.aspx" target="_blank"&gt;AfterProperties&lt;/a&gt; и &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spitemeventproperties.beforeproperties.aspx" target="_blank"&gt;BeforeProperties&lt;/a&gt;. На первый взгляд очень хорошие свойства, которые позволяют с небольшими усилиями реализовать множество сценариев. Но на деле все не так…&lt;/p&gt; &lt;ol&gt; &lt;li&gt;Для начала необходимо запомнить, а потом распечатать и повесить на стену, на холодильник, да доску и на все остальные поверхности следующие таблицы: &lt;br&gt; &lt;table border="1" cellspacing="0" cellpadding="2"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top"&gt;&lt;em&gt;Список&lt;/em&gt;&lt;/td&gt; &lt;td valign="top"&gt;&lt;strong&gt;BeforeProperties&lt;/strong&gt;&lt;/td&gt; &lt;td valign="top"&gt;&lt;strong&gt;AfterProperties&lt;/strong&gt;&lt;/td&gt; &lt;td valign="top"&gt;&lt;strong&gt;properties.ListItem&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top"&gt;ItemAdding&lt;/td&gt; &lt;td valign="top" align="center"&gt;Пусто&lt;/td&gt; &lt;td valign="top" align="center"&gt;Новые значения&lt;/td&gt; &lt;td valign="top" align="center"&gt;null&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top"&gt;ItemAdded&lt;/td&gt; &lt;td valign="top" align="center"&gt;Пусто&lt;/td&gt; &lt;td valign="top" align="center"&gt;Новые значения&lt;/td&gt; &lt;td valign="top" align="center"&gt;Новые значения&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top"&gt;ItemUpdating&lt;/td&gt; &lt;td valign="top" align="center"&gt;Пусто&lt;/td&gt; &lt;td valign="top" align="center"&gt;Новые значения&lt;/td&gt; &lt;td valign="top" align="center"&gt;Старые значения&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top"&gt;ItemUpdated&lt;/td&gt; &lt;td valign="top" align="center"&gt;Пусто&lt;/td&gt; &lt;td valign="top" align="center"&gt;Новые значения&lt;/td&gt; &lt;td valign="top" align="center"&gt;Новые значения&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top"&gt;ItemDeleting&lt;/td&gt; &lt;td valign="top" align="center"&gt;Пусто&lt;/td&gt; &lt;td valign="top" align="center"&gt;Пусто&lt;/td&gt; &lt;td valign="top" align="center"&gt;Старые значения&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top"&gt;ItemDeleted&lt;/td&gt; &lt;td valign="top" align="center"&gt;Пусто&lt;/td&gt; &lt;td valign="top" align="center"&gt;Пусто&lt;/td&gt; &lt;td valign="top" align="center"&gt;null&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br&gt; &lt;table border="1" cellspacing="0" cellpadding="2"&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td valign="top"&gt;&lt;em&gt;Библиотека&lt;/em&gt;&lt;/td&gt; &lt;td valign="top"&gt;&lt;strong&gt;BeforeProperties&lt;/strong&gt;&lt;/td&gt; &lt;td valign="top"&gt;&lt;strong&gt;AfterProperties&lt;/strong&gt;&lt;/td&gt; &lt;td valign="top"&gt;&lt;strong&gt;properties.ListItem&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top"&gt;ItemAdding&lt;/td&gt; &lt;td valign="top" align="center"&gt;Пусто&lt;/td&gt; &lt;td valign="top" align="center"&gt;Пусто&lt;/td&gt; &lt;td valign="top" align="center"&gt;null&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top"&gt;ItemAdded&lt;/td&gt; &lt;td valign="top" align="center"&gt;Пусто&lt;/td&gt; &lt;td valign="top" align="center"&gt;Пусто&lt;/td&gt; &lt;td valign="top" align="center"&gt;Новые значения&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top"&gt;ItemUpdating&lt;/td&gt; &lt;td valign="top" align="center"&gt;Старые значения&lt;/td&gt; &lt;td valign="top" align="center"&gt;Новые значения&lt;/td&gt; &lt;td valign="top" align="center"&gt;Старые значения&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top"&gt;ItemUpdated&lt;/td&gt; &lt;td valign="top" align="center"&gt;Старые значения&lt;/td&gt; &lt;td valign="top" align="center"&gt;Новые значения&lt;/td&gt; &lt;td valign="top" align="center"&gt;Новые значения&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top"&gt;ItemDeleting&lt;/td&gt; &lt;td valign="top" align="center"&gt;Пусто&lt;/td&gt; &lt;td valign="top" align="center"&gt;Пусто&lt;/td&gt; &lt;td valign="top" align="center"&gt;Старые значения&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt; &lt;td valign="top"&gt;ItemDeleted&lt;/td&gt; &lt;td valign="top" align="center"&gt;Пусто&lt;/td&gt; &lt;td valign="top" align="center"&gt;Пусто&lt;/td&gt; &lt;td valign="top" align="center"&gt;null&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br&gt;Поведение списков и библиотек документов разное и отличается от предполагаемого. &lt;br&gt;Как видите из таблиц выше, BeforeProperties почти бесполезное свойство. &lt;br&gt; &lt;li&gt;&lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spitemeventproperties.afterproperties.aspx"&gt;AfterProperties&lt;/a&gt; и &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spitemeventproperties.beforeproperties.aspx"&gt;BeforeProperties&lt;/a&gt; содержат свойство &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spitemeventdatacollection.changedproperties.aspx" target="_blank"&gt;ChangedProperties&lt;/a&gt;. Это свойство показывает измененные поля в самой коллекции, а не в элементе.  &lt;li&gt;Свойство-индексатор &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spitemeventproperties.afterproperties.aspx"&gt;AfterProperties&lt;/a&gt; и &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spitemeventproperties.beforeproperties.aspx"&gt;BeforeProperties&lt;/a&gt; принимает Internal Name поля, а не Display Name как в properties.ListItem.  &lt;li&gt;&lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spitemeventproperties.afterproperties.aspx"&gt;AfterProperties&lt;/a&gt; и &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spitemeventproperties.beforeproperties.aspx"&gt;BeforeProperties&lt;/a&gt; поддерживают нетипизированный &lt;a href="http://msdn.microsoft.com/EN-US/library/system.collections.ienumerable(v=VS.90)" target="_blank"&gt;IEnumerable&lt;/a&gt;, но нигде в документации не указано какого типа возвращается перечисление. Опыты показывают что возвращается &lt;a href="http://msdn.microsoft.com/en-us/library/system.collections.dictionaryentry(v=VS.90).aspx" target="_blank"&gt;DictionaryEntry&lt;/a&gt;.  &lt;li&gt;&lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spitemeventproperties.afterproperties.aspx"&gt;AfterProperties&lt;/a&gt; и &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spitemeventproperties.beforeproperties.aspx"&gt;BeforeProperties&lt;/a&gt; возвращают все значения в виде строк. Кроме того для boolean типа поля может быт возвращено "-1" в качестве значения, а строки возвращаются в универсальном формате и при парсинге автоматически переводятся в текущую локаль (+3 часа обычно получается).  &lt;li&gt;При изменении элемента списка в коде в &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spitemeventproperties.afterproperties.aspx"&gt;AfterProperties&lt;/a&gt; попадают только измененные значения, а при сохранении формы – все значения формы.  &lt;li&gt;При сохранении формы с RichText полем в &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spitemeventproperties.afterproperties.aspx"&gt;AfterProperties&lt;/a&gt; попадает HTML со всеми заглавными буквами в названиях тегов. &lt;/li&gt;&lt;/ol&gt; &lt;h4&gt;Отмена действия&lt;/h4&gt; &lt;blockquote&gt; &lt;p&gt;Pre-события позволяют отменить действие. Для этого необходимо в &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.speventpropertiesbase.cancel.aspx" target="_blank"&gt;properties.Cancel&lt;/a&gt; присвоить true, присвоить необходимые значения свойствам &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.speventpropertiesbase.status.aspx" target="_blank"&gt;properties.Status&lt;/a&gt; и &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.speventpropertiesbase.errormessage.aspx" target="_blank"&gt;properties.ErrorMessage&lt;/a&gt;. Но и тут есть особенности:&lt;/p&gt;&lt;/blockquote&gt; &lt;ol&gt; &lt;li&gt;Если вы собираетесь отменить действие, то не вызывайте базовую реализацию метода-обработчика. Иначе отмена не произойдет.  &lt;li&gt;Если вы поставите статус CancelWithError, то будет выкинуто стандартное исключение, которое в режиме отладки отображается желтым экраном смерти.  &lt;li&gt;Если хотите показать свое сообщение об ошибке, то сделайте CancelWithRedirect, но учтите что в таком случае управление не вернется к вызывающему коду.  &lt;li&gt;Если необходимо выполнение разных способов отмены, то анализируйте &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spcontext.aspx" target="_blank"&gt;SPContext&lt;/a&gt; и его свойства &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spcontext.formcontext.aspx" target="_blank"&gt;FormContext&lt;/a&gt; и &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spcontext.viewcontext.aspx" target="_blank"&gt;ViewContext&lt;/a&gt;.  &lt;li&gt;Отмена работает всегда, для любой учетной записи, в том числе системной. Желательно позволять выполнять действие (не отменять его) администратору коллекции сайтов и учетной записи "SHAREPOINT\system". &lt;/li&gt;&lt;/ol&gt; &lt;h4&gt;Конкурентное выполнение обработчиков событий&lt;/h4&gt; &lt;p&gt;В &lt;a href="http://blogs.msdn.com/b/sharepointdev/archive/2011/05/30/sharepoint-foundation-2010-events-pipeline.aspx" target="_blank"&gt;этом посте&lt;/a&gt; описан пайплайн обработки событий, картинка ниже кратко его иллюстрирует. &lt;img src="http://blogs.msdn.com/cfs-file.ashx/__key/CommunityServer-Blogs-Components-WeblogFiles/00-00-01-40-86-metablogapi/8372.SharePoint_5F00_Foundation_5F00_2010_5F00_Events_5F00_Pipeline_5F00_thumb_5F00_2A3C65B2.png"&gt;&lt;/p&gt; &lt;p&gt;Как видно обработчики Post-событий могут запускаться параллельно. Если будут параллельно запускаться несколько обработчиков, изменяющих сам элемент, то может появиться состояние гонки. &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spcontext.item.aspx" target="_blank"&gt;Класс SPListItem&lt;/a&gt; снабжен механизмом так называемой “оптимистичной конкуренции”. В случая если другой потом успел поменять значение в базе между моментом считывания и записью данных, то выпадет исключение. Но исключения в пост-обработчиках не очень эффективны, так как нет возможности как-либо сигнализировать об ошибке, если только это не синхронный обработчик.&lt;/p&gt; &lt;ol&gt; &lt;li&gt;По возможности не изменяйте элемент списка из асинхронного пост-обработчика.  &lt;li&gt;Перехватывайте &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.splistdatavalidationexception.aspx" target="_blank"&gt;SPListDataValidationException&lt;/a&gt;. Если поймали такое исключение, то выполните &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spitemeventproperties.invalidatelistitem.aspx" target="_blank"&gt;properties.InvalidateListItem&lt;/a&gt;, а потом снова код обновления элемента.  &lt;li&gt;Используйте следующий блок кода в пост-обработчиках, чтобы не вызвать их циклического запуска &lt;br&gt;&lt;pre class="brush: csharp;"&gt;try
{
    this.EventFiringEnabled = false;
    //...здесь вызов Update...
}
finally
{
    this.EventFiringEnabled = true;
}&lt;/pre&gt;
&lt;li&gt;Также можно использовать SystemUpdate, чтобы не вызывать обработчики событий и не менять время последнего изменения элемента.&lt;pre&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ol&gt;&lt;br&gt;К сожалению это далеко не все грабли, которые встречаются при обработке событий. С прочими граблями можно ознакомиться тут:&lt;a href="http://msdn.microsoft.com/en-us/library/aa979520.aspx"&gt;http://msdn.microsoft.com/en-us/library/aa979520.aspx&lt;/a&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1302241583051203640-4749049714414570390?l=gandjustas.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=GYduHUGQPro:acw9wgkFC0Y:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=GYduHUGQPro:acw9wgkFC0Y:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?i=GYduHUGQPro:acw9wgkFC0Y:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=GYduHUGQPro:acw9wgkFC0Y:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GandjustasBlog/~4/GYduHUGQPro" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/GandjustasBlog/~3/GYduHUGQPro/blog-post.html</link><author>noreply@blogger.com (Станислав Выщепан)</author><thr:total>4</thr:total><feedburner:origLink>http://gandjustas.blogspot.com/2011/05/blog-post.html</feedburner:origLink></item></channel></rss>

