Nie ma to jak dobry projekt w SharePoint, człowiek odkrywa ukryte funkcjonalności z dnia na dzień :) Ale do rzeczy, większość z was zna zapewne metodę Update() oraz SystemUpdate() na SPListItem. Różnica pomiędzy nimi jest dość prosta:
- Update() aktualizuje metadane, zmieniając informacje na temat daty modyfikacji i zwiększając wersję elementu jeżeli wersjonowanie jest włączone;
- SystemUpdate() aktualizuje metadane, nie zmieniając informacji na temat daty modyfikacji czy też wersji elementu.
Obie metody mają swoje plusy i minusy. Osobiście używam SystemUpdate() wszędzie tam gdzie serwer wykonuje operacje na elementach, zaś operacje użytkownika pozostawiam metodzie Update(). Wszystko było piękne aż do wczoraj (3 nad ranem), kiedy to nagle potrzebowałem trzeciego rodzaju metody aktualizującej element. Już tłumaczę o co chodzi :) W jednym z projektów, generuje faktury w PDF na podstawie wartości z kilku list. W tym celu wykorzystuje obiekt SPQuery na kilku listach z których następnie tworze odpowiednio z preparowane źródło danych. PDFy mogą być utworzone tylko i wyłącznie podczas zatwierdzania faktury, zaś zatwierdzanie modyfikuje kilka pól elementu tak by ustawić mu dane typu: Data Zatwierdzenia Faktury, czy Osoba Zatwierdzająca Fakturę, Status Faktury itp.
Wszystko działało wyśmienicie aż do wprowadzenia security na elementach. Mianowicie w zależności od pewnych pól w fakturze, podczas jej zatwierdzania osoby które są przypisane do wartości tych pól muszą mieć ustawione prawa dostępu do elementu, jak i prawa autora elementu muszą zostać zmienione. Przy tworzeniu rozwiązania nawet myślałem o tym by zrobić dwie listy i kopiować elementy by tylko w security nie wchodzić lub skończyć Security Manager dla List View – o którym mam nadzieję niebawem uda mi się napisać coś więcej ;) – dla osób nie korzystających z WSS, w WSS nie ma Audiences więc też nie ma jak przypisać View do pewnej grupy osób, albo View są nie widoczne, albo wszystkie View są widoczne, więc zarządzanie wyświetlaniem elementów poprzez View odpadło. Rozwiązanie na jakie się zdecydowałem to ustawianie bezpośrednio uprawnień na elementach podczas aktualizacji elementu.
Na początku chciałem to zrobić na zdarzenie ItemUpdating, jednak nie można wtedy aktualizować elementu, który właśnie jest aktualizowany – chyba, że się przepisze wszystkie pola i stworzy nowy element na liście na podstawie danych przekazanych do zdarzenia. Jest to czasochłonne, a przy tym można stracić pewne dane historyczne, ale da się to tak zrobić. Ja poszedłem na łatwiznę i zrobiłem ustawianie uprawnień na ItemUpdated.
Sytuacja która zaszła wyglądała tak:
- Użytkownik zatwierdza fakturę;
- Ja z kodu modyfikuje odpowiednie pola wywołuje Update na elemencie;
- Wywołuje generowanie PDFów i przypisanie ich do elementu;
Wszystko było by świetnie gdyby nie to, że ItemUpdated jest zdarzeniem asynchronicznym wywoływanym po aktualizacji elementu. W tym momencie mój punkt 3, czasami próbował zaktualizować element z SPContext.Current.ListItem który został zaktualizowany przez moje zdarzenie ItemUpdated – dodanie uprawnień wymaga aktualizacji elementu.
Przez to wszystko, PDFy się nie generowały – zwracały błąd Save Conflict – , zaś na stronie z której użytkownik zatwierdzał fakturę SPContext.Current.ListItem zwracał informacje, że je posiada PDFy. Dzieje się tak iż w danym kontekście element ma już te PDFy ma je tak długo do póki nie wyjdziemy ze strony lub nie wywołamy metody Update. Więc użytkownikowi pokazywał się błąd:Nie udało się wygenerować faktury w postaci PDF, na stronie pokazywały się linki do pobrania PDF, zaś ich kliknięcie zwracało błąd: Brak PDF do pobranie. Czyli maksymalny mis-masz. Dopiero odświeżenie całkowite strony pokazywało odpowiedni przycisk do generowania ponownego PDFów.
Zacząłem więc kombinować jak to zrobić by Update i ustawianie uprawnień działało i by PDFy się generowały. Update podczas zatwierdzania jednak był niezbędny ze względu na to iż do generowania PDFów wykorzystuje SPQuery, które zwraca elementy zapisane na listach. Więc bez Update zwrócił by niepoprawne dane. Zaś stworzenie PDFów wymaga zrobienia Update na elemencie gdyż inaczej załączniki nie zostaną zapisane. Można powiedzieć, że zapędziłem się w kozi róg :)
Jednak tym razem po tym jak przekląłem, wypiłem parę kaw oraz wypaliłem pół paczki, odpaliłem .NET Reflectora by przyjrzeć się jak MS wywołuje metodę Update i SystemUpdate() bo może da się coś zrobić by jednak te zdarzenia asynchroniczne nie były wywoływane kiedy nie chcę by były, zaś zostały wtedy kiedy o to SharePointa poproszę :)
W gwoli ścisłości, podczas pisania kodu w SPItemEventReceiver macie dostępne dwie metody:
- DisableEventFiring() – wyłącza wywoływanie zdarzeń przez co w ItemUpdate można wywołać Update na danym elemencie i zdarzenie nie zostanie ponownie wywołane :)
- EnableEventFiring() – włącza wywoływanie zdarzeń.
Te dwie metody są dostępne w instancji SPItemEventReceiver i nie ma do nich dostępu zewnątrz.
Pomyślałem, że skoro coś takiego jest w zdarzeniach, to też musi być dostępne dla elementu listy. W końcu MS czasami w jakiś sposób aktualizuje te elementy, nie zmieniając w nich żadnych informacji oraz nie wywołując żadnych naszych zdarzeń.
No to przypatrzyłem się temu co robi Update i SystemUpdate():
Mała różnica prawda? Jedynie pierwszy parametr, który oznacza czy to jest update systemowy czy też nie :)
To co przykuło moją uwagę to ostatni parametr o nazwie suppresAfterEvents. Już sama nazwa mi się bardzo spodobała, jednak w celu upewnienia się, odpaliłem Google.com i pierwszy link na jaki natrafiłem to był opis własnośći SPImportSettings.SupressAfterEvents. Tutaj wklejam opis który wytłumaczy co przykuło moją uwagę:
Disabling the firing and handling of After events can have a big impact on the performance of import operations. However, when performance is not a concern, you might want to disable handling After events to avoid potential problems resulting from event handlers not running their business logic.
Trafiłem w sedno! Więc zabrałem się do napisania prostej metody, PerformSystemUpdate():
private bool PerformSystemUpdate()
{
// ostatni parametr - suppres after events :D
// this.UpdateInternal(false, false, Guid.Empty, false, false, false, false, false, false);
try
{
Type type = typeof(SPListItem);
MethodInfo method = type.GetMethod("UpdateInternal",
BindingFlags.Instance | BindingFlags.NonPublic,
null,
new Type[]
{
typeof(bool),
typeof(bool),
typeof(Guid),
typeof(bool),
typeof(bool),
typeof(bool),
typeof(bool),
typeof(bool),
typeof(bool)
},
null
);
object[] parameters = new object[]
{
// System Update
true,
false,
Guid.Empty,
false,
false,
false,
false,
false,
// Suppres After Events
true
};
object obj = method.Invoke(SPContext.Current.ListItem, parameters);
}
catch
{
return false;
}
return true;
}
I boom (bomb(track)) zaczęło działać! :)
Dzięki tej metodzie mogę spokojnie zatwierdzić element, dodać załączniki i wywołać ustawianie uprawnień.
Sweet and easy :)
Mam nadzieję, że Tip się komuś przyda :)