Wyobraźcie sobie sytuacje gdy jesteście poproszeni przez szefa by za każdym razem gdy ankieta zostanie wypełniona (skończona) został wysłany mail do działu HR z informacją, że nowa/zmodyfikowana ankieta została zapisana na listę.
Wydawało by się, że to nic prostszego :) Dodanie workflow lub SPItemEventReceiverItemAdded/ItemUpdated i wykonanie odpowiedniej operacji. Nic bardziej mylnego :)
Workflowy na ankietach nie działają i działać nie będą. KB opisujący problem znajdziecie tutaj. Jednak jak wiadomo MS czasami pisze różne rzeczy (Vista szybsza i mniej wymagająca od XP ;) itp.). Więc po pewnych analizach .NET Reflector doszedłem do następujących wniosków.
Podczas startu workflow, SharePoint wywołuje następującą metodę: Microsoft.SharePoint.Workflow.SPWinOEWSSService.MakeActivation, która, wywołuje konstruktor klasy: Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties, który zaś próbuje pobrać kolumnę na liście o ID ustawionym w zmiennej statycznej: Microsoft.SharePoint.SPBuiltInFieldId.GUID. Tej kolumny na liście Survey nie ma, dlatego też workflow na Survey nigdy nie zadziała, i nie sądzę, żeby patche do SharePoint to zmieniły.
Zaś ItemAdded i ItemUpdated będą działać bardzo dobrze na ankietach, które nie posiadają Page Separators. Czyli cała ankieta mieści się na jednej stronie, i od razu widzimy przycisk Finish :)
Niestety świat nie jest piękny – przynajmniej aż tak ;) Wystarczy, że pojawi się Page Separators a już przy akcji Next zostanie wywołane zdarzenie ItemAdded, każde następne Next oraz Finishspowodują wywołanie ItemUpdated. Wyobrażacie sobie minę działu HR który z jednej ankiety otrzymuje 20 maili? ;)
A więc do dzieła :) Każda ankieta zawiera kolumnę Completed:
Która określa stan wypełnienia ankiety. Jeżeli kolumna przyjmuje wartość Yes oznacza, że ankieta została w wypełniona i zapisana. Status No pojawia się wtedy gdy ankieta zostanie zapisana ale nie skończona, lub jest w trakcie wypełniania (pamiętacie o Page Separators i ItemAdded?):
Jakbym teraz klikną na Cancel element zostałby usunięty z listy zaś następny przyjąłby wartość #62:
By było jeszcze ciekawiej, ankieta zawiera pole Completed, która pryzmuje wartości 1 i 255 :) i skąd biedny człowiek ma wiedzieć co one oznaczają? Problem polega na tym, że odczyt tylko tej kolumny nie załatwi sprawy, ba nawet Microsoft z niej nie korzysta :) Skąd wiem? Otwórzcie SharePoint Designera i stronę AllItems.aspx dla ankiety. Następnie prawym przyciskiem myszy kliknijcie na Web Part odpowiedzialny z wyświetlenie listy i wybierzcie opcję:
Następnie w kodzie wyszukajcie tekstu: 255, powinniście zobaczyć coś takiego:
I dochodzi nam kolejna kolumna _Level :) i obiecuje wam, ze nie ostatnia :) ale reszta będzie opisana w kodzie.
Prosta spraw zamieniła się w dość skomplikowaną :) ale nie ma rzeczy nie możliwych ;) Więc zacznijmy tworzyć SPItemEventReceiver z… metodami ItemAdded i ItemUpdating (mała różnica ale znacząca!) :)
Szkielet naszej klasy powinien wyglądać mniej więcej tak:
public class SurveyItemEventReceiver : SPItemEventReceiver
{
[SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods")]
[SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)]
public override void ItemAdded(SPItemEventProperties properties)
{
}
[SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods")]
[SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)]
public override void ItemUpdating(SPItemEventProperties properties)
{
}
}
Zacznijmy implementację od ItemAdded:
[SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods")]
[SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)]
public override void ItemAdded(SPItemEventProperties properties)
{
// ItemAdded jest nam potrzebne tylko i wylacznie wtedy kiedy ankieta sklada sie
// z jednej strony, w przeciwnym wypadku, wszystkie operacje bedziemy wykonywac
// na ItemUpdated.
// Pobranie poziomu - zgodnie z tym co twierdzi SharePoint oraz ShrePoint Designer
// to wlasnie ta wartosc okresla czy ankieta zostala skonczona czy tez nie.
// Wartosc _Level jest nazwa wewnetrzna (Internal Name), jako ze dzialamy na
// asynchronicznym zdarzeniu, wiemy juz ze element zostal dodany do listy
// przez co properties.ListItem bedzie zawieralo jego wlasnosci.
int level = Convert.ToInt32(properties.ListItem["Level"]);
// Tak jak wam obiecalem, kolejne pole :) Jednakze ma ono kluczowe znaczenie.
// To pole pojawia sie tylko i wylacznie wtedy, gdy nastepuje ostatnie
// zapisanie/zaktualizowanie ankiety. To znaczy, ze jezeli mamy przycisk
// Next to pole sie nie pojawi! zas jezeli mamy przycisk Finish, to pole
// sie pojawi! :) To co nas tak naprawde interesuje to, to czy ono istnieje
// jezeli nie istnieje to znaczy ze to nie koniec :)
object ctype = properties.AfterProperties["ContentType"];
// Teraz kluczowy element. Wiemy juz ze 255 oznacza nie skonczona ankieta. Wiemy
// takze ze dzialamy w asynchronicznym zdarzeniu po fizycznym dodaniu elementu do listy.
// Czyli level juz ulegl zmianie na 1 jezeli naprawde zakonczylismy dodwanie elementu.
// W tym wypadku ctype jest jedynie wetylem bezpieczenstwa - po tym co widzialem w
// SharePoint moge sie spodziewac wszystkiego, wiec wole sie ubezpieczyc.
if (level != 255 && ctype != null)
{
// A tutaj slemy e-mail
SPUtility.SendEmail(
properties.OpenWeb(),
false,
false,
"[email protected]",
"Nowa ankieta wgrana przez " + properties.UserLoginName,
"Zapraszamy do obejrzenia"
);
}
}
Komentarze w kodzie powinny wszystko wyjaśnić, jeżeli nie to zapraszam do kontaktu za pomocą komentarzy.
Dobrze, mamy połowę roboty z głowy, teraz pora zająć się ItemUpdating. Z nim mamy dwa problemy, po pierwsze ItemAdded działa wtedy i tylko wtedy gdy od razu mamy przycisk Finish, czyli obsługujemy tylko jeden przypadek, zaś w ItemUpdating mamy dwa przypadki, ankieta nie została jeszcze w ogóle dodana i ankieta została dodana i teraz jest aktualizowana.
Implementacja ItemUpdating powinna wyglądać następująco:
[SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods")]
[SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)]
public override void ItemUpdating(SPItemEventProperties properties)
{
// ItemUpdating jest nam potrzebne w kazdym miejscu! oprocz kiedy nastepuje
// przypadek ItemAdded - czyli niednostronnicowa ankieta.
// Pobranie poziomu - zgodnie z tym co twierdzi SharePoint oraz ShrePoint Designer
// to wlasnie ta wartosc okresla czy ankieta zostala skonczona czy tez nie.
// Wartosc _Level jest nazwa wewnetrzna (Internal Name), jako ze dzialamy na
// synchronicznym zdarzeniu ItemUpdating, wiemy juz ze element zostal dodany do listy
// przez co properties.ListItem bedzie zawieralo jego wlasnosci.
int level = Convert.ToInt32(properties.ListItem["Level"]);
// Tak jak wam obiecalem, kolejne pole :) Jednakze ma ono kluczowe znaczenie.
// To pole pojawia sie tylko i wylacznie wtedy, gdy nastepuje ostatnie
// zapisanie/zaktualizowanie ankiety. To znaczy, ze jezeli mamy przycisk
// Next to pole sie nie pojawi! zas jezeli mamy przycisk Finish, to pole
// sie pojawi! :) To co nas tak naprawde interesuje to, to czy ono istnieje
// jezeli nie istnieje to znaczy ze to nie koniec :)
object ctype = properties.AfterProperties["ContentType"];
// Jezeli aktualnie nie bylismy na ostatniej stronie (ctype)
// oraz wciaz nie skonczylismy ankiety (level) to kontynuujmy
// dalej jej wypelnianie
if (level == 255 && ctype == null)
{
// Kontynuacja wypelniania
return;
}
// Jezeli aktualnie jestesmy na ostatnie stronie (ctype)
// oraz aktualnie zapisana ankieta nie jest jescze skonczona (level)
// to oznacza ze wypelnilismy nowa ankiete. Dzieje sie tak, gdyz
// level zawiera wartosc przed aktualizacja/zapisem ankiety na liscie
// czyli dopiero po operacji ItemUpdating przyjmi on wartosc 1.
else if (level == 255 && ctype != null)
{
// A tutaj slemy e-mail
SPUtility.SendEmail(
properties.OpenWeb(),
false,
false,
"[email protected]",
"Nowa ankieta wgrana przez " + properties.UserLoginName,
"Zapraszamy do obejrzenia"
);
}
// Jezeli ankieta zostala juz zapisana i teraz ja edytujemy (level)
// i wciaz nie jestesmy na ostatniej stronie (ctype) to kontynuujmy
// wypelnianie ankiety
else if (level != 255 && ctype == null)
{
// Kontynuacja wypelniania
return;
}
// Jezeli ankieta zostala juz zapisana i teraz ja edytujemy (level)
// i jestesmy wlasnie na ostatniej stronie (ctype) to wyslijmy maila
// do HR!
else if (level != 255 && ctype != null)
{
// A tutaj slemy e-mail
SPUtility.SendEmail(
properties.OpenWeb(),
false,
false,
"[email protected]",
"Zaktualizowana ankieta wgrana przez " + properties.UserLoginName,
"Zapraszamy do obejrzenia"
);
}
}
Dla wyjaśnienia. W tym wypadku działamy na zdarzeniu synchronicznym. Zdarzenie to wywoływane jest tuż przed zapisaniem zmian elementu na listę. Dlatego wartości w ListItem są stare (te przed aktualizacją, zaś wartości w AfterProperties są wartościami zmienionymi – i tylko wyłącznie zmienionymi, to znaczy tymi które uległy zmianie). Nie odwołuje się do BeforePropertiesze względu na to, że nie byłbym wstanie wziąć poziomu z jego własności, dostał bym wartość null.
I to wszystko! Teraz należy tylko stworzyć feature.xml i wgrać nasz SPItemEventReceiver. Możemy to też zrobić z poziomu kodu. Jednakże zakładam, że wiecie jak to zrobić :) Jak nie, zapraszam do komentarzy, postaram się odpowiedzieć na wszystkie pytania a jeżeli będą się one powatarzały to stworzę wpis na blogu wyjaśniający wątpliwości.














