<?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>Mon, 21 May 2012 13:32:53 +0000</lastBuildDate><category>SOLID</category><category>AOP</category><category>Visual Studio</category><category>XSLT</category><category>MVC</category><category>Architecture</category><category>архитектура</category><category>MVVM</category><category>javascript</category><category>IoC</category><category>security</category><category>RBS</category><category>SharePoint</category><category>MVP</category><category>TPL</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>.NET</category><category>Silverlight</category><title>gandjustas' blog</title><description /><link>http://gandjustas.blogspot.com/</link><managingEditor>noreply@blogger.com (Станислав Выщепан)</managingEditor><generator>Blogger</generator><openSearch:totalResults>76</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-8837594741333816744</guid><pubDate>Tue, 24 Apr 2012 06:00:00 +0000</pubDate><atom:updated>2012-04-24T10:00:06.627+04:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">SharePoint</category><title>Очередная встреча RUSUG 26.04.2012</title><description>&lt;p&gt;Очередная встреча RUSUG состоится &lt;strong&gt;26 апреля&lt;/strong&gt;, в четверг. Пройдёт она, традиционно, в &lt;a href="http://rusug.net/Lists/Locations/DispForm.aspx?ID=3"&gt;Технологическом центре компании Microsoft&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;Первым докладчиком стану я, буду выступать с докладом «&lt;strong&gt;SharePoint Governance для разработчиков&lt;/strong&gt;». В докладе пойдет речь о том как планировать, создавать и управлять решениями на SharePoint. Какие инструменты для этого есть в SharePoint, как их эффективно использовать и расширять.&lt;/p&gt;  &lt;p&gt;Второй доклад будет посвящён &lt;strong&gt;лучшим практикам в настройке SQL Server для работы с SharePoint&lt;/strong&gt;. Об этом расскажет &lt;strong&gt;Максим Хлупнов&lt;/strong&gt;, Архитектор Технологического центра Microsoft.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Планируется проведение трансляции мероприятия, на которую необходима регистрация.&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;Регистрация на мероприятие и трансляцию: &lt;a href="http://rusug.net/Lists/Apr2012Reg/NewForm.aspx"&gt;http://rusug.net/Lists/Apr2012Reg/NewForm.aspx&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-8837594741333816744?l=gandjustas.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=zLsWL2RKjl0:7xgvjugcgyE: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=zLsWL2RKjl0:7xgvjugcgyE:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?i=zLsWL2RKjl0:7xgvjugcgyE:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=zLsWL2RKjl0:7xgvjugcgyE: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/zLsWL2RKjl0" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/GandjustasBlog/~3/zLsWL2RKjl0/rusug-26042012.html</link><author>noreply@blogger.com (Станислав Выщепан)</author><thr:total>0</thr:total><feedburner:origLink>http://gandjustas.blogspot.com/2012/04/rusug-26042012.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-1302241583051203640.post-8339427691223953701</guid><pubDate>Wed, 18 Apr 2012 06:00:00 +0000</pubDate><atom:updated>2012-04-18T10:00:09.571+04:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">SharePoint</category><title>Я участвую в SPCUA 2012.</title><description>&lt;p&gt;&lt;strong&gt;25 апреля&lt;/strong&gt; пройдет событие года по теме SharePoint в СНГ – &lt;strong&gt;конференция SharePoint Conference Ukraine 2012&lt;/strong&gt; (&lt;a href="http://spcua.com/"&gt;http://spcua.com/&lt;/a&gt;). Генеральный партнер конференции Microsoft Украина, организатор – Lizard Soft (&lt;a href="http://lizard-soft.com"&gt;http://lizard-soft.com&lt;/a&gt;).&lt;/p&gt;  &lt;p&gt;Конференция собрала докладчиков и участников не только со всего СНГ — но и из США и Европы. Ее именитыми докладчиками стали Майкл Ноэль (Michael Noel), Пол Свайдер (Paul J. Swider), Павел Вробель (Pawel Wrobel). Microsoft делегировала на конференцию докладчиков из ЕС, российского и украинского офиса Microsoft. Партнеры конференции из Австралии, Великобритании, Польши, России и Украины.&lt;/p&gt;  &lt;p&gt;А также на конференции я буду читать доклад “Искусство управления SharePoint: как получить максимальную выгоду для бизнеса” и отвечать на вопросы.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1302241583051203640-8339427691223953701?l=gandjustas.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=s2w6F_yC9Kg:XoabYqER1oY: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=s2w6F_yC9Kg:XoabYqER1oY:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?i=s2w6F_yC9Kg:XoabYqER1oY:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=s2w6F_yC9Kg:XoabYqER1oY: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/s2w6F_yC9Kg" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/GandjustasBlog/~3/s2w6F_yC9Kg/spcua-2012.html</link><author>noreply@blogger.com (Станислав Выщепан)</author><thr:total>0</thr:total><feedburner:origLink>http://gandjustas.blogspot.com/2012/04/spcua-2012.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-1302241583051203640.post-4966785369327181686</guid><pubDate>Mon, 16 Apr 2012 07:00:00 +0000</pubDate><atom:updated>2012-04-16T11:00:11.056+04:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">.NET</category><category domain="http://www.blogger.com/atom/ns#">TPL</category><category domain="http://www.blogger.com/atom/ns#">Architecture</category><category domain="http://www.blogger.com/atom/ns#">Reactive Extensions</category><title>Блеск и нищета Task Parallel Library</title><description>&lt;p&gt;Task Parallel Library (пространство имен &lt;a href="http://msdn.microsoft.com/ru-ru/library/system.threading.tasks.aspx" target="_blank"&gt;System.Threading.Tasks&lt;/a&gt;) появилась еще в .NET 4, но большой популярностью не пользовалась. На подходе компилятор C# 5, который поддерживает конструкцию async\await. В скомпилированом коде async метод превращается в метод возвращающий Task\Task&amp;lt;T&amp;gt;.&amp;#160; Поэтому в скором времени использование TPL станет повсеместным.&lt;/p&gt;  &lt;p&gt;Task Parallel Library – имеет очень мощные механизмы: вложенные (nested) задачи, локальные очереди для потоков, чтобы уменьшить латентность, work-stealing алгоритмы для работы, кучи опций запуска задач и продолжений, различные планировщики. Все создано в лучших традициях оптимизации асинхронного кода.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Но, &lt;/strong&gt;TPL создавался до мозга костей практиками и цели сделать библиотеку удобной для потребителей даже не ставилось судя по всему.&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Как это не смешно, то в стандартной библиотеке нету метода, позволяющего обернуть значение T в Task&amp;lt;T&amp;gt;. (Только в .NET 4.5 появился &lt;a href="http://msdn.microsoft.com/en-us/library/hh194922(v=vs.110).aspx" target="_blank"&gt;Task.FromResult&lt;/a&gt;) &lt;/li&gt;    &lt;li&gt;Сам объект Task представляет из себя “задачу”, которая может быть как еще не запущена, так и уже выполняться.&amp;#160; Поэтому работа с разными Task_ами будет осуществляться по-разному. &lt;/li&gt;    &lt;li&gt;Task не является immutable объектом, метод ContinueWith изменяет саму задачу. &lt;/li&gt;    &lt;li&gt;Функция ContinueWith срабатывает при любом завершении задачи, в том числе отмене или ошибке. Необходимо каждый раз указывать флаги.&lt;/li&gt;    &lt;li&gt;Исключения внутри задач собираются в один объект, что затрудняет структурированную их обработку.&lt;/li&gt;    &lt;li&gt;Метод ContinueWith принимает Func&amp;lt;Task, T&amp;gt;, а не Func&amp;lt;Task, Task&amp;gt;. Это значит что единственный способ связать две задачи – создать вложенную (nested), что может привести к переполнению стека при завершении цепочки вложенных задач. &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;Последний пункт особенно актуален.&amp;#160; Даже был сделан extension-метод &lt;a href="http://msdn.microsoft.com/en-us/library/dd780917.aspx" target="_blank"&gt;Unwrap&lt;/a&gt;, который позволяет писать код в виде:&lt;/p&gt;  &lt;pre class="brush: csharp;"&gt;var task = SomeFuncReturningTask(...)
                .ContinueWith(t =&amp;gt; SomeOtherFuncReturningTask(t.Result))
                .Unwrap().ContinueWith(...)
                .Unwrap().ContinueWith(...);&lt;/pre&gt;

