Я участвую в SPCUA 2012.

25 апреля пройдет событие года по теме SharePoint в СНГ – конференция SharePoint Conference Ukraine 2012 (http://spcua.com/). Генеральный партнер конференции Microsoft Украина, организатор – Lizard Soft (http://lizard-soft.com).

Конференция собрала докладчиков и участников не только со всего СНГ — но и из США и Европы. Ее именитыми докладчиками стали Майкл Ноэль (Michael Noel), Пол Свайдер (Paul J. Swider), Павел Вробель (Pawel Wrobel). Microsoft делегировала на конференцию докладчиков из ЕС, российского и украинского офиса Microsoft. Партнеры конференции из Австралии, Великобритании, Польши, России и Украины.

А также на конференции я буду читать доклад “Искусство управления SharePoint: как получить максимальную выгоду для бизнеса” и отвечать на вопросы.



Блеск и нищета Task Parallel Library

Task Parallel Library (пространство имен System.Threading.Tasks) появилась еще в .NET 4, но большой популярностью не пользовалась. На подходе компилятор C# 5, который поддерживает конструкцию async\await. В скомпилированом коде async метод превращается в метод возвращающий Task\Task<T>.  Поэтому в скором времени использование TPL станет повсеместным.

Task Parallel Library – имеет очень мощные механизмы: вложенные (nested) задачи, локальные очереди для потоков, чтобы уменьшить латентность, work-stealing алгоритмы для работы, кучи опций запуска задач и продолжений, различные планировщики. Все создано в лучших традициях оптимизации асинхронного кода.

Но, TPL создавался до мозга костей практиками и цели сделать библиотеку удобной для потребителей даже не ставилось судя по всему.

  1. Как это не смешно, то в стандартной библиотеке нету метода, позволяющего обернуть значение T в Task<T>. (Только в .NET 4.5 появился Task.FromResult)
  2. Сам объект Task представляет из себя “задачу”, которая может быть как еще не запущена, так и уже выполняться.  Поэтому работа с разными Task_ами будет осуществляться по-разному.
  3. Task не является immutable объектом, метод ContinueWith изменяет саму задачу.
  4. Функция ContinueWith срабатывает при любом завершении задачи, в том числе отмене или ошибке. Необходимо каждый раз указывать флаги.
  5. Исключения внутри задач собираются в один объект, что затрудняет структурированную их обработку.
  6. Метод ContinueWith принимает Func<Task, T>, а не Func<Task, Task>. Это значит что единственный способ связать две задачи – создать вложенную (nested), что может привести к переполнению стека при завершении цепочки вложенных задач.

Последний пункт особенно актуален.  Даже был сделан extension-метод Unwrap, который позволяет писать код в виде:

var task = SomeFuncReturningTask(...)
                .ContinueWith(t => SomeOtherFuncReturningTask(t.Result))
                .Unwrap().ContinueWith(...)
                .Unwrap().ContinueWith(...);

Тут надо обратить внимание на пункт 4, вызовы методов станут еще более многословными.

Async\await в C# 5 частично решает проблему, позволяя писать в виде:

var r1 = await SomeFuncReturningTask();
var r2 = await SomeOtherFuncReturningTask();

Но есть еще другие варианты использования где даже async\await не помогает, например рекурсивные функции.

Также для .NET существует библиотека Rx, которая создавалась теоретиками (которые Linq придумали). Причем основная часть Rx была получена составлением монады двойственной к IEnumerable.

Для сравнения “теоретического” и “практического” подхода попробую написать функцию вычисления высоты дерева.

Само дерево:

class Tree<T>
{
    public Tree()
    {
        this.Children = new List<Tree<T>>();
    }

    public T Data { get; set; }
    public List<Tree<T>> Children { get; private set; }
}

Код тестов очень простой:

var t = new Tree<int> { Data = 0 };
for (int i = 1; i < 10 * 1000; i++)
{
    t = new Tree<int>
    {
        Data = i,
        Children = { t }
    };
}

Console.WriteLine(HeightObservable(t, x => x.Children).Wait());
Console.WriteLine(HeightTask(t, x => x.Children).Result);

Высота дерева специально выбрана большой – 10000, чтобы проверить насколько эффективно библиотека работает со стеком.

Функция вычисления высоты на Rx:

static IObservable<int> HeightObservable<T>(T element, Func<T, IEnumerable<T>> childSelector)
{
    return childSelector(element)
            .ToObservable()
            .SelectMany(e => HeightObservable(e, childSelector))
            .Aggregate(0, Math.Max)
            .Select(x => x + 1)
            .ObserveOn(Scheduler.ThreadPool);
}

Последний ObserveOn нужен чтобы не возникало stack overflow. Rx стремится как можно меньше использовать concurrency внутри себя и по-умолчанию большинство вызовов “продолжений”(continuation) выполняется синхронно, что приводит к переполнению стека в рекурсивном вызове.
Если использовать Rx v2, то можно без ObserveOn обойтись.

Теперь на Task\C# 5. Попытка №1

static async Task<int> HeightTask<T>(T element, Func<T, IEnumerable<T>> childSelector)
{
    var max = 0;
    foreach (var child in childSelector(element))
    {
        var v = await HeightTask(child, childSelector);
        if (v > max)
        {
            max = v;
        }
    }
    return await Task.FromResult(max + 1);
}

Падает со stack overflow . Await вычисляет аргумент и получает awaiter у результата. Таким образом идет полный обход всего дерева в первом же await. Кроме этого вычисление высот поддеревьев идет по-очереди. Код не распараллелен.

Чтобы распараллелить код нужна функция IEnumerable<Task<T>> –> Task<IEnumerable<T>>, которая параллельно выполняет задачи и собирает результат в одну последовательность. Такая функция называется ForkJoin.

Первое что пришло в голову написать:

static Task<IEnumerable<T>> ForkJoin<T>(IEnumerable<Task<T>> tasks)
{
    var result = new List<T>();
    //...
    foreach (var task in tasks)
    {
        task.ContinueWith(t => result.Add(t.Result) /*...*/);
    }
    //...
}

Такой код не работает. Падает со stackoverflow на foreach, потому что при получении первого элемента пытается синхронно обойти все дерево.

Нужно написать функцию, которая асинхронно обходит IEnumerable

static Task<int> EnumerateWithTask<T>(IEnumerable<Task<T>> tasks, Action<Task<T>> continuation)
{
    var tcs = new TaskCompletionSource<int>();
    var enumerator = tasks.GetEnumerator();
    Action recursive = null;
    var count = 0;

    recursive = () =>
        {
            Task.Factory
                .StartNew<bool>(enumerator.MoveNext)
                .ContinueWith(t =>
                    {
                        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;
}

В этой функции вместо итерации используется рекурсия. Так как рекурсия вызывается внутри продолжения Task, то переполнения стека не получается. Функция возвращает Task, которая считает число элементов после окончания обхода последовательности.

Теперь можно написать и ForkJoin:

static Task<IEnumerable<T>> ForkJoin<T>(IEnumerable<Task<T>> tasks)
{
    var result = new List<T>();
    var tcs = new TaskCompletionSource<IEnumerable<T>>();

    Task<int> countTask = null;
    var completedCount = 0;
    var isCompleted = false;

    countTask = EnumerateWithTask(tasks, t =>
            {
                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) && completedCount == countTask.Result)
                    {
                        tcs.TrySetResult(result.AsReadOnly());
                        isCompleted = true;
                    }
                }
            });

    countTask.ContinueWith(t =>
        {
            if (!isCompleted)
            {
                if (completedCount == t.Result)
                {
                    tcs.TrySetResult(result.AsReadOnly());
                    isCompleted = true;
                }
            }
        });
    return tcs.Task;
}

Когда писал этот код получил разрыв мозга. Боюсь даже представить что будет если придется такой код читать. Скорее всего в этом коде есть ошибки и он не обрабатывает отмену. Так что для production придется помучаться еще сильнее.

Ну и наконец сам код расчета высоты дерева на Task_ах, попытка №2:

static Task<int> HeightTask<T>(T element, Func<T, IEnumerable<T>> childSelector)
{
    return ForkJoin(childSelector(element).Select(e => HeightTask(e, childSelector)))
            .ContinueWith(t => t.Result.Aggregate(0, Math.Max, x => x + 1));
}

Замеры показывают что Rx проигрывает по времени выполнения Task_ам примерно в 2 раза (на Rx v2). Это при том что реально асинхронных операций нет. Реальная асинхронность сделает разницу незаметной. Сложность кода Rx на порядок (в 10 раз) меньше, чем Tasks.

Новомодные фишки типа async\await не помогли в этой задаче вообще никак. Причиной этому служит то, что  Rx спроектирован на основе монад (как и async в F#). Монады позволяют комбинировать вычисления с некоторым контекстом (в данном случае с “продолжениями”) с помощью небольшого набора функций. На базе этих функций можно построить много других.

Async\await – не более чем переписывание кода в компиляторе, никаких монадических конструкций само по себе оно не создает, а TPL не предоставляет средства для композиции. Вот и получаются проблемы на любом коде, сложнее того что в примерах.

Как ни странно но сейчас не существует библиотеки, которая содержит код, решающий проблемы. Максимум некоторые расширения можно найти в примерах на MSDN.

Заключение

Крайне не рекомендую использовать Tasks для высокоуровневого кода. Rx подходит на эту роль гораздо лучше.



Жириновский о SharePoint

jirinovskiy-on-sharepoint

via @avishnyakov



SharePoint + Javascript + Visual Studio = Love

Для тех кто еще не знаком с клиентской объектной моделью SharePoint, настоятельно рекомендую ознакомиться с ней. Client OM доступна как в C# (.NET и Silverlight), так и в Javascript.

.NET разработчики обычно не любят javascript, считают его убогим языком, приводящим к куче ошибок. И они в чем-то правы… Основной причиной называют динамическую типизацию и интерпретируемость javascript. На самом деле разработчика нужна не типизация и компиляция, а проверка кода до запуска и подсказки по коду. Обе возможности доступны в Visual Studio с расширениями.

Проверка кода до запуска

Для этого в галерее расширений visual studio есть два расширения:

  1. JSLint – встроенный в VS инструмент проверки кода javascript, который можно найти по адресу http://www.jslint.com/
  2. Javascript parser – расширение, которое парсит js код на лету (читай показывает ошибки) и предоставляет навигацию по коду.

Эти два расширения позволяют отловить наиболее частые ошибки в коде.

image

Кроме того рекомендую установить jscript editor extensions чтобы редактор был больше похож на редактор кода C#.

Подсказки по коду aka Intellisence

Эта возможность доступна начиная с Visual Studio 2008, но о ней до сих пор знает катастрофически малая часть разработчиков.  Чтобы intellicence для js заработал в visual studio, необходимо visual studio дать подсказку откуда брать файлы.

Если вы пишите javascript код в html\aspx\ascx\materpage файлах, то vs автоматически подтягивает все .js файлы из тегов script и серверного контрола ScriptManager. Далее VS парсит файлы и пытается построить подсказку по коду. Если же нужно обновить подсказку, то можно нажать ctrl+shift+j.

Если же код пишется в .js файле, то файлы, которые надо анализировать для построения подсказки надо указать в специальном теге:

///<reference path="c:\scripts\jquery-1.5.1.js"/>

Имя файла можно указать любое, в том числе url с какого-либо CDN.

В VS11 можно не указывать в каждом файле теги script или reference, а задать в настройках самой студии. Но в VS 2010 такой возможности нет.

Также visual studio поддерживает документирующие комментарии для JS, как и для .NET языков. Подробнее описано по ссылке: http://msdn.microsoft.com/en-us/library/bb385682.aspx

Подсказка по коду позволяет набирать код на javascript быстрее и без ошибок в именах классов и функций.

Причем здесь SharePoint?

Клиентская объектная модель SharePoint для JS вполне успешно понимается студией. Это помогает разработчикам писать код работы с ClientOM на js.

Чтобы все заработало нужно подключить следующие файлы:

///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\MicrosoftAjax.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\1033\init.debug.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\1033\core.debug.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.Core.debug.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.Runtime.debug.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.debug.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\CUI.debug.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.Ribbon.debug.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.Exp.debug.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.UI.Rte.debug.js"/>

Причем порядок файлов важен, но какой правильный – никто сказать не может, надо проверять.

И все, дальше можно писать код на js, а студия будет подсказывать имена всех классов и функций клиентской объектной модели.

image

image

image

Весь код:

///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\MicrosoftAjax.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\1033\init.debug.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\1033\core.debug.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.Core.debug.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.Runtime.debug.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.debug.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\CUI.debug.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.Ribbon.debug.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.Exp.debug.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.UI.Rte.debug.js"/>

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());
    });
}
Развертывание скриптов

