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:

gutek_moss_101

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?):

gutek_moss_102

Jakbym teraz klikną na Cancel element zostałby usunięty z listy zaś następny przyjąłby wartość #62:

 gutek_moss_105

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ę:

gutek_moss_103

Następnie w kodzie wyszukajcie tekstu: 255, powinniście zobaczyć coś takiego:

gutek_moss_104

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.