Tag Archive for 'SharePoint'

To yield or not to yield

Das yield Keyword ist ein in C# häufig unterschätztes Keyword. Die MSDN – bzw. die C# Spezifikation – sagt dazu:

Def: Wird in einem Iterator-Block verwendet, um für das Enumerationsobjekt einen Wert zu liefern oder um das Ende der Iteration zu signalisieren.
Der Ausdruck wird ausgewertet und als ein Wert an das Enumerationsobjekt zurückgegeben. Der expression muss implizit in den yield-Typ des Iterators konvertierbar sein.
Die yield-Anweisung kann nur innerhalb eines iterator-Blocks stehen, der als Rumpf einer Methode, eines Operators oder eines Accessors verwendet wird. Der Rumpf solcher Methoden, Operatoren oder Accessoren unterliegt folgenden Einschränkungen:

- Unsichere Blöcke werden nicht zugelassen.
- Parameter für die Methode, den Operator oder den Accessor dürfen nicht ref oder out sein.

Eine yield-Anweisung darf nicht in einer anonymen Methode stehen. Weitere Informationen finden Sie unter Anonyme Methoden (C#-Programmierhandbuch).
Bei Verwendung mit expression darf eine yield return-Anweisung nicht in einem catch-Block oder einem try-Block stehen, der mehr als eine catch-Klausel enthält.

Wie sieht das jetzt in der Praxis aus?
Das Beispiel in der MSDN ist jetzt nicht besonders praxistauglich, daher hier mal eine andere Verwendungsweise:

class Program
    {
        static void Main(string[] args)
        {
            string searchedCRMId = "de7ea510-8e55-de11-a210-0017084f5232";
            Console.WriteLine("Starting to search for user with CRM_ID '{0}'...", searchedCRMId);
            foreach (UserProfile item in GetProfiles("http://your_ssp_host/"))
            {
                string currentID = Convert.ToString(item["CRMUserID"].Value);
                if (string.IsNullOrEmpty(currentID))
                    continue;

if (currentID.Equals(searchedCRMId, StringComparison.CurrentCultureIgnoreCase))
                {
                    Console.WriteLine("Found searched user!\nAccountname: {0}", item[PropertyConstants.AccountName].Value);
                    break;
                }
            }
            Console.WriteLine("Done");
            Console.ReadLine();
        }

static IEnumerable<UserProfile> GetProfiles(string SharedServiceProviderUrl)
        {
            SPWebApplication webapp = SPWebApplication.Lookup(new Uri(SharedServiceProviderUrl));
            UserProfileManager upm = new UserProfileManager(ServerContext.GetContext(webapp));
            foreach (UserProfile profile in upm)
            {
                yield return profile;
            }
        }
    }

Diese ConsoleApplication iteriert über alle vorhandenen UserProfile und vergleicht eine CustomProperty mit einem Suchwert. In der GetProfiles() Methode wird allerdings mittels “yield return profile” das aktuelle Profil aus der Auflistung zurückgegeben und die übergeordnete foreach-Schleife weiter bearbeitet. Das spart zum einen Performance und zum anderen Speicher.
Zu beachten ist dabei allerdings, das bei folgender Verwendung immer das erste Item “verschwindet”:

IEnumerable<UserProfile> profileList = GetProfiles("http://your_ssp_host/");
foreach (UserProfile item in profileList)
{
 string currentID = Convert.ToString(item["CRMUserID"].Value);
        if (string.IsNullOrEmpty(currentID))
         continue;

if (currentID.Equals(searchedCRMId, StringComparison.CurrentCultureIgnoreCase))
        {
         Console.WriteLine("Found searched user!\nAccountname: {0}", item[PropertyConstants.AccountName].Value);
                break;
        }
}

Die Ursache ist recht einfach:

Typische Implementation Implementation mit yield
1. Funktionsaufruf 1.Funktionsaufruf
2.Funktion wird ausgeführt und gibt eine Liste zurück 2.Aufrufer fordert ein Item an
3.Aufrufer arbeitet mit der Liste 3.Nächstes Item aus der Auflistung wird zurückgegeben
  4.Sprung zu 2. solange wie der Aufrufer kein Item mehr haben will (break im Caller) oder keine Items mehr vorhanden sind

Was man letztendlich verwendet ist natürlich immer von der Anforderung abhängig. :)

SharePoint: Alle Sharedservice Provider ermitteln

Ab und an kommt mal wieder die Anforderung einen Timerjob an die Webapplication des Sharedserviceproviders anzuhängen. Bei einem SSP ist das auf den ersten Blick auch kein Problem: Entweder die URL als Konfigurationseinstellung verdraten oder über eine Settingspage in der Zentraladministration konfigurierbar machen. Spätestens wenn der Kunde aber mehr als einen SSP im Einsatz hat, und der Timerjob an alle SSPs angehängt werden soll, steht man vor einem Problem: Entweder immer wieder Einstellungen hinzufügen oder man überlegt sich eine generelle Lösung.

Jeder SSP verfügt über eine Administrationsoberfläche die mit einer bestimmten Sitedefinition erzeugt wurde. Diese findet man im 12er Hive unter TEMPLATE\SiteTemplates\OSRV. Wie kann man sich nun eine Liste aller vorhandenen SSPs erzeugen? Zum Beispiel mit folgender Funktion:

        public static List GetSharedServiceProviders()
        {
            List result = new List();
            SPFarm farm = SPFarm.Local;
            SPWebService service =
                farm.Services.GetValue();
            foreach (SPWebApplication webApp in
                service.WebApplications)
            {
                if (webApp.Sites.Count == 0)
                    continue;
                using (SPSite rootsite = webApp.Sites[0])
                {
                    if (rootsite.AllWebs.Count == 0)
                        continue;
                    using (SPWeb rootweb = rootsite.RootWeb)
                    {
                        if (rootweb.WebTemplateId == 40)
                            result.Add(rootsite.Url);
                    }
                }
            }
            return result;
        }

Eigentlich ist es simpel: Man holt sich aus der Farm alle WebApplications, öffnet die Rootsite und prüft mit welchem SiteTemplate das Rootweb angelegt wurde. Ist die TemplateID gleich der TemplateID der SSP Sitedefinition, dann ist es ein Sharedservice Provider.