&lt;p&gt;Тут надо обратить внимание на пункт 4, вызовы методов станут еще более многословными.&lt;/p&gt;

&lt;p&gt;Async\await в C# 5 частично решает проблему, позволяя писать в виде:&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;var r1 = await SomeFuncReturningTask();
var r2 = await SomeOtherFuncReturningTask();&lt;/pre&gt;

&lt;p&gt;Но есть еще другие варианты использования где даже async\await не помогает, например рекурсивные функции.&lt;/p&gt;

&lt;p&gt;Также для .NET существует библиотека Rx, которая создавалась теоретиками (которые Linq придумали). Причем основная часть Rx была получена составлением монады двойственной к IEnumerable.&lt;/p&gt;

&lt;p&gt;Для сравнения “теоретического” и “практического” подхода попробую написать функцию вычисления высоты дерева.&lt;/p&gt;

&lt;p&gt;Само дерево:&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;class Tree&amp;lt;T&amp;gt;
{
    public Tree()
    {
        this.Children = new List&amp;lt;Tree&amp;lt;T&amp;gt;&amp;gt;();
    }

    public T Data { get; set; }
    public List&amp;lt;Tree&amp;lt;T&amp;gt;&amp;gt; Children { get; private set; }
}
&lt;/pre&gt;

&lt;p&gt;Код тестов очень простой:&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;var t = new Tree&amp;lt;int&amp;gt; { Data = 0 };
for (int i = 1; i &amp;lt; 10 * 1000; i++)
{
    t = new Tree&amp;lt;int&amp;gt;
    {
        Data = i,
        Children = { t }
    };
}

Console.WriteLine(HeightObservable(t, x =&amp;gt; x.Children).Wait());
Console.WriteLine(HeightTask(t, x =&amp;gt; x.Children).Result);&lt;/pre&gt;

&lt;p&gt;Высота дерева специально выбрана большой – 10000, чтобы проверить насколько эффективно библиотека работает со стеком.&lt;/p&gt;

&lt;p&gt;Функция вычисления высоты на Rx:&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;static IObservable&amp;lt;int&amp;gt; HeightObservable&amp;lt;T&amp;gt;(T element, Func&amp;lt;T, IEnumerable&amp;lt;T&amp;gt;&amp;gt; childSelector)
{
    return childSelector(element)
            .ToObservable()
            .SelectMany(e =&amp;gt; HeightObservable(e, childSelector))
            .Aggregate(0, Math.Max)
            .Select(x =&amp;gt; x + 1)
            .ObserveOn(Scheduler.ThreadPool);
}&lt;/pre&gt;

&lt;p&gt;Последний ObserveOn нужен чтобы не возникало stack overflow. Rx стремится как можно меньше использовать concurrency внутри себя и по-умолчанию большинство вызовов “продолжений”(continuation) выполняется синхронно, что приводит к переполнению стека в рекурсивном вызове. &lt;br /&gt;Если использовать Rx v2, то можно без ObserveOn обойтись.&lt;/p&gt;

&lt;p&gt;Теперь на Task\C# 5. Попытка №1&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;static async Task&amp;lt;int&amp;gt; HeightTask&amp;lt;T&amp;gt;(T element, Func&amp;lt;T, IEnumerable&amp;lt;T&amp;gt;&amp;gt; childSelector)
{
    var max = 0;
    foreach (var child in childSelector(element))
    {
        var v = await HeightTask(child, childSelector);
        if (v &amp;gt; max)
        {
            max = v;
        }
    }
    return await Task.FromResult(max + 1);
}&lt;/pre&gt;

&lt;p&gt;Падает со stack overflow . Await вычисляет аргумент и получает awaiter у результата. Таким образом идет полный обход всего дерева в первом же await. Кроме этого вычисление высот поддеревьев идет по-очереди. Код не распараллелен.&lt;/p&gt;

&lt;p&gt;Чтобы распараллелить код нужна функция IEnumerable&amp;lt;Task&amp;lt;T&amp;gt;&amp;gt; –&amp;gt; Task&amp;lt;IEnumerable&amp;lt;T&amp;gt;&amp;gt;, которая параллельно выполняет задачи и собирает результат в одну последовательность. Такая функция называется ForkJoin.&lt;/p&gt;

&lt;p&gt;Первое что пришло в голову написать:&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;static Task&amp;lt;IEnumerable&amp;lt;T&amp;gt;&amp;gt; ForkJoin&amp;lt;T&amp;gt;(IEnumerable&amp;lt;Task&amp;lt;T&amp;gt;&amp;gt; tasks)
{
    var result = new List&amp;lt;T&amp;gt;();
    //...
    foreach (var task in tasks)
    {
        task.ContinueWith(t =&amp;gt; result.Add(t.Result) /*...*/);
    }
    //...
}&lt;/pre&gt;

&lt;p&gt;Такой код не работает. Падает со stackoverflow на foreach, потому что при получении первого элемента пытается синхронно обойти все дерево.&lt;/p&gt;

&lt;p&gt;Нужно написать функцию, которая асинхронно обходит IEnumerable&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;static Task&amp;lt;int&amp;gt; EnumerateWithTask&amp;lt;T&amp;gt;(IEnumerable&amp;lt;Task&amp;lt;T&amp;gt;&amp;gt; tasks, Action&amp;lt;Task&amp;lt;T&amp;gt;&amp;gt; continuation)
{
    var tcs = new TaskCompletionSource&amp;lt;int&amp;gt;();
    var enumerator = tasks.GetEnumerator();
    Action recursive = null;
    var count = 0;

    recursive = () =&amp;gt;
        {
            Task.Factory
                .StartNew&amp;lt;bool&amp;gt;(enumerator.MoveNext)
                .ContinueWith(t =&amp;gt;
                    {
                        if (t.IsFaulted)
                        {
                            tcs.TrySetException(t.Exception.InnerExceptions);
                        }
                        else if (t.IsCompleted)
                        {
                            if (!t.Result)
                            {
                                tcs.TrySetResult(count);
                            }
                            else
                            {
                                count++;
                                enumerator.Current.ContinueWith(continuation, TaskContinuationOptions.ExecuteSynchronously);
                                recursive();
                            }
                        }
                    }, TaskContinuationOptions.ExecuteSynchronously);
        };

    recursive();
    return tcs.Task;
}&lt;/pre&gt;

&lt;p&gt;В этой функции вместо итерации используется рекурсия. Так как рекурсия вызывается внутри продолжения Task, то переполнения стека не получается. Функция возвращает Task, которая считает число элементов после окончания обхода последовательности.&lt;/p&gt;

&lt;p&gt;Теперь можно написать и ForkJoin:&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;static Task&amp;lt;IEnumerable&amp;lt;T&amp;gt;&amp;gt; ForkJoin&amp;lt;T&amp;gt;(IEnumerable&amp;lt;Task&amp;lt;T&amp;gt;&amp;gt; tasks)
{
    var result = new List&amp;lt;T&amp;gt;();
    var tcs = new TaskCompletionSource&amp;lt;IEnumerable&amp;lt;T&amp;gt;&amp;gt;();

    Task&amp;lt;int&amp;gt; countTask = null;
    var completedCount = 0;
    var isCompleted = false;

    countTask = EnumerateWithTask(tasks, t =&amp;gt;
            {
                if (!isCompleted)
                {
                    if (t.IsCanceled)
                    {
                        tcs.TrySetCanceled();
                        isCompleted = true;
                    }
                    else if (t.IsFaulted)
                    {
                        tcs.TrySetException(t.Exception.InnerExceptions);
                        isCompleted = true;
                    }
                    else if (t.IsCompleted)
                    {
                        result.Add(t.Result);
                        completedCount++;
                    }

                    if ((countTask.IsCompleted) &amp;amp;&amp;amp; completedCount == countTask.Result)
                    {
                        tcs.TrySetResult(result.AsReadOnly());
                        isCompleted = true;
                    }
                }
            });

    countTask.ContinueWith(t =&amp;gt;
        {
            if (!isCompleted)
            {
                if (completedCount == t.Result)
                {
                    tcs.TrySetResult(result.AsReadOnly());
                    isCompleted = true;
                }
            }
        });
    return tcs.Task;
}&lt;/pre&gt;

&lt;p&gt;Когда писал этот код получил разрыв мозга. Боюсь даже представить что будет если придется такой код читать. Скорее всего в этом коде есть ошибки и он не обрабатывает отмену. Так что для production придется помучаться еще сильнее.&lt;/p&gt;

