Kilka razy zdarzyło mi się opublikować przydatne metody/hacki w SharePointcie na jego API. Było już o rejestrowaniu Event Receivers (niestety kod zawierał błędy), czy też pobieraniu RedirectUrl. Dziś udostępnię wszystkie metody z jakich korzystam, nie ma ich dużo jednak są to te metody z których najczęściej korzystałem.

Operacje na liście

Cały kod opisany poniżej należy zawrzeć pomiędzy:

using System;
using System.Collections.Generic;
using Microsoft.SharePoint;

namespace LineByLine.Common.Extensions
{
    public static class SPListEx
    {
        // tutja wkleic kod
    }
}

Dodanie elementu

Podczas dyskusji przy wrapperze, w komentarzach padło rozwiązanie szybkiego dodawania elementów do listy, które nie zważa na liczbę istniejących elementów na liście. Chodzi o to, że przy normalnym dodawaniu trzeba odwołać się do kolekcji elementów, która od razu jest pobierana co trwa, im więcej elementów tym dłużej to trwa. W którymś momencie nie da się z tego korzystać :) Nie wiem komu należą się creditsy za ten fragment kodu – w lutym 2009 roku Rob Garrett opublikował dosłownie taki sam fragment kodu jaki Marek w komentarzach u mnie. Zakładam więc ze credits nalezą się Robowi. Poniżej extension method dla efektywnego dodawania elementu:

public static SPListItem AddItem(this SPList list)
{
    var query = new SPQuery {Query = "0"};

    return list.GetItems(query).Add();
}

Rejestrowanie i od-rejestrowanie Event Receivers

Swojego czasu opublikowałem to rozwiązanie, niestety z błędami (o czym przekonałem się podczas jednego projektu, sorki!), poniżej poprawna wersja:

public static void Register<T>(this SPList list, params SPEventReceiverType[] receiverTypes)
    where T : SPItemEventReceiver
{
    if (receiverTypes == null || receiverTypes.Length == 0)
    {
        return;
    }

    Type type = typeof(T);
    string assemblyFullName = type.Assembly.FullName;
    string typeFullName = type.FullName;

    foreach (var receiverType in receiverTypes)
    {
        var def = list.EventReceivers.Add();
        def.Class = typeFullName;
        def.Assembly = assemblyFullName;
        def.Type = receiverType;
        def.Name = string.Format("{0} ({1})", typeFullName, receiverType);

        def.Update();
    }

    list.Update();
}

public static void Unregister<T>(this SPList list, params SPEventReceiverType[] receiverTypes)
    where T : SPItemEventReceiver
{
    if (receiverTypes == null || receiverTypes.Length == 0)
    {
        return;
    }

    var toBeDeleted = new List<Guid>();

    Type type = typeof(T);
    string assemblyFullName = type.Assembly.FullName;
    string typeFullName = type.FullName;

    foreach (SPEventReceiverDefinition eventReceiverDef in list.EventReceivers)
    {
        foreach (var receiverType in receiverTypes)
        {
            if (eventReceiverDef.Type == receiverType)
            {
                if (eventReceiverDef.Assembly == assemblyFullName && eventReceiverDef.Class == typeFullName)
                    toBeDeleted.Add(eventReceiverDef.Id);
            }
        }
    }

    foreach (var id in toBeDeleted)
    {
        list.EventReceivers[id].Delete();
    }

    list.Update();
}

Operacje na elemencie

Taki mały hack, który opisałem swojego czasu tutaj. Chodzi o aktualizacje elementu na liście bez wywoływania event receivers:

using System;
using System.Reflection;
using Microsoft.SharePoint;

namespace LineByLine.Common.Extensions
{
    public static class SPListItemEx
    {
        public static bool PerformSystemUpdate(this SPListItem list)
        {
            // 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
                    );

                var parameters = new object[] {/* System Update */ true, false, Guid.Empty, false, false, false, false, false, /* Suppres After Events */ true };

                object obj = method.Invoke(list, parameters);
            }
            catch
            {
                return false;
            }

            return true;
        }
    }
}

Operacje na SPWeb

Podczas tworzenia pełnych rozwiązań przeważnie korzystamy z prostych nazw list albo takich by lista miała prosty URL na przykład NaszeTaski zamist Taski Firmowe XYZ. Czasami także po udostępnieniu rozwiązania firmie trzeciej, pewien menadżer stwierdza, że jednak taka nazwa listy mu nie odpowiada i ją zmienia. Jeżeli nasz kod opierał się na nazwie to mamy przewalone, a większości przypadków widziałem, że ludzie lubią się opierać na nazwach i potem dodawać do dokumentacji „zakaz zmiany nazw list inaczej gwarancja zostanie zerwana” czy coś w tym stylu :) Zamiast tego, można założyć, że opieramy się na wewnętrznej nazwie listy (tej nadanej podczas jej tworzenia) a nie na nazwie wyświetlanej. Pewnie nie raz zauważyliście, że nazwa listy Moje zadania utworzone przez was ma tak naprawdę URL w stylu Moje%20Zadania i jak zmienicie nazwę listy to URL pozostaje taki sam. Skoro znacie nazwę nadaną podczas utworzenia listy (bez znaków specjalnych jak spacja itp., prosty string) to możecie wykorzystać poniższą metodę by zdobyć ID listy:

