Swojego czasu opisałem implementację Field Mappera dla SharePoint wraz z wykorzystaniem jego w wzorcu repozytorium. Jednakże pominąłem jedną istotną kwestią o jaką ostatnio dostałem zapytanie:

Jak wykorzystać repozytorium z filed mapper w event receiver w SharePoint?

W przykładach pokazywałem iż możemy za pomocą SharePointServiceLocator uzyskać implementację danego interfejsu repozytorium i następnie już operować na danej implementacji.

_activityTypeRepository = SharePointServiceLocator.Current.GetInstance<IActivityTypeRepository>();

ActivityType at = _activityTypeRepository.GetById(1);
at.Name = "jola";

_activityTypeRepository.Update(at);

Jednakże sytuacja ciutkę inaczej wygląda z Event Receivers w których nie ma dostępu do obiektu HttpContext.Current, przez co także do SPContext.Current.

Jest to częściowo nie prawda gdyż przy synchronicznych zdarzeniach możemy dostać się do obiektu HttpContext ale jedynie w konstruktorze klasy. Problem jest trochę bardziej zabawny gdyż nie przy wszystkich synchronicznych zdarzeniach ten kontekst będzie… o dziwo, z tego co pamiętam problem taki istniał w ItemDeleting (do sprawdzenia, nie dam sobie teraz ręki uciąć:)).

Dlatego też cały SharePointServiceLocator nie będzie nam poprawnie działał w Event Receivers. Obejściem problemu jest proste, ale przez to też nie możemy korzystać z service locator. Mianowicie w moich repozytoriach istnieje przeciążenie konstruktora przejmującego jako parametr aktualny obiekt SPWeb.

public class LogRepository : BaseEntityRepository<Log>, ILogRepository
{
    public LogRepository()
    {
        this.Initialize(Constants.ListLogsIdConfigKey);
        base.ListItemFieldMapper.AddMapping(LogFields.Id, log => log.Id);
        base.ListItemFieldMapper.AddMapping(LogFields.UniqueId, log => log.UniqueId);
        base.ListItemFieldMapper.AddMapping(LogFields.Title, log => log.Title);
        base.ListItemFieldMapper.AddMapping(LogFields.LogDate, log => log.Date);
        base.ListItemFieldMapper.AddMapping(LogFields.LogDesc, log => log.Message);
        base.ListItemFieldMapper.AddMapping(LogFields.LogFrom, log => log.FromIp);
        base.ListItemFieldMapper.AddMapping(LogFields.LogItem, log => log.Item);
        base.ListItemFieldMapper.AddMapping(LogFields.LogList, log => log.List);
        base.ListItemFieldMapper.AddMapping(LogFields.LogUser, log => log.ByUser);
        base.ListItemFieldMapper.AddMapping(LogFields.LogType, log => log.LogType);
    }

    public LogRepository(SPWeb web)
    {
        this.Initialize(Constants.ListLogsIdConfigKey, web);
        base.ListItemFieldMapper.AddMapping(LogFields.Id, log => log.Id);
        base.ListItemFieldMapper.AddMapping(LogFields.UniqueId, log => log.UniqueId);
        base.ListItemFieldMapper.AddMapping(LogFields.Title, log => log.Title);
        base.ListItemFieldMapper.AddMapping(LogFields.LogDate, log => log.Date);
        base.ListItemFieldMapper.AddMapping(LogFields.LogDesc, log => log.Message);
        base.ListItemFieldMapper.AddMapping(LogFields.LogFrom, log => log.FromIp);
        base.ListItemFieldMapper.AddMapping(LogFields.LogItem, log => log.Item);
        base.ListItemFieldMapper.AddMapping(LogFields.LogList, log => log.List);
        base.ListItemFieldMapper.AddMapping(LogFields.LogUser, log => log.ByUser);
        base.ListItemFieldMapper.AddMapping(LogFields.LogType, log => log.LogType);
    }
    
    // CUT
}

Metoda Initialize pochodzi z klasy bazowej:

protected virtual void Initialize(string configKey)
{
    var hierarchicalConfig = SharePointServiceLocator.Current.GetInstance<IConfigManager>();
    ListId = hierarchicalConfig.GetFromPropertyBag<Guid>(configKey, SPContext.Current.Web);

    List = SPContext.Current.Web.Lists[ListId];
    Logger = SharePointServiceLocator.Current.GetInstance<ILogger>();
}

protected virtual void Initialize(string configKey, SPWeb web)
{
    var hierarchicalConfig = SharePointServiceLocator.Current.GetInstance<IConfigManager>();
    Logger = SharePointServiceLocator.Current.GetInstance<ILogger>();

    Logger.TraceToDeveloper("hierarchicalConfig is null: " + (hierarchicalConfig == null).ToString());

    ListId = hierarchicalConfig.GetFromPropertyBag<Guid>(configKey, web);
    List = web.Lists[ListId];
    
}

Uwaga. Pewne odwołoania do SharePointServiceLocator są możliwe nawet bez SPWeb/SPSite.

Wystarczy więc iż w naszym Event Receiver dostaniemy się do obiektu SPWeb i następnie przekażemy go do konstruktora konkretnej implementacji repozytorium. I to jest IMO najlepsze wyjście. Nie polegałbym na konstruktorze naszego Event Receiver.

Czyli kod mniej więcej będzie wyglądał tak:

public override void ItemDeleting(SPItemEventProperties properties)
{
    try
    {
        SPSite site = new SPSite(properties.SiteId);
        SPWeb web = site.OpenWeb();
        ILogRepository = new LogRepository(web);
        // CUT
    }
    // CUT
}

Oczywiście chętni mogą naprawić implementację servie locator od MS tak by brał on pod uwagę obiekt SPWeb/SPSite co by dało możliwość korzystania z niego w Event Receivers.

PS.: wpis ku pamięci oraz dla tych, którzy będą kiedyś szukać rozwiązania tego problemu.

2 KOMENTARZE

  1. Witaj,

    Post na podstawie mojego maila. Jak miło :)

    Twoją implementację mappera wykorzystałem w nowym rozwiązaniu i muszę powiedzieć, że jest bardzo spoko. Zdecydowanie bardziej przypadł mi do gustu niż beznadziejny spmetal…

    Jeszcze raz dzięki za pomoc.

Comments are closed.