&lt;p&gt;Ну и наконец сам код расчета высоты дерева на Task_ах, попытка №2:&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;static Task&amp;lt;int&amp;gt; HeightTask&amp;lt;T&amp;gt;(T element, Func&amp;lt;T, IEnumerable&amp;lt;T&amp;gt;&amp;gt; childSelector)
{
    return ForkJoin(childSelector(element).Select(e =&amp;gt; HeightTask(e, childSelector)))
            .ContinueWith(t =&amp;gt; t.Result.Aggregate(0, Math.Max, x =&amp;gt; x + 1));
}&lt;/pre&gt;

&lt;p&gt;Замеры показывают что Rx проигрывает по времени выполнения Task_ам примерно в 2 раза (на Rx v2). Это при том что реально асинхронных операций нет. Реальная асинхронность сделает разницу незаметной. Сложность кода Rx на порядок (в 10 раз) меньше, чем Tasks. &lt;/p&gt;

&lt;p&gt;Новомодные фишки типа async\await не помогли в этой задаче вообще никак. Причиной этому служит то, что&amp;#160; Rx спроектирован на основе монад (как и async в F#). Монады позволяют комбинировать вычисления с некоторым контекстом (в данном случае с “продолжениями”) с помощью небольшого набора функций. На базе этих функций можно построить много других.&lt;/p&gt;

&lt;p&gt;Async\await – не более чем переписывание кода в компиляторе, никаких монадических конструкций само по себе оно не создает, а TPL не предоставляет средства для композиции. Вот и получаются проблемы на любом коде, сложнее того что в примерах.&lt;/p&gt;

&lt;p&gt;Как ни странно но сейчас не существует библиотеки, которая содержит код, решающий проблемы. Максимум некоторые расширения можно найти в &lt;a href="http://code.msdn.microsoft.com/Samples-for-Parallel-b4b76364/sourcecode?fileId=44488&amp;amp;pathId=1890196978" target="_blank"&gt;примерах на MSDN&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Заключение&lt;/p&gt;

&lt;p&gt;Крайне не рекомендую использовать Tasks для высокоуровневого кода. Rx подходит на эту роль гораздо лучше.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1302241583051203640-4966785369327181686?l=gandjustas.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=SZFMZ013Thw:dNh7Ltm5eVI: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=SZFMZ013Thw:dNh7Ltm5eVI:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?i=SZFMZ013Thw:dNh7Ltm5eVI:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=SZFMZ013Thw:dNh7Ltm5eVI: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/SZFMZ013Thw" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/GandjustasBlog/~3/SZFMZ013Thw/task-parallel-library.html</link><author>noreply@blogger.com (Станислав Выщепан)</author><thr:total>5</thr:total><feedburner:origLink>http://gandjustas.blogspot.com/2012/04/task-parallel-library.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-1302241583051203640.post-7350983164731087977</guid><pubDate>Mon, 16 Apr 2012 06:00:00 +0000</pubDate><atom:updated>2012-04-16T10:00:10.263+04:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">SharePoint</category><title>Жириновский о SharePoint</title><description>&lt;p&gt;&lt;a href="http://lh4.ggpht.com/-QjMdyiW4j4I/T4mlYfotpEI/AAAAAAAAAMA/XUM_kanjxNs/s1600-h/jirinovskiy-on-sharepoint%25255B3%25255D.jpg"&gt;&lt;img title="jirinovskiy-on-sharepoint" border="0" alt="jirinovskiy-on-sharepoint" src="http://lh6.ggpht.com/-4Mij-cqK8xY/T4mlav3CoUI/AAAAAAAAAMI/tEzKamSSx3w/jirinovskiy-on-sharepoint_thumb%25255B1%25255D.jpg?imgmax=800" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;via &lt;a href="https://twitter.com/avishnyakov" target="_blank"&gt;@avishnyakov&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-7350983164731087977?l=gandjustas.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=S5XPpVbVoRI:0xAHdfUDRLo: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=S5XPpVbVoRI:0xAHdfUDRLo:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?i=S5XPpVbVoRI:0xAHdfUDRLo:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=S5XPpVbVoRI:0xAHdfUDRLo: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/S5XPpVbVoRI" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/GandjustasBlog/~3/S5XPpVbVoRI/sharepoint.html</link><author>noreply@blogger.com (Станислав Выщепан)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://lh6.ggpht.com/-4Mij-cqK8xY/T4mlav3CoUI/AAAAAAAAAMI/tEzKamSSx3w/s72-c/jirinovskiy-on-sharepoint_thumb%25255B1%25255D.jpg?imgmax=800" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://gandjustas.blogspot.com/2012/04/sharepoint.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-1302241583051203640.post-242896804918697880</guid><pubDate>Wed, 14 Mar 2012 06:00:00 +0000</pubDate><atom:updated>2012-03-14T10:00:03.555+04:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">js</category><category domain="http://www.blogger.com/atom/ns#">javascript</category><category domain="http://www.blogger.com/atom/ns#">SharePoint</category><category domain="http://www.blogger.com/atom/ns#">Visual Studio</category><title>SharePoint + Javascript + Visual Studio = Love</title><description>&lt;p&gt;Для тех кто еще не знаком с клиентской объектной моделью SharePoint, настоятельно рекомендую &lt;a href="http://msdn.microsoft.com/en-us/library/ee537247.aspx" target="_blank"&gt;ознакомиться с ней&lt;/a&gt;. Client OM доступна как в C# (.NET и Silverlight), так и в Javascript. &lt;/p&gt;  &lt;p&gt;.NET разработчики обычно не любят javascript, считают его убогим языком, приводящим к куче ошибок. И они в чем-то правы… Основной причиной называют динамическую типизацию и интерпретируемость javascript. На самом деле разработчика нужна не типизация и компиляция, а &lt;em&gt;проверка кода до запуска и подсказки по коду&lt;/em&gt;. Обе возможности доступны в Visual Studio с расширениями.&lt;/p&gt;  &lt;h5&gt;Проверка кода до запуска&lt;/h5&gt;  &lt;p&gt;Для этого в галерее расширений visual studio есть два расширения:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;&lt;a href="http://visualstudiogallery.msdn.microsoft.com/961e6734-cd3a-4afb-a121-4541742b912e" target="_blank"&gt;JSLint&lt;/a&gt; – встроенный в VS инструмент проверки кода javascript, который можно найти по адресу &lt;a href="http://www.jslint.com/"&gt;http://www.jslint.com/&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href="http://visualstudiogallery.msdn.microsoft.com/288a2b0f-1357-47b4-8215-1134c36bdf30" target="_blank"&gt;Javascript parser&lt;/a&gt; – расширение, которое парсит js код на лету (читай &lt;em&gt;показывает ошибки&lt;/em&gt;) и предоставляет навигацию по коду. &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;Эти два расширения позволяют отловить наиболее частые ошибки в коде.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh5.ggpht.com/-Ijg-8hY29vQ/T16CCg5I9TI/AAAAAAAAAKY/k5IBhNzENs4/image%25255B3%25255D.png?imgmax=800"&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/-PzViOKDDwsc/T16CJNjJU8I/AAAAAAAAAKg/uPdMh-QgFrI/image_thumb%25255B1%25255D.png?imgmax=800" width="543" height="395" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Кроме того рекомендую установить &lt;a href="http://visualstudiogallery.msdn.microsoft.com/872d27ee-38c7-4a97-98dc-0d8a431cc2ed" target="_blank"&gt;jscript editor extensions&lt;/a&gt; чтобы редактор был больше похож на редактор кода C#.&lt;/p&gt;  &lt;h5&gt;Подсказки по коду aka Intellisence&lt;/h5&gt;  &lt;p&gt;Эта возможность доступна начиная с Visual Studio 2008, но о ней до сих пор знает катастрофически малая часть разработчиков.&amp;#160; Чтобы intellicence для js заработал в visual studio, необходимо visual studio дать подсказку откуда брать файлы.&lt;/p&gt;  &lt;p&gt;Если вы пишите javascript код в html\aspx\ascx\materpage файлах, то vs автоматически подтягивает все .js файлы из тегов script и серверного контрола ScriptManager. Далее VS парсит файлы и пытается построить подсказку по коду. Если же нужно обновить подсказку, то можно нажать ctrl+shift+j.&lt;/p&gt;  &lt;p&gt;Если же код пишется в .js файле, то файлы, которые надо анализировать для построения подсказки надо указать в специальном теге:&lt;/p&gt;  &lt;pre class="brush: js;"&gt;///&amp;lt;reference path=&amp;quot;c:\scripts\jquery-1.5.1.js&amp;quot;/&amp;gt;&lt;/pre&gt;