Самый простой способ сделать ваш скрипт доступным в SharePoint – с помощью sandboxed решения и элемента CustomAction.

Для этого надо создать новый проект, поместить в него один элемент –модуль, поместить в него файл скрипта  и написать в манифесте следующий xml:

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Module Name="Module1">
  <File Path="Module1\JScript1.js" Url="Module1/JScript1.js" />
</Module>

  <CustomAction Location="ScriptLink"
                ScriptSrc="~site/Module1/JScript1.js"/>
</Elements>

Далее можно нажать f5, написать прямо в строке адреса вызов функции и посмотреть результат. При этом работает интерактивная отладка в Visual Studio.

image

Скрипты в веб-частях

Если вы собираетесь писать скрипты в веб-частях, то вы можете написать кучу тегов script, аналогично reference, но это приведет к загрузке всех файлов при размещении веб-части на форме, что нежелательно.

Есть очень хороший способ с этим бороться:

image

Директивы условной компиляции приведут к тому что теги script не попадут в результрующую разметку, а VisualStudio игнорирует эти директивы и нормально показывает intellisence.

Заключение

Несмотря на то что javascript очень недружественный к разработчику язык, хорошие инструменты позволяют писать на нем с тем же уровнем комфорта, как на .NET языках.



Создание Silverlight приложений для SharePoint. Часть 2.

