Standardowym scenariuszem deploy'u webpartów jest umieszczenie plików dll zawierających kod webpartów w GACu lub binie, stworzenie manifestu i umieszczenie go w galerii składników webpart oraz dodanie odpowiedniego wpisu safecontrol do pliku web.config web aplikacji, w której chcemy skorzystać z naszego rozwiązania.
Problem pojawia się w momencie, gdy ten prosty wydawałoby się scenariusz nie zadziała.
Dosyć częstym komunikatem, który się pojawiaja gdy coś poszło nie tak jest:
Błąd składnika Web Part: Nie można wyświetlić lub zaimportować składnika Web Part lub formantu formularza sieci Web na tej stronie. Nie można znaleźć typu lub nie został on zarejestrowany jako bezpieczny.
Pokaż szczegóły błędu
Ukryj szczegóły dotyczące błędu
[UnsafeControlException: Nie można wyświetlić lub zaimportować składnika Web Part lub formantu formularza sieci Web na tej stronie. Nie można znaleźć typu lub nie został on zarejestrowany jako bezpieczny.]
at Microsoft.SharePoint.ApplicationRuntime.SafeControls.GetTypeFromGuid(Guid guid)
at Microsoft.SharePoint.WebPartPages.SPWebPartManager.CreateWebPartsFromRowSetData(Boolean onlyInitializeClosedWebParts)
Ten błąd informuje nas w zasadzie tylko o tym, że coś poszło nie tak i sami musimy stwierdzić co.
Jedną z możliwości jest, że nie został dodany wpis safecontrol do pliku web.config. Możemy to zweryfikować przeglądając plik web.config w katalogu głównym web aplikacji, w której chcemy skorzystać ze składnika webpart. Standardowym katalogiem dla aplikacji działających na porcie 80-tym jest katalog C:\inetpub\wwwroot\wss\VirtualDirectories\80.
Jeśli wpis safecontrol jest poprawny drugą możliwością jest niezaładowanie się pliku dll. Najlepiej wtedy odnaleźć manifest wadliwego składnika webpart w galerii składników webpart i zobaczyć, czy w GACu lub binie znajduje się plik dll, który jest wymieniony w manifeście. Jeśli go brakuje musimy zwerfyikować dlaczego go tam nie. W przeciwnym przypadku możemy dokonać recyclingu puli aplikacji lub reset serwera IIS. Gdy to się nie powiedzie możemy skorzystać z narzędzia Fuslog Viewer. Więcej informacji o tym narzędziu znajduje się tutaj
Co jednak w przypadku, gdy webpart wyrzuca nieobsłużony wyjątek i cała strona przestaje działać? Z pomocą przychodzi nam Webpart Maintenance Page. Jest to bardzo przydatna sztuczka, o której wielu ludzi zapomina. Do adresu strony, która przestała działać, można dodać parametr ?contents=1 i wtedy pojawi się strona, na której możemy zarządzać składnikami webpart. Ukrywając po kolei składniki webpart możemy zdiagnozować, który z nich powoduje błąd.
W czasie pisania aplikacji po Sharepointa bardzo często zachodzi potrzeba odwołania się do obiektów SPWeb i SPSite.Niestety obiekty te, pomimo że wydają się obiektami zarządzanymi, w rzeczywistości takie nie są. Zawierają one bowiem referencję do obiektu SPRequest, który z kolei zawiera referencję do obiektów COM odpowiedzialnych za komunikację z bazą danych. Pamięć zaalokowana przez tego typu obiekty nie zawsze jest zwalniana automatycznie, dlatego bezpośrednio po ich użyciu musimy sami zatroszczyć się o jej zwolnienie. Jeśli o to nie zadbamy możemy mieć do czynienia z tzw. wyciekiem pamięci, spadkiem wydajności naszej aplikacji, a w pewnych sytuacjach nawet z przepełnieniem stosu i zatrzymaniem się Sharepointa.
Obydwa obiekty: SPWeb i SPSite implementują interfejs IDisposable, aby je zwolnić po zakończeniu pracy z nimi wywołujemy więc na nich metodę Dispose. Możemy także skorzystać z wyrażenia using, które wywołuje Dispose automatycznie:
using (SPSite tmpSite = newSPSite("http://sharepoint"))
{
using (SPWeb tmpWeb = tmpSite.OpenWeb())
{
return tmpWeb.Name;
}
}
Kod ten jest równoważny poniższemu:
SPSite tmpSite = null;
SPWeb tmpWeb = null;
try
{
tmpSite = newSPSite("http://sharepoint");
tmpWeb = tmpSite.OpenWeb();
return tmpWeb.Name;
}
catch(Exception e)
{
//Jeżeli nie implementujemy obsługi błędów
// można użyć prostszej składni z „using”
}
finally
{
if (tmpSite!= null)
tmpSite.Dispose();
if (tmpWeb!= null)
tmpWeb.Dispose();
}
Kod z pierwszego przykładu można jeszcze uprościć:
using (SPSite tmpSite = newSPSite("http://sharepoint"))
using (SPWeb tmpWeb = tmpSite.OpenWeb())
{
return tmpWeb.Name;
}
Z upraszczaniem nie należy jednak przesadzać:
using (SPWeb tmpWeb = newSPSite("http://sharepoint").OpenWeb())
{
return tmpWeb.Name;
}
//niestety SPSite zostanie w pamięci
Jak widać na powyższym kodzie jeżeli tworzymy nowy obiekt, należy pamiętać o jego zwolnieniu. Odnosi się to również do iterowania po liście webów, gdzie podczas odwołania do elementów kolekcji są tworzone ich instancje.
using (SPSite tmpSite = newSPSite("http://sharepoint"))
{
foreach (SPWebtmpWeb intmpSite.AllWebs)
{
//tmpWeb nie zostanie zwolniony
}
}
W powyższym kodzie zwolniliśmy obiekt tmpSite, niestety każdy z webów z SPWebCollecion został w pamięci. Poprawnie powinno być tak:
using (SPSite tmpSite = newSPSite("http://sharepoint"))
{
foreach (SPWebtmpWeb intmpSite.AllWebs)
{
try
{
//jeśli chcemy można jeszcze dodać obsługę błędów
}
finally
{
if(tmpWeb!= null)
innerWeb.Dispose();
}
}
}
Zanim rozpędzimy się ze zwalnianiem jeszcze przykład do rozważenia:
using (SPSite tmpSite = SPContext.Current.Site){}
//using wywołujeDispose automatycznie, w tym wypadku raczej tego nie chcemy wykonać
Pamiętajmy:
Jeśli tworzymy nowe obiekty (za pomocą konstruktora lub funkcji (np. OpenWeb()), lub iterujemy po kolekcji należy pamiętać o zwolnieniu obiektów.
Jeśli obiekty pobierane są z kontekstu to ze zwalnianiem trzeba uważać (i pamiętać że using wywołuje Dispose automatycznie po wyjściu z sekcji kodu!).
Jeżeli używamy obiektów implementujących IDosposable należy unikać tworzenia ich w jednym wątku, a zwalniania w innym. Dlatego obiekty typu SPWeb i SPSite nie powinny być przekazywane między wątkami, ani też deklarowane jako statyczne.
O ile odwołanie do kolekcji typu SPWebCollection i SPSiteCollection nie powoduje konieczności zwalniania obiektów (kolekcja nie używa kodu niezarządzanego, tylko jej elementy), to jednak odwołania do elementów kolekcji (za pomocą [ ]), oraz dodawanie do niej elementów tworzy ich instancje które należy zwolnić.
Mając do czynienia z obiektami Sharepointa impelemtującymi interfejs IDisposable należy pamiętać że może zajść konieczność ich zwolnienia. Jak zwykle zdarzają się jednak wyjątki, np. obiektów SPSite.RootWeb,SPWeb.ParentWeb nie musimy zwalniać ręcznie.
Przy tworzeniu aplikacji dla SharePoint'a może zdarzyć się potrzeba ustawienia zawartości kontrolki PeopleEditor przy pomocy Javascript'u. Poniższy przykład wyjaśnia jak to zrobić.
<script type="text/javascript">
function SetPickerValue(pickerid, key, dispval) { var xml = '<Entities Append="False" Error="" Separator=";" MaxHeight="3">'; xml = xml + PreparePickerEntityXml(key, dispval); xml = xml + '</Entities>';
Najlepszą metodą jest użycie funkcji EntityEditorCallback. Jest ona dostępna wraz z innymi wbudowanymi funkcjami dostarczonymi z SharePointem (zarówno w WSS 3.0, MOSS2007 jak i SP2010). Ta sama funkcja jest używana przez kontrolkę PeopleEditor'a do ustawiania swojej własnej wartości.
Najważniejsze są dwa pierwsze parametry funkcji - XML zawierający dane oraz identyfikator kontrolki (ClientID) której zawartość ma być ustawiona.
Powyższy kod prezentuje funkcję SetPickerValue zawierającą zarówno generację odpowieniego XML'a jak i wywołanie funkcji EntityEditorCallback.
I przekazuje go do metody EntityEditorCallback.
Przy podawaniu nazwy konta domenowego należy zwrócić uwagę na znak \ (backslash). W języku Javascript jest to znak specjalny i nie można go używać bezpośrednio w parametrach tekstowych. Należy użyć \\ aby tekst był zinterpretowany jak znak backslash.
Aby ustawić zawartość PeopleEditora na kilka wartości na raz, trzeba zmodyfikować funkcję SetPickerValue tak, by generowała kilka węzłów <Entity /> w tworzonym XMLu. Każdy taki węzeł odpowiada jednej wartości w kontrolce.