&lt;p&gt;Имя файла можно указать любое, в том числе url с какого-либо CDN.&lt;/p&gt;

&lt;p&gt;В VS11 можно не указывать в каждом файле теги script или reference, а задать в настройках самой студии. Но в VS 2010 такой возможности нет.&lt;/p&gt;

&lt;p&gt;Также visual studio поддерживает документирующие комментарии для JS, как и для .NET языков. Подробнее описано по ссылке: &lt;a href="http://msdn.microsoft.com/en-us/library/bb385682.aspx"&gt;http://msdn.microsoft.com/en-us/library/bb385682.aspx&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Подсказка по коду позволяет набирать код на javascript быстрее и без ошибок в именах классов и функций.&lt;/p&gt;

&lt;h5&gt;Причем здесь SharePoint?&lt;/h5&gt;

&lt;p&gt;Клиентская объектная модель SharePoint для JS вполне успешно понимается студией. Это помогает разработчикам писать код работы с ClientOM на js.&lt;/p&gt;

&lt;p&gt;Чтобы все заработало нужно подключить следующие файлы:&lt;/p&gt;

&lt;pre class="brush: js; collapse: true;"&gt;///&amp;lt;reference path=&amp;quot;C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\MicrosoftAjax.js&amp;quot;/&amp;gt;
///&amp;lt;reference path=&amp;quot;C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\1033\init.debug.js&amp;quot;/&amp;gt;
///&amp;lt;reference path=&amp;quot;C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\1033\core.debug.js&amp;quot;/&amp;gt;
///&amp;lt;reference path=&amp;quot;C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.Core.debug.js&amp;quot;/&amp;gt;
///&amp;lt;reference path=&amp;quot;C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.Runtime.debug.js&amp;quot;/&amp;gt;
///&amp;lt;reference path=&amp;quot;C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.debug.js&amp;quot;/&amp;gt;
///&amp;lt;reference path=&amp;quot;C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\CUI.debug.js&amp;quot;/&amp;gt;
///&amp;lt;reference path=&amp;quot;C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.Ribbon.debug.js&amp;quot;/&amp;gt;
///&amp;lt;reference path=&amp;quot;C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.Exp.debug.js&amp;quot;/&amp;gt;
///&amp;lt;reference path=&amp;quot;C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.UI.Rte.debug.js&amp;quot;/&amp;gt;&lt;/pre&gt;

&lt;p&gt;Причем порядок файлов важен, но какой правильный – никто сказать не может, надо проверять.&lt;/p&gt;

&lt;p&gt;И все, дальше можно писать код на js, а студия будет подсказывать имена всех классов и функций клиентской объектной модели.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://lh4.ggpht.com/-Hmc0FPEkG3U/T16CKzYmrmI/AAAAAAAAAKo/bpj7wQb9xPw/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://lh6.ggpht.com/-pnL2M1GM4LU/T16CMMpjJyI/AAAAAAAAAKw/cZ9MWR4ytRY/image_thumb%25255B3%25255D.png?imgmax=800" width="535" height="389" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="http://lh3.ggpht.com/-sGdJnbehisc/T16CNqtG7wI/AAAAAAAAAK4/ZfZ8i64juyo/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://lh6.ggpht.com/-3oiwcPfxKlM/T16CQrQ5opI/AAAAAAAAALA/8DBQtJCJNdw/image_thumb%25255B6%25255D.png?imgmax=800" width="535" height="393" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="http://lh6.ggpht.com/-2fpwVQ1dYPA/T16CR57QIJI/AAAAAAAAALI/zvrJE8gr61k/s1600-h/image%25255B18%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://lh6.ggpht.com/-ItQqe7euT9U/T16CTV9WMqI/AAAAAAAAALQ/Zvkrz9g4vqg/image_thumb%25255B10%25255D.png?imgmax=800" width="536" height="398" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Весь код:&lt;/p&gt;

&lt;pre class="brush: js; collapse: true;"&gt;///&amp;lt;reference path=&amp;quot;C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\MicrosoftAjax.js&amp;quot;/&amp;gt;
///&amp;lt;reference path=&amp;quot;C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\1033\init.debug.js&amp;quot;/&amp;gt;
///&amp;lt;reference path=&amp;quot;C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\1033\core.debug.js&amp;quot;/&amp;gt;
///&amp;lt;reference path=&amp;quot;C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.Core.debug.js&amp;quot;/&amp;gt;
///&amp;lt;reference path=&amp;quot;C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.Runtime.debug.js&amp;quot;/&amp;gt;
///&amp;lt;reference path=&amp;quot;C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.debug.js&amp;quot;/&amp;gt;
///&amp;lt;reference path=&amp;quot;C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\CUI.debug.js&amp;quot;/&amp;gt;
///&amp;lt;reference path=&amp;quot;C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.Ribbon.debug.js&amp;quot;/&amp;gt;
///&amp;lt;reference path=&amp;quot;C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.Exp.debug.js&amp;quot;/&amp;gt;
///&amp;lt;reference path=&amp;quot;C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.UI.Rte.debug.js&amp;quot;/&amp;gt;

function showWebTitle() {
    var ctx = SP.ClientContext.get_current();
    var web = ctx.get_web();
    ctx.load(web, 'Title');
    ctx.executeQueryAsync(function () {
        alert(web.get_title());
    }, function myfunction(o, ex) {
        alert(ex.get_message());
    });
}&lt;/pre&gt;

&lt;h5&gt;Развертывание скриптов&lt;/h5&gt;

&lt;p&gt;Самый простой способ сделать ваш скрипт доступным в SharePoint – с помощью sandboxed решения и элемента CustomAction.&lt;/p&gt;

&lt;p&gt;Для этого надо создать новый проект, поместить в него один элемент –модуль, поместить в него файл скрипта&amp;#160; и написать в манифесте следующий xml:&lt;/p&gt;

&lt;pre class="brush: xml;"&gt;&amp;lt;Elements xmlns=&amp;quot;http://schemas.microsoft.com/sharepoint/&amp;quot;&amp;gt;
  &amp;lt;Module Name=&amp;quot;Module1&amp;quot;&amp;gt;
  &amp;lt;File Path=&amp;quot;Module1\JScript1.js&amp;quot; Url=&amp;quot;Module1/JScript1.js&amp;quot; /&amp;gt;
&amp;lt;/Module&amp;gt;

  &amp;lt;CustomAction Location=&amp;quot;ScriptLink&amp;quot;
                ScriptSrc=&amp;quot;~site/Module1/JScript1.js&amp;quot;/&amp;gt;
&amp;lt;/Elements&amp;gt;&lt;/pre&gt;

&lt;p&gt;Далее можно нажать f5, написать прямо в строке адреса вызов функции и посмотреть результат. При этом работает интерактивная отладка в Visual Studio.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://lh3.ggpht.com/-aMyQyLDMJ40/T16CWItUXaI/AAAAAAAAALY/Gav_bdRr7W4/s1600-h/image%25255B26%25255D.png"&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="image" border="0" alt="image" src="http://lh4.ggpht.com/-20Hab_ojVNI/T16CXsrnv3I/AAAAAAAAALg/TZ25HK6-NEU/image_thumb%25255B14%25255D.png?imgmax=800" width="536" height="409" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;Скрипты в веб-частях&lt;/h5&gt;

&lt;p&gt;Если вы собираетесь писать скрипты в веб-частях, то вы можете написать кучу тегов script, аналогично reference, но это приведет к загрузке всех файлов при размещении веб-части на форме, что нежелательно.&lt;/p&gt;

&lt;p&gt;Есть очень хороший способ с этим бороться:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://lh5.ggpht.com/-zjsuI1iGKKw/T16CaOJiTkI/AAAAAAAAALo/uMry5LtCcCk/s1600-h/image%25255B33%25255D.png"&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="image" border="0" alt="image" src="http://lh3.ggpht.com/-hixA-vYweXw/T16Cbg-Ou3I/AAAAAAAAALw/0sZwy7UMzIk/image_thumb%25255B17%25255D.png?imgmax=800" width="558" height="348" /&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;Директивы условной компиляции приведут к тому что теги script не попадут в результрующую разметку, а VisualStudio игнорирует эти директивы и нормально показывает intellisence.&lt;/p&gt;