Полгода прошло с тех пор как я опубликовал первую часть статьи. Как раз полгода назад произошло смещение приоритетов веб-разработки Microsoft  в сторону HTML5, и злые языки начали пророчить скорую смерть Silverlight. Тем не менее вышла пятая версия SL и, скорее всего, будет еще и шестая. Тем не менее фокус больше смещается в сторону HTML\JS, и по слухам в следующей версии SharePoint будет гораздо больше javascript и гораздо меньше SL.

Поэтому данная статья будет больше обзорной.

Варианты развертывания Silverlight приложений

Как обычно их три:

  1. В sandbox решении в виртуальную файловую систему sharepoint.
  2. В fulltrust решении в физическую файловую систему.
  3. Приложение на внешнем сервере, с использованием fluid application model.
Размещение Silverlight на портале.

Самый часто используемый способ – веб-часть silverlight. Её можно найти в категории Media. Также возможно создание своих веб-частей, которые отображают silverlight приложение, чтобы передать дополнительные параметры и\или сделать fallback. Но лучше такие веб-части не создавать, а воспользоваться расширением для visual studio. А в следующей версии visual studio такая веб-часть доступна “изкаропки”.

Еще один вариант – создание field control на silverlight. Пример можно посмотреть в статье на msdn.

Демо-приложение