using System;
using System.Reflection;
using Microsoft.SharePoint;

namespace LineByLine.Common.Extensions
{
    public static class SPWebEx
    {
        public static Guid GetListIdByInternalName(this SPWeb web, string internalName)
        {
            SPListCollection lists = web.Lists;

            try
            {
                Type type = typeof(SPListCollection);
                MethodInfo method = type.GetMethod("ItemByInternalName",
                                                   BindingFlags.Instance | BindingFlags.NonPublic,
                                                   null,
                                                   new Type[] { typeof(string), typeof(bool) },
                                                   null
                    );

                var parameters = new object[] { internalName, false };

                object obj = method.Invoke(lists, parameters);

                return ((SPList)obj).ID;
            }
            catch
            {
                return Guid.Empty;
            }
        }
    }
}

Rozszerzenie SPUtils

Niestety, SPUtils ma same statyczne metody i pisać Extension Methods dla nich się nie da :) Dlatego też zamiast extenshion method, stworzyłem statyczną klasę ze statyczną własnością, która umożliwia pobranie adresy URL strony na którą w SharePoint powinno nastąpić przekierowanie. Całość opisałem swojego czasu tutaj, a poniżej zamieszam lekko zmodyfikowaną metodę:

using System;
using System.Reflection;
using System.Web;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;

namespace LineByLine.Common.Extensions
{
    public static class SPUtilityEx
    {
        public static string RedirectUrl
        {
            get 
            {
                Type type = typeof(SPUtility);
                MethodInfo method = type.GetMethod("GetRedirectUrl",
                    BindingFlags.Static | BindingFlags.NonPublic,
                    null,
                    new Type[] { typeof(HttpRequest), typeof(SPList) },
                    null
                );

                object obj = method.Invoke(null, new object[] { HttpContext.Current.Request, SPContext.Current.List });

                if (obj == null)
                    return string.Empty;
                else
                    return obj.ToString();
            }
        }
    }
}

Koniec

Macie jakieś swoje metody, z których często korzystacie a które można pod extension methods podciągnąć? Może warto by było zebrać takie metody i po prostu jej udostępnić. Wiem, że jest już SP2010, ale dużo ludzi wciąż programuje w 2007, więc znajdą się i tacy, którzy będą chcieli z nich skorzystać.

2 KOMENTARZE

  1. Rzecz której mi brakuje to pobieranie wartość pola po Name, bez względu na to czy jest to InternalName, StaticName czy DisplayName. Z tego co pamiętam nie ma metody do pobierania po którymś z wyżej wymienionych Name i trzeba się zastanawiać. Metoda zwracała by wartość pola niezależnie od tego czy podało się internal, static czy display name jako parametr, ewentualnie rzucała wyjątek w przydaniu gdy pole nie istnieje.

  2. Tutaj http://bit.ly/9PVIoQ opisalem swojego czasu dlaczego nie powinno sie pobierac pola po Display i InternalName, osobiscie odwoluje sie tylko po GUID, ale tak jakbys chcial to SPListItem[Int|Guid|String] dziala tak jak ty chcesz. Dla stringa sprawdza najpierw po InternalName, jak nie znajduje to bierze po DisplayName. Zas static name to przewaznie przy nie przypisanych polach InternalName. Dodatkowo, ustawiajac StaticName ustawiasz InternalName gdyz InternalName jest z zalozenia ReadOnly.

    Ogolnie zasada przy StaticName jest taka ze raz ustawione raczej nie powinno ulegac zmianie. Nie spotkalem sie tez z sytuacja bym w ogole musial sie do tego pola odwolywac – a ty sie spotkales?

    tak na wszelki wypadek metoda zwracajaca pole po stringu:

    internal SPField GetField(string strName, bool bThrowException)
    {
    this.EnsureFieldsSafeArray(false);
    SPField fieldByInternalName = this.GetFieldByInternalName(strName, false);
    if (fieldByInternalName == null)
    {
    fieldByInternalName = this.GetFieldByDisplayName(strName, false);
    }
    if (((fieldByInternalName == null) && (SPContext.Current != null)) && SPContext.Current.IsDesignTime)
    {
    fieldByInternalName = SPContext.Current.ContentType.Fields.GetFieldByInternalName(strName, false);
    if (fieldByInternalName == null)
    {
    fieldByInternalName = SPContext.Current.ContentType.Fields.GetFieldByDisplayName(strName, false);
    }
    }
    if ((fieldByInternalName == null) && bThrowException)
    {
    throw new ArgumentException();
    }
    return fieldByInternalName;
    }

Comments are closed.