&lt;h5&gt;Заключение&lt;/h5&gt;

&lt;p&gt;Несмотря на то что javascript очень недружественный к разработчику язык, хорошие инструменты позволяют писать на нем с тем же уровнем комфорта, как на .NET языках.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1302241583051203640-242896804918697880?l=gandjustas.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=p5OGsMCEhJU:friE_MiK5Ow: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=p5OGsMCEhJU:friE_MiK5Ow:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?i=p5OGsMCEhJU:friE_MiK5Ow:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=p5OGsMCEhJU:friE_MiK5Ow: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/p5OGsMCEhJU" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/GandjustasBlog/~3/p5OGsMCEhJU/sharepoint-javascript-visual-studio.html</link><author>noreply@blogger.com (Станислав Выщепан)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://lh4.ggpht.com/-PzViOKDDwsc/T16CJNjJU8I/AAAAAAAAAKg/uPdMh-QgFrI/s72-c/image_thumb%25255B1%25255D.png?imgmax=800" height="72" width="72" /><thr:total>4</thr:total><feedburner:origLink>http://gandjustas.blogspot.com/2012/03/sharepoint-javascript-visual-studio.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-1302241583051203640.post-3307112291466361186</guid><pubDate>Tue, 13 Mar 2012 06:00:00 +0000</pubDate><atom:updated>2012-03-13T10:00:02.115+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. Часть 2.</title><description>&lt;p&gt;Полгода прошло с тех пор как я опубликовал &lt;a href="http://gandjustas.blogspot.com/2011/08/silverlight-sharepoint-1.html" target="_blank"&gt;первую часть статьи&lt;/a&gt;. Как раз полгода назад произошло смещение приоритетов веб-разработки Microsoft&amp;#160; в сторону HTML5, и злые языки начали пророчить скорую смерть Silverlight. Тем не менее вышла пятая версия SL и, скорее всего, будет еще и шестая. Тем не менее фокус больше смещается в сторону HTML\JS, и по слухам в следующей версии SharePoint будет гораздо больше javascript и гораздо меньше SL.&lt;/p&gt;  &lt;p&gt;Поэтому данная статья будет больше обзорной.&lt;/p&gt;  &lt;h5&gt;Варианты развертывания Silverlight приложений&lt;/h5&gt;  &lt;p&gt;Как обычно их три:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;В sandbox решении в виртуальную файловую систему sharepoint. &lt;/li&gt;    &lt;li&gt;В fulltrust решении в физическую файловую систему. &lt;/li&gt;    &lt;li&gt;Приложение на внешнем сервере, с использованием &lt;a href="http://msdn.microsoft.com/en-us/library/ee787051.aspx" target="_blank"&gt;fluid application model&lt;/a&gt;. &lt;/li&gt; &lt;/ol&gt;  &lt;h5&gt;Размещение Silverlight на портале.&lt;/h5&gt;  &lt;p&gt;Самый часто используемый способ – веб-часть silverlight. Её можно найти в категории Media. Также возможно создание своих веб-частей, которые отображают silverlight приложение, чтобы передать дополнительные параметры и\или сделать fallback. Но лучше такие веб-части не создавать, а воспользоваться &lt;a href="http://visualstudiogallery.msdn.microsoft.com/e8360a85-58ca-42d1-8de0-e48a1ab071c7" target="_blank"&gt;расширением для visual studio&lt;/a&gt;. А в следующей версии visual studio такая веб-часть доступна “изкаропки”.&lt;/p&gt;  &lt;p&gt;Еще один вариант – создание field control на silverlight. Пример можно посмотреть в &lt;a href="http://msdn.microsoft.com/en-us/library/ff679964.aspx" target="_blank"&gt;статье на msdn&lt;/a&gt;. &lt;/p&gt;  &lt;h5&gt;Демо-приложение&lt;/h5&gt;  &lt;p&gt;Для демонстрации всех способов развертывания напишу простое приложение, при запуске оно будет выводить Title узла, на котором запущено приложение.&lt;/p&gt;  &lt;p&gt;Сначала надо добавить в проект SL сборки &lt;em&gt;Microsoft.SharePoint.Client.Silverlight&lt;/em&gt;&amp;#160; и &lt;em&gt;Microsoft.SharePoint.Client.Silverlight.Runtime&lt;/em&gt;. Их можно найти в папке {SharePointRoot}\TEMPLATE\LAYOUTS\ClientBin.&lt;/p&gt;  &lt;p&gt;Теперь можно написать немного кода:&lt;/p&gt;  &lt;pre class="brush: csharp;"&gt;var ctx = ClientContext.Current;
var web = ctx.Web;
ctx.Load(web, w =&amp;gt; w.Title);

ctx.ExecuteQueryAsync(
(o, args) =&amp;gt; //success
{
    Dispatcher.BeginInvoke(() =&amp;gt;
    {
        tbTitle.Text = web.Title;
    });

},
(o, args) =&amp;gt; //failure
{
    Dispatcher.BeginInvoke(() =&amp;gt;
        {
            MessageBox.Show(args.Exception.ToString());
        });
});&lt;/pre&gt;

&lt;p&gt;&amp;#160;&lt;/p&gt;

&lt;p&gt;Для тех кто не знаком с клиентской объектной моделью SharePoint краткий ликбез. Точка входа в клиентскую объектную модель – класс ClientContext, в SL приложении можно получить “текущий контекст”, если приложение развернуто в SharePoint.&lt;/p&gt;

&lt;p&gt;Все объекты в клиентской объектной модели являются”обещаниями” (promise), то есть на момент получения они не содержат значений, а только общение что значения когда-нибудь там будут.&amp;#160; Чтобы загрузить свойства объекта с сервера надо выполнить метод ClientContext.Load. В методе Load можно указать с помощью лямбда-выражений какие свойства загружать.&lt;/p&gt;

&lt;p&gt;Другая особенность клиентской объектной модели заключается в в том что команды не выполняются сразу, а складываются в очередь и отправляются на сервер при вызове ExecuteQuery\ExecuteQueryAsync. Так как в SL нельзя блокировать поток UI, то воспользуемся асинхронным вариантом.&lt;/p&gt;

&lt;p&gt;Третья особенность заключается в том что коллбеки завершения асинхронного вызова клиентской объектной модели не маршалятся в поток UI (сколько непонятных слов, сам в шоке). Поэтому надо вызывать Dispatcher.BeginInvoke чтобы поменять что-либо в UI или вывести Message Box.&lt;/p&gt;

&lt;p&gt;На первый взгляд код выглядит сложным, но при некоторой сноровке пишется “на автомате”.&lt;/p&gt;

&lt;h5&gt;Развертывание на уровне фермы&lt;/h5&gt;

&lt;p&gt;Это самый простой и прямолинейный вариант. Он вполне может быть оправдан если вы используете Silverlight приложение как field control для кастомного поля, в остальных случаях я бы не рекомендовал.&lt;/p&gt;

&lt;p&gt;Чтобы развернуть приложение в физическую файловую систему надо создать&amp;#160; один пустой элемент (SPI), даже не нужен манифест (Element.xml) и фича, их можно просто удалить.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://lh3.ggpht.com/-uLAnOcrXcr0/T1vf2nehPPI/AAAAAAAAAI4/emxw34zYEWY/s1600-h/image%25255B9%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/-bAg4cxXqWRU/T1vf3r01OvI/AAAAAAAAAJA/ktNYAlIxaYY/image_thumb%25255B7%25255D.png?imgmax=800" width="410" height="453" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Далее в необходимо добавить в элемент project output из проекта silverlight, обязательно указав Deployment Type=TemplateFile&lt;/p&gt;

&lt;p&gt;&lt;a href="http://lh5.ggpht.com/-9hAGquSNUgw/T1vf4eFAL2I/AAAAAAAAAJE/hKE33U6-X9M/s1600-h/image%25255B14%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://lh6.ggpht.com/-JCJW0ITNfGs/T1vf5XR1eoI/AAAAAAAAAJQ/WPr5b6ZLMXE/image_thumb%25255B10%25255D.png?imgmax=800" width="546" height="300" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;После этого надо добавить элемент в package (не в фичу). &lt;/p&gt;