Для демонстрации всех способов развертывания напишу простое приложение, при запуске оно будет выводить Title узла, на котором запущено приложение.

Сначала надо добавить в проект SL сборки Microsoft.SharePoint.Client.Silverlight  и Microsoft.SharePoint.Client.Silverlight.Runtime. Их можно найти в папке {SharePointRoot}\TEMPLATE\LAYOUTS\ClientBin.

Теперь можно написать немного кода:

var ctx = ClientContext.Current;
var web = ctx.Web;
ctx.Load(web, w => w.Title);

ctx.ExecuteQueryAsync(
(o, args) => //success
{
    Dispatcher.BeginInvoke(() =>
    {
        tbTitle.Text = web.Title;
    });

},
(o, args) => //failure
{
    Dispatcher.BeginInvoke(() =>
        {
            MessageBox.Show(args.Exception.ToString());
        });
});

 

Для тех кто не знаком с клиентской объектной моделью SharePoint краткий ликбез. Точка входа в клиентскую объектную модель – класс ClientContext, в SL приложении можно получить “текущий контекст”, если приложение развернуто в SharePoint.

Все объекты в клиентской объектной модели являются”обещаниями” (promise), то есть на момент получения они не содержат значений, а только общение что значения когда-нибудь там будут.  Чтобы загрузить свойства объекта с сервера надо выполнить метод ClientContext.Load. В методе Load можно указать с помощью лямбда-выражений какие свойства загружать.

Другая особенность клиентской объектной модели заключается в в том что команды не выполняются сразу, а складываются в очередь и отправляются на сервер при вызове ExecuteQuery\ExecuteQueryAsync. Так как в SL нельзя блокировать поток UI, то воспользуемся асинхронным вариантом.

Третья особенность заключается в том что коллбеки завершения асинхронного вызова клиентской объектной модели не маршалятся в поток UI (сколько непонятных слов, сам в шоке). Поэтому надо вызывать Dispatcher.BeginInvoke чтобы поменять что-либо в UI или вывести Message Box.

На первый взгляд код выглядит сложным, но при некоторой сноровке пишется “на автомате”.

Развертывание на уровне фермы

Это самый простой и прямолинейный вариант. Он вполне может быть оправдан если вы используете Silverlight приложение как field control для кастомного поля, в остальных случаях я бы не рекомендовал.

