Dziś troszkę przydatnych wieści na temat stosowania workflowów w najnowszym Sharepoint 2010. A ściślej rzecz biorąc na temat stosowania ich w kodzie (używanie ich w designerze jest bowiem trywialne ;)). Generalnie do samego pomysłu i idei “WorkFlows” jestem dość pozytywnie nastawiony, tak do wykonania tego pomysłu przez Microsoft - już niezbyt.

Pomysł bowiem okazuje się dobry. Zróbmy dodatkowe przepływy sterowania na naszej platformie, które zajmą się ważnymi, ale nie naglącymi procesami niezależnie od działania całej platformy. Jeśli mamy zaawansowaną aplikację internetową, która musi wykonywać jakieś bardzo zaawansowane akcje w różnych sytuacjach, to chcielibyśmy by akcje te były wykonywane bez zakłócania dostępności aplikacji w sieci. Jak dla mnie – pomysł jest genialny i całkiem przydatny. M$ jednak zrobił z tego bezwartościową zabawkę, w której “zmusza” programistę do zabawy z wymyślonymi graficznymi klockami nijak mającymi się zapewne do standardów. Ponadto klocki te, które z założenia miały obrazować problem i proces biznesowy lepiej od kodu, nie są w tym tak dobre. A ponadto bardzo skutecznie zaciemniają kod własną budową ;]

No ale dość płakania na dziś. Kiedyś napiszę artykuł dedykowany WF (jak już trochę poznam wroga ;)). Teraz zaś postarajmy się rozwiązać pewien problem z istniejącym i poprawnie już skonstruowanym workflowem. Powiedzmy, że nasz workflow integruje dane pomiędzy listami sharepointowymi, a zewnętrzną bazą danych. Tzn. jeśli w sharepointowej liście dodamy element do listy, to ten element powinien zostać uwzględniony także w bazie danych.

Do tego celu doskonale nadają się workflowy (lub EventReceivery, które są notabene bardzo sympatyczne :) i o których nie omieszkam zamieścić niewielkiego artykułu) Problem jaki się pojawia w naszym przypadku to sytuacja, w której będziemy robili sobie migrację danych z bazy do Sharepointa. W dużym skrócie chcemy na chwilę wyłączyć konkretne workflowy, tak by nie zniszczyły bazy podczas, gdy będzie ona migrowana, a potem, gdy proces migracji się zakończy, wyłączone workflowy ponownie podpiąć pod odpowiednie listy. Proste? W teorii tak, w praktyce niestety okaże się to niezbyt trywialne.

Dissociating workflows

No więc przystąpimy do usunięcia workflowów w sposób analogiczny do tego jak je stworzyliśmy, czyli postaramy się usunąć je stamtąd, gdzie zostały umieszczone, czyli z konkretnego ContentType. Może to wyglądać następująco: (zasadniczą część przeniosłem do osobnej funkcji, bo się jeszcze przyda później :))

//nasza zasadnicza funkcja
private void RemoveWFAssociationFromCT(SPContentType CT, string WFTemplateName)
{
    foreach (SPWorkflowAssociation WFAssociation in CT.WorkflowAssociations)
    {
        if (WFAssociation.BaseTemplate.Name == WFTemplateName)
            CT.WorkflowAssociations.Remove(WFAssociation.Id);
    }
}

//tak jej użyjemy w naszym kodzie:
private void OurGreatFunc(SPContentType CT, SPList list, string WFName)
{
//...
    if (CT.WorkflowAssociations.Count > 0)
        this.RemoveWFAssociationFromCT(CT, WFName);
//...
}

Ok, wygląda całkiem nieźle i chyba nawet o to nam chodzi. Odpalamy i… no nie działa tak jakbyśmy chcieli :( Co się stało? Stało się dokładnie to czego chcieliśmy – workflow został usunięty z ContentType’a, jednak w dalszym ciągu istnieje i ma się dobrze. A co najgorsze jego funkcjonalność jest dokładnie taka sama jak wcześniej. Przy okazji tego punktu należy zrozumieć pewną przykrą specyfikę WF – mianowicie WF po stworzeniu i przypisaniu do ContentType automatycznie (z wyjątkiem, o którym pomówimy za chwilę) zostaje skopiowany do list, w których dany ContentType występuje. Podkreśliłem ‘skopiowany’, bo to nie jest właśnie racjonalne.

Logicznie rzecz biorąc ten WF mógłby być do takich list podpięty (coś a la link), jednak jest robiona jego kopia. Powoduje to taką sytuację, że jeden Workflow może istnieć w kilku różnych miejscach zupełnie niezależnie od siebie). Nie wystarczy zatem usunąć asocjacji do workflowa z ContentType, ale także z ContentType’ów podpiętych do konkretnych list. Ok, teraz wygląda to tak i teraz nawet działa tak jakbyśmy tego chcieli:

private void RemoveWFAssociationFromCT(SPContentType CT, string WFTemplateName)
{
    foreach (SPWorkflowAssociation WFAssociation in CT.WorkflowAssociations)
    {
        if (WFAssociation.BaseTemplate.Name == WFTemplateName)
            CT.WorkflowAssociations.Remove(WFAssociation.Id);
    }
}

private void OurGreatFunc(SPContentType CT, SPList list, string WFName)
{
//...
    if (CT.WorkflowAssociations.Count > 0)
        this.RemoveWFAssociationFromCT(CT, WFName);
    foreach (SPWorkflowAssociation workflowAssociation in list.ContentTypes[CT.Name].WorkflowAssociations)
        if (workflowAssociation.BaseTemplate.Name == WFName)
        {
            list.WorkflowAssociations.Remove(workflowAssociation.Id);
            CT.Update();
            return;
        }
//...
}

Associating Workflows

Teraz mamy kolejną ciekawostkę. Gdy tworzymy naszego site’a zupełnie od zera i tworzymy wszystkie ContentType’y i podpinamy do nich Workflowy to jest fajnie i wszystko działa. Ale gdy je usuniemy i zechcemy podłączyć ponownie, to nie działa to tak samo. Problem jakby jest powiązany z tym, który napotkaliśmy w momencie usuwania WF. Mianowicie, po podłączeniu WF do ContentType, jest on co prawda widoczny w Sharepoint Managerze, jednak nie działa. Tutaj teoretycznie jakbyśmy się przyjrzeli temu co widać w samych ustawieniach workflowa w ContentType, widać fajną opcję pod tytułem “Update all content types that inherit from this type with these workflow settings”. Jeśli byśmy w to kliknęli, to tak naprawdę rozwiązuje to nasz problem. Ustawienia ContentType, którego właśnie stworzyliśmy spadają na ContentType przypięte do list, dzięki temu też nasz workflow ma już listę, na której może sobie działać. W kodzie realizowane jest to przez tę jedną niepozorną linijkę:

CT.UpdateWorkflowAssociationsOnChildren(true, true, true, true);

Jak już wspominałem wcześniej – linijka ta nie jest wymagana przy pierwszym tworzeniu, a dopiero przy kolejnych.

Podsumowanie

Podsumowanie jak zwykle wygląda dość marnie. A raczej sprowadza całego SharePointa do miana niedorobionej i generalnie zje&%^$ej technologii. Mam niemniej nadzieję, że komuś powyższy wpis się przydał lub się przyda, a jeśli ktoś, podobnie jak ja, nie podziela zbyt radości z obcowania z tą technologią, to można poczytać sobie trochę bardziej rzeczowe narzekania na SharePointa tutaj: What are your biggest complaints about Sharepoint?