&lt;p&gt;Последний шаг перед развертыванием – включить отладку Silverlight.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://lh5.ggpht.com/-w9Sh0Jct_CI/T1vf6sDLjQI/AAAAAAAAAJY/WINT5_T_CNk/s1600-h/image%25255B18%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://lh6.ggpht.com/-NVKVYjyyIs0/T1vf7lnUidI/AAAAAAAAAJg/6nyUMCXPcTg/image_thumb%25255B12%25255D.png?imgmax=800" width="548" height="376" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;После этого можно жать F5 на проекте SharePoint, добавлять на страницу веб-часть Silverlight и ставить точки останова в проекте Silverlight.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://lh4.ggpht.com/-RK9XiSwUUEg/T1vf8kqecOI/AAAAAAAAAJo/lpxMDQOOTq4/s1600-h/image%25255B27%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/-1KJjXPyXj2Y/T1vf9o0Ln7I/AAAAAAAAAJw/TlzqCxdnahQ/image_thumb%25255B17%25255D.png?imgmax=800" width="552" height="466" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;Развертывание в sandbox&lt;/h5&gt;

&lt;p&gt;Для развертывания решения в sandbox надо выполнить те же шаги, что и для fulltrust решения, но вместо пустого элемента создать модуль, указать Deployment Type=ElementFile в project output references и использовать фичу для развертывания.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://lh5.ggpht.com/-CevDpRNeVR4/T1vf-W-kC9I/AAAAAAAAAJ4/1zbp92abdec/s1600-h/image%25255B33%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/-KtHLQx9xgdg/T1vf_pO-dHI/AAAAAAAAAKA/sjyWZGor5Iw/image_thumb%25255B21%25255D.png?imgmax=800" width="445" height="500" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="http://lh3.ggpht.com/--CuiyCHENI4/T1vgATP2pAI/AAAAAAAAAKE/GIb1rK0DvyI/s1600-h/image%25255B38%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/-Ixg-Yk5dipI/T1vgBcDirqI/AAAAAAAAAKQ/vGDWArqbC-E/image_thumb%25255B24%25255D.png?imgmax=800" width="564" height="424" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;И все, в манифест модуля автоматически попадет элемент с xap файлом.&lt;/p&gt;

&lt;p&gt;Далее как обычно: F5, веб-часть, отладка.&lt;/p&gt;

&lt;h5&gt;Развертывание на внешнем хосте&lt;/h5&gt;

&lt;p&gt;Для начала надо включить поддержку внешних приложений в SharePoint. Это можно сделать в powershell небольшим скриптом.&lt;/p&gt;

&lt;pre class="brush: ps;"&gt;$cs = [Microsoft.SharePoint.Administration.SPWebService]::ContentService
$cs.ExternalApplicationSettings.Enabled = $true
$cs.Update()&lt;/pre&gt;

&lt;p&gt;Я не проверял включены ли внешние приложения в office 365, очень надеюсь что включены. Иначе я зря это все пишу.&lt;/p&gt;

&lt;p&gt;Чтобы разместить внешнее приложение в SharePoint надо передать веб-части silverlight манифест приложения (&lt;a href="http://msdn.microsoft.com/en-us/library/ee535930.aspx" target="_blank"&gt;applicationXml&lt;/a&gt;). При добавлении веб-части появится запрос манифеста (после включения внешних приложений).&lt;/p&gt;

&lt;pre class="brush: xml;"&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;utf-8&amp;quot; ?&amp;gt;
&amp;lt;applicationParts xmlns='http://schemas.microsoft.com/sharepoint/2009/fluidapp'&amp;gt;
  &amp;lt;applicationPart&amp;gt;
    &amp;lt;metaData&amp;gt;
      &amp;lt;applicationId&amp;gt;F0F0E6C1-5B42-4277-9EFF-777F1330BCD8&amp;lt;/applicationId&amp;gt;
      &amp;lt;applicationUrl&amp;gt;http://localhost:21351/HostWebSite/ClientBin/SlApp.xap&amp;lt;/applicationUrl&amp;gt;
      &amp;lt;principal&amp;gt;contoso\sp-app&amp;lt;/principal&amp;gt;
      &amp;lt;sharepointRequestHandlerUrl&amp;gt;/HostWebSite/sp.ashx&amp;lt;/sharepointRequestHandlerUrl&amp;gt;
    &amp;lt;/metaData&amp;gt;
    &amp;lt;data&amp;gt;
      &amp;lt;webPartProperties&amp;gt;
        &amp;lt;property name='Title'&amp;gt;SlApp&amp;lt;/property&amp;gt;
        &amp;lt;property name='Height'&amp;gt;400px&amp;lt;/property&amp;gt;
        &amp;lt;property name='Width'&amp;gt;300px&amp;lt;/property&amp;gt;
        &amp;lt;property name='MinRuntimeVersion'&amp;gt;3.0&amp;lt;/property&amp;gt;
      &amp;lt;/webPartProperties&amp;gt;
      &amp;lt;customProperties&amp;gt;
      &amp;lt;/customProperties&amp;gt;
    &amp;lt;/data&amp;gt;
  &amp;lt;/applicationPart&amp;gt;
&amp;lt;/applicationParts&amp;gt;&lt;/pre&gt;

&lt;p&gt;&amp;#160;&lt;/p&gt;

&lt;p&gt;До того как создавать веб-часть необходимо дать доступ пользователю, который указан в разделе principal, с помощью метода &lt;a href="http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spweb.addapplicationprincipal.aspx" target="_blank"&gt;AddApplicationPrincipal&lt;/a&gt;. Лучше всего это делать в коде активации фичи.&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
    var web = properties.Feature.Parent as SPWeb;
    var principal = web.AddApplicationPrincipal(&amp;quot;contoso\\sp-app&amp;quot;, true, false);
    web.RoleAssignments.Add(new SPRoleAssignment(principal)
    {
        //Don't do this in podution code
        RoleDefinitionBindings = { web.RoleDefinitions.GetByType(SPRoleType.Administrator) }
    });           
}

public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
    var web = properties.Feature.Parent as SPWeb;
    var principal = web.AddApplicationPrincipal(&amp;quot;contoso\\sp-app&amp;quot;, true, false);
    web.RoleAssignments.Remove(principal);
}&lt;/pre&gt;

&lt;p&gt;После деплоя решения и активации фичи можно попробовать добавить на страницу веб-часть Silverlight и указать application xml. Приложение отобразится но сразу будет падать с ошибкой.&lt;/p&gt;

&lt;p&gt;Небольшой ликбез про кросс-доменные вызовы в silverlight. По умолчанию приложение на SL может обращаться только к тому домену, откуда оно загружено. В модели external application в sharepoint приложение silverlight находится в другом домене и ему кросс-доменные вызовы запрещены.&lt;/p&gt;

&lt;p&gt;Иногда может помочь файл clientaccesspolicy.xml, который разрешает silvelight обращаться за пределы домена, но там надо явно прописать куда можно обращаться или указать что обращаться можно везде. Но это небезопасно, так как&amp;#160; в случае sharepoint приложение на Silverlight будет передавать пароль по сети для аутентификации.&lt;/p&gt;

&lt;p&gt;Поэтому сделана такая система. В манифесте приложения указывается куда silverlight приложение будет отправлять запросы при вызове клиентской объектной модели (элемент &lt;em&gt;sharepointRequestHandlerUrl&lt;/em&gt;). Обработчик по указанному url будет определять какой сайт sharepoint обращается и передавать логин и пароль именно для этого сайта.&lt;/p&gt;

&lt;p&gt;Чтобы создать этот обработчик надо в сайте, где хостится silverlight создать обычный ashx-хендлер.&lt;/p&gt;

&lt;pre class="brush: csharp;"&gt;public class sp : IHttpHandler {
    
    public void ProcessRequest (HttpContext context) {
        RequestForwarder forwarder = new RequestForwarder(context);
        if (!String.IsNullOrEmpty(forwarder.Url))
        {
            forwarder.WebRequest.Credentials
                = new System.Net.NetworkCredential(&amp;quot;sp-app&amp;quot;, &amp;quot;P@ssw0rd&amp;quot;, &amp;quot;contoso&amp;quot;);
            forwarder.ProcessRequest();
        }
    }
 
    public bool IsReusable {
        get {
            return false;
        }
    }
}&lt;/pre&gt;

&lt;p&gt;&amp;#160;&lt;/p&gt;

&lt;p&gt;Класс RequestForwarder находится во “взрослой” клиентской объектной модели.&lt;/p&gt;

&lt;p&gt;После добавления хендлера приложение перестает выдавать ошибку и работает нормально.&lt;/p&gt;

&lt;h5&gt;Заключение&lt;/h5&gt;

&lt;p&gt;Как видите довольно много возможностей развертывания&amp;#160; приложений Silverlight в SharePoint. При этом само приложение не поменялось ни в одном сценарии.&lt;/p&gt;
&lt;iframe style="padding-bottom: 0px; background-color: #fcfcfc; padding-left: 0px; padding-right: 0px; padding-top: 0px" title="Preview" height="120" marginheight="0" src="https://skydrive.live.com/embed?cid=E74E2842A8A54DC1&amp;amp;resid=E74E2842A8A54DC1%21608&amp;amp;authkey=AFBS--TufsmkxDs" frameborder="0" width="98" marginwidth="0" scrolling="no"&gt;&lt;/iframe&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1302241583051203640-3307112291466361186?l=gandjustas.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=ALCM2EI-fnI:U9-BohcA_dM: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=ALCM2EI-fnI:U9-BohcA_dM:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?i=ALCM2EI-fnI:U9-BohcA_dM:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=ALCM2EI-fnI:U9-BohcA_dM: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/ALCM2EI-fnI" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/GandjustasBlog/~3/ALCM2EI-fnI/silverlight-sharepoint-2.html</link><author>noreply@blogger.com (Станислав Выщепан)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://lh3.ggpht.com/-bAg4cxXqWRU/T1vf3r01OvI/AAAAAAAAAJA/ktNYAlIxaYY/s72-c/image_thumb%25255B7%25255D.png?imgmax=800" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://gandjustas.blogspot.com/2012/03/silverlight-sharepoint-2.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-1302241583051203640.post-8319286402973856116</guid><pubDate>Mon, 12 Mar 2012 06:00:00 +0000</pubDate><atom:updated>2012-03-12T10:00:08.362+04:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">SharePoint</category><title>Поиск в приложениях SharePoint. Часть 1.</title><description>&lt;p&gt;Одна из наиболее часто возникающих задач при разработке порталов на SharePoint&amp;#160; - создание веб-части , отображающей ближайшие дни рождения пользователей.&lt;/p&gt;  &lt;p&gt;В SharePoint 2010 есть служба профилей пользователей, которая хранит данные о пользователях, в том числе дни рождения, в масштабах фермы. Для отображения списка пользователей можно использовать веб-часть &amp;quot;Основные результаты поиска людей&amp;quot;. Но если разместить веб-часть на форму, то она не выводит результатов.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh3.ggpht.com/-TtRxDcQLGzI/T1uzDoEhj5I/AAAAAAAAAHI/7cYNveE0f2o/s1600-h/image%25255B8%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/-_JBFeZJffc0/T1uzFwh9kuI/AAAAAAAAAHQ/7bJDhSH4d1g/image_thumb%25255B4%25255D.png?imgmax=800" width="552" height="429" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Чтобы отобразить результаты необходимо &amp;quot;Добавить текст в запрос&amp;quot;.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh5.ggpht.com/-HNEV_rcbxxE/T1uzHpKjFrI/AAAAAAAAAHY/N9y3-9TkSMw/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://lh5.ggpht.com/-1msL8vMwTCc/T1uzJG7FewI/AAAAAAAAAHg/vWii7ufMGFY/image_thumb%25255B3%25255D.png?imgmax=800" width="551" height="355" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Так как используется поиск надо заранее выполнить обход и в службе профилей должны быть записи.&lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;Веб-часть отображения результатов поиска наследует DataFormWebPart, поэтому можно настроить отображение в виде xslt. Но для этого надо отключить отображение по-умолчанию.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh4.ggpht.com/-ORqgB_V-S2s/T1uzLn-6a5I/AAAAAAAAAHo/FDhFvFoHCeU/s1600-h/image%25255B14%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/-xN9pev_rE-Y/T1uzNMYPBzI/AAAAAAAAAHw/ippfW5rXbeM/image_thumb%25255B8%25255D.png?imgmax=800" width="548" height="457" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;XSL для отображения:&lt;/p&gt;  &lt;pre class="brush: xml; collapse: true;"&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;
&amp;lt;xsl:stylesheet version=&amp;quot;1.0&amp;quot; xmlns:xsl=&amp;quot;http://www.w3.org/1999/XSL/Transform&amp;quot;&amp;gt;
&amp;lt;xsl:output method=&amp;quot;xml&amp;quot; version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; indent=&amp;quot;yes&amp;quot;/&amp;gt;
&amp;lt;xsl:template match=&amp;quot;/&amp;quot;&amp;gt;
  &amp;lt;table&amp;gt;
    &amp;lt;tr&amp;gt;
      &amp;lt;th&amp;gt;
        User name
      &amp;lt;/th&amp;gt;
      &amp;lt;th&amp;gt;
        Account name
      &amp;lt;/th&amp;gt;
    &amp;lt;/tr&amp;gt;
    &amp;lt;xsl:apply-templates select=&amp;quot;All_Results/Result&amp;quot;/&amp;gt;
  &amp;lt;/table&amp;gt;
&amp;lt;/xsl:template&amp;gt;
  &amp;lt;xsl:template match=&amp;quot;Result&amp;quot;&amp;gt;
    &amp;lt;tr&amp;gt;
      &amp;lt;td&amp;gt;
        &amp;lt;xsl:value-of select=&amp;quot;preferredname&amp;quot;/&amp;gt;
      &amp;lt;/td&amp;gt;
      &amp;lt;td&amp;gt;
        &amp;lt;xsl:value-of select=&amp;quot;accountname&amp;quot;/&amp;gt;
      &amp;lt;/td&amp;gt;
    &amp;lt;/tr&amp;gt;
  &amp;lt;/xsl:template&amp;gt;
&amp;lt;/xsl:stylesheet&amp;gt;&lt;/pre&gt;

&lt;p&gt;Веб-части поиска выводят управляемые свойства поиска, которые задаются в central administration. Чтобы выводить (и использовать в запросах) дату рождения необходимо создать управляемое свойство, которое отображается на свойство профиля.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://lh6.ggpht.com/-2q-8LKeL3Ik/T1uzOZAh3-I/AAAAAAAAAH4/fvwrUPqKA_4/s1600-h/image%25255B19%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/-dR8Fx8vXfs0/T1uzP2FczoI/AAAAAAAAAIA/6tvo6Y4IOXs/image_thumb%25255B11%25255D.png?imgmax=800" width="556" height="452" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="http://lh5.ggpht.com/-RKND5PwhbiY/T1uzRz7GW-I/AAAAAAAAAII/-Zneygdt3rs/s1600-h/image%25255B29%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/-M6rwUl1SptA/T1uzTS0enXI/AAAAAAAAAIQ/7kN896VGb9E/image_thumb%25255B17%25255D.png?imgmax=800" width="557" height="462" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;После добавления свойства обязательно надо выполнить полный обход содержимого.&lt;/p&gt;

&lt;p&gt;После обходи можно добавить элемент в свойства columns веб-части и дописать xslt для вывод нужного значения.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://lh3.ggpht.com/-hxLIHJ6SFG4/T1uzVuPJDZI/AAAAAAAAAIY/1P93BRws52E/s1600-h/image%25255B34%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/-ZS_AfNzssks/T1uzXVIUf5I/AAAAAAAAAIg/Ew0gzdX6DUk/image_thumb%25255B20%25255D.png?imgmax=800" width="552" height="459" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Даты в профиле пользователя вводятся в формате “Месяц День”, 2000 год дописывается сам (чтобы можно было 29 февраля ввести). При выводе вам надо самостоятельно форматировать даты.&lt;/p&gt;

&lt;p&gt;Теперь остается две проблемы:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Сортировка результатов. По-умолчанию веб часть не поддерживает сортировку по произвольному полю. &lt;/li&gt;

  &lt;li&gt;Динамический запрос. В параметрах веб-части можно ввести только статическую строку и получить текущую дату нельзя. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Для того чтобы победить обе проблемы можно наследоваться от веб-части поиска, но веб-часть поиска людей sealed. Поэтому надо наследоваться от веб-части поиска контента.&lt;/p&gt;

&lt;p&gt;Для этого нужно создать веб-часть в visual studio.&lt;/p&gt;