Чтобы развернуть приложение в физическую файловую систему надо создать  один пустой элемент (SPI), даже не нужен манифест (Element.xml) и фича, их можно просто удалить.

image

Далее в необходимо добавить в элемент project output из проекта silverlight, обязательно указав Deployment Type=TemplateFile

image

После этого надо добавить элемент в package (не в фичу).

Последний шаг перед развертыванием – включить отладку Silverlight.

image

После этого можно жать F5 на проекте SharePoint, добавлять на страницу веб-часть Silverlight и ставить точки останова в проекте Silverlight.

image

Развертывание в sandbox

Для развертывания решения в sandbox надо выполнить те же шаги, что и для fulltrust решения, но вместо пустого элемента создать модуль, указать Deployment Type=ElementFile в project output references и использовать фичу для развертывания.

image

image

И все, в манифест модуля автоматически попадет элемент с xap файлом.

Далее как обычно: F5, веб-часть, отладка.

Развертывание на внешнем хосте

Для начала надо включить поддержку внешних приложений в SharePoint. Это можно сделать в powershell небольшим скриптом.

$cs = [Microsoft.SharePoint.Administration.SPWebService]::ContentService
$cs.ExternalApplicationSettings.Enabled = $true
$cs.Update()

Я не проверял включены ли внешние приложения в office 365, очень надеюсь что включены. Иначе я зря это все пишу.

Чтобы разместить внешнее приложение в SharePoint надо передать веб-части silverlight манифест приложения (applicationXml). При добавлении веб-части появится запрос манифеста (после включения внешних приложений).

<?xml version="1.0" encoding="utf-8" ?>
<applicationParts xmlns='http://schemas.microsoft.com/sharepoint/2009/fluidapp'>
  <applicationPart>
    <metaData>
      <applicationId>F0F0E6C1-5B42-4277-9EFF-777F1330BCD8</applicationId>
      <applicationUrl>http://localhost:21351/HostWebSite/ClientBin/SlApp.xap</applicationUrl>
      <principal>contoso\sp-app</principal>
      <sharepointRequestHandlerUrl>/HostWebSite/sp.ashx</sharepointRequestHandlerUrl>
    </metaData>
    <data>
      <webPartProperties>
        <property name='Title'>SlApp</property>
        <property name='Height'>400px</property>
        <property name='Width'>300px</property>
        <property name='MinRuntimeVersion'>3.0</property>
      </webPartProperties>
      <customProperties>
      </customProperties>
    </data>
  </applicationPart>
</applicationParts>

 

До того как создавать веб-часть необходимо дать доступ пользователю, который указан в разделе principal, с помощью метода AddApplicationPrincipal. Лучше всего это делать в коде активации фичи.

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
    var web = properties.Feature.Parent as SPWeb;
    var principal = web.AddApplicationPrincipal("contoso\\sp-app", 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("contoso\\sp-app", true, false);
    web.RoleAssignments.Remove(principal);
}

После деплоя решения и активации фичи можно попробовать добавить на страницу веб-часть Silverlight и указать application xml. Приложение отобразится но сразу будет падать с ошибкой.

Небольшой ликбез про кросс-доменные вызовы в silverlight. По умолчанию приложение на SL может обращаться только к тому домену, откуда оно загружено. В модели external application в sharepoint приложение silverlight находится в другом домене и ему кросс-доменные вызовы запрещены.

Иногда может помочь файл clientaccesspolicy.xml, который разрешает silvelight обращаться за пределы домена, но там надо явно прописать куда можно обращаться или указать что обращаться можно везде. Но это небезопасно, так как  в случае sharepoint приложение на Silverlight будет передавать пароль по сети для аутентификации.

Поэтому сделана такая система. В манифесте приложения указывается куда silverlight приложение будет отправлять запросы при вызове клиентской объектной модели (элемент sharepointRequestHandlerUrl). Обработчик по указанному url будет определять какой сайт sharepoint обращается и передавать логин и пароль именно для этого сайта.

Чтобы создать этот обработчик надо в сайте, где хостится silverlight создать обычный ashx-хендлер.

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("sp-app", "P@ssw0rd", "contoso");
            forwarder.ProcessRequest();
        }
    }
 
    public bool IsReusable {
        get {
            return false;
        }
    }
}

 

Класс RequestForwarder находится во “взрослой” клиентской объектной модели.

После добавления хендлера приложение перестает выдавать ошибку и работает нормально.

Заключение

Как видите довольно много возможностей развертывания  приложений Silverlight в SharePoint. При этом само приложение не поменялось ни в одном сценарии.