&lt;pre class="brush: csharp; collapse: true;"&gt;public class WebPart1 : CoreResultsWebPart
{
    protected override string DefaultSearchLocation
    {
        get
        {
            //For people search
            return &amp;quot;LocalPeopleSearchIndex&amp;quot;;
        }
    }

    protected override void ConfigureDataSourceProperties()
    {
        base.ConfigureDataSourceProperties();

        var ds = this.DataSource as CoreResultsDatasource;
        ds.SortOrder.Clear();
        ds.SortOrder.Add(&amp;quot;Birthday&amp;quot;, Microsoft.Office.Server.Search.Query.SortDirection.Descending);
    }

    protected override void OnInit(EventArgs e)
    {
        base.OnInit(e);

        var today2000 = new DateTime(2000, DateTime.Today.Month, DateTime.Today.Day);           
        this.AppendedQuery = string.Format(&amp;quot;Birthday&amp;gt;=\&amp;quot;{0}\&amp;quot; AND Birthday&amp;lt;\&amp;quot;{1}\&amp;quot; &amp;quot;, 
                                    today2000.ToShortDateString(),
                                    today2000.AddDays(3).ToShortDateString());
    }
}&lt;/pre&gt;

&lt;p&gt;Свойство AppendedQuery – это “Добавить текст в запрос” в интерфейсе.&lt;/p&gt;

&lt;p&gt;Результат после размещения на форме и правки представления:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://lh4.ggpht.com/-Yv1AtU-DXTg/T1uzYX87xXI/AAAAAAAAAIo/pX9W7yzNklU/s1600-h/image%25255B41%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/--bji5kx2Jzs/T1uzZry2vgI/AAAAAAAAAIw/chUqEcwAm0Y/image_thumb%25255B25%25255D.png?imgmax=800" width="552" height="471" /&gt;&lt;/a&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-8319286402973856116?l=gandjustas.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=zcj1Ge76RYk:eSI8GeBibdg: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=zcj1Ge76RYk:eSI8GeBibdg:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?i=zcj1Ge76RYk:eSI8GeBibdg:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=zcj1Ge76RYk:eSI8GeBibdg: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/zcj1Ge76RYk" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/GandjustasBlog/~3/zcj1Ge76RYk/sharepoint-1.html</link><author>noreply@blogger.com (Станислав Выщепан)</author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://lh4.ggpht.com/-_JBFeZJffc0/T1uzFwh9kuI/AAAAAAAAAHQ/7bJDhSH4d1g/s72-c/image_thumb%25255B4%25255D.png?imgmax=800" height="72" width="72" /><thr:total>12</thr:total><feedburner:origLink>http://gandjustas.blogspot.com/2012/03/sharepoint-1.html</feedburner:origLink></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-1302241583051203640.post-5182434830410150215</guid><pubDate>Tue, 31 Jan 2012 06:00:00 +0000</pubDate><atom:updated>2012-01-31T10:00:02.890+04:00</atom:updated><category domain="http://www.blogger.com/atom/ns#">SharePoint</category><title>Миф о кастомизации страниц в SharePoint</title><description>&lt;p&gt;Немного теории. В SharePoint есть &lt;em&gt;виртуальная файловая система&lt;/em&gt;, которая содержится в контентной БД. Когда вы в farm solution помещаете в нее файл, с помощью модуля, то файл лежит на диске в {SharePointRoot}\TEMPLATE\Features, а контентной БД появляется ссылка на этот файл.&lt;/p&gt;  &lt;p&gt;Таким образом экономится место в БД, ведь один и тот же файл может быть добавлен на несколько сайтов. Кроме того упрощается обновление – достаточно обновить файл на диске. &lt;/p&gt;  &lt;p&gt;Такие файлы называются &lt;em&gt;uncustomized&lt;/em&gt;, &lt;em&gt;ghosted&lt;/em&gt; (в терминологии 2007 шарика) или просто &lt;em&gt;некастомизироваными&lt;/em&gt;. Если же попробовать внести какое-либо изменение в файл &lt;strong&gt;в виртуальной файловой системе (!)&lt;/strong&gt;, то измененное состояние файла записывается в контентную БД и SharePoint перестает обращаться к диску за файлом. такие файлы называются &lt;em&gt;customized&lt;/em&gt;, &lt;em&gt;unghosted&lt;/em&gt; или &lt;em&gt;кастомизированные&lt;/em&gt;. &lt;/p&gt;  &lt;p&gt;Причем это относится не только к файлам в виртуальной ФС, но и к типам контента, шаблонам списков итд. Но об этом другой раз…&lt;/p&gt;  &lt;h4&gt;Миф&lt;/h4&gt;  &lt;blockquote&gt;   &lt;p&gt;Кастомизированные страницы работают медленнее некастомизированных.&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;В сети довольно много рассуждений на эту тему, что для кастомизированных страниц не работают какие-то механизмы кеширования, требуется перекомпиляция каждый раз итд.&lt;/p&gt;  &lt;p&gt;Я решил просто померить как изменяется скорость работы SharePoint от кастомизации страниц. Сделал простой веб-тест в Visual Studio, который обращается к странице по-умолчанию, а потом создал нагрузочный тест и замерял &lt;strong&gt;Page Response Time&lt;/strong&gt;.&lt;/p&gt;  &lt;p&gt;Вот график (кликабельно):&lt;/p&gt;  &lt;p&gt;&lt;a href="https://public.blu.livefilestore.com/y1p_8XFGKM9Hoe3olDx_FjGwg097SgZdhWwcNccWt3UeoLHkSfrvLLxpUJs4yMlb_Zc76MOHfi4Vf8SxfnejLyNzA/customized%20page%20performance.png?psid=1" target="_blank"&gt;&lt;img src="https://public.blu.livefilestore.com/y1p_8XFGKM9Hoe3olDx_FjGwg097SgZdhWwcNccWt3UeoLHkSfrvLLxpUJs4yMlb_Zc76MOHfi4Vf8SxfnejLyNzA/customized%20page%20performance.png?psid=1" width="517" height="281" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Центральный пик - сохранение кастомизированной страницы, слева - время ответда для некастомизированной старницы, справа - время ответа кастомизированной страницы. Среднее время ответа некастомизированной страницы – 1.7 сек, кастомизироанной – 1.9 сек. Разница около 10%. Если на странице будут “тяжелые” контролы, то разница станет еще менее заметной.&lt;/p&gt;  &lt;p&gt;При кастомизации мастер страницы график еще интереснее:&lt;/p&gt;  &lt;p&gt;&lt;a href="https://public.blu.livefilestore.com/y1pJ-CsVyCXXE2xXm1JH_IOcQKDGxiVKQcRLJgOUUDtKF6zo1OrEbTz5prFTzORejYq4lYlo3chgyfWgHNSNy06sw/customized%20masterpage%20performance.png?psid=1" target="_blank"&gt;&lt;img src="https://public.blu.livefilestore.com/y1pJ-CsVyCXXE2xXm1JH_IOcQKDGxiVKQcRLJgOUUDtKF6zo1OrEbTz5prFTzORejYq4lYlo3chgyfWgHNSNy06sw/customized%20masterpage%20performance.png?psid=1" width="544" height="288" /&gt;&lt;/a&gt;&lt;/p&gt;    &lt;p&gt;Большой пик – кастомизация мастер-страницы. Справа пик поменьше – было сделано Revert To Definition для мастер-страницы. Получается результаты почти одинаковые, за исключением того, что для некастомизированного masterpage результаты гораздо менее стабильны.&lt;/p&gt;  &lt;h4&gt;Заключение&lt;/h4&gt;  &lt;p&gt;Использовать исключительно некастомизированные страницы имеет смысл только для hiload публичных сайтов. Хотя кто использует sharepoint для hiload сценариев???….&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1302241583051203640-5182434830410150215?l=gandjustas.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=RxhvvuLTWtc:_y1i70gGR50: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=RxhvvuLTWtc:_y1i70gGR50:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GandjustasBlog?i=RxhvvuLTWtc:_y1i70gGR50:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GandjustasBlog?a=RxhvvuLTWtc:_y1i70gGR50: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/RxhvvuLTWtc" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/GandjustasBlog/~3/RxhvvuLTWtc/sharepoint.html</link><author>noreply@blogger.com (Станислав Выщепан)</author><thr:total>5</thr:total><feedburner:origLink>http://gandjustas.blogspot.com/2012/01/sharepoint.html</feedburner:origLink></item><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>2</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>5</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></channel></rss>

