Codebeispiel: SharePoint-to-LinkedIn-Konnektor

Letzte Änderung: Montag, 22. August 2011

Gilt für: SharePoint Server 2010

Inhalt dieses Artikels
Einführung in die LinkedIn-Integration
Struktur der Lösung
OAuth-Authentifizierung und LinkedIn
Verwenden der LinkedIn-API
Erstellen des Zeitgeberauftrags
Speicherung des SharePoint-Benutzerprofils
Verwalten von Token
Erstellen der Bestätigungsseite
Aktualisieren des LinkedIn-Zeitgeberauftrags
Aktualisieren des Benutzerstatus
Erstellen und Ausführen des Beispiels

In diesem Beispiel wird veranschaulicht, wie Sie soziale Daten von anderen Websites mit sozialen Netzwerken einer MeineWebsite hinzufügen, neue Benutzerprofileigenschaften erstellen und verwenden und neue Zeitgeberaufträge implementieren, die auf der Website für die SharePoint-Zentraladministration verwaltet werden. Im ersten Teil der Lösung muss der Benutzer der SharePoint-Anwendung die Berechtigung erteilen, in seinem Auftrag zu handeln. Für diesen Bestätigungsvorgang wird der OAuth-Authentifizierungsstandard verwendet, der von LinkedIn und vielen anderen Websites für thematische Computerfeatures verwendet wird. Über die Anmeldeseite wird das Token bezogen und gespeichert, das für die Aktualisierung der LinkedIn-Daten durch die Anwendung notwendig ist. Der zweite Teil der Lösung wird als Zeitgeberauftrag implementiert, mit dem Benutzerprofiländerungen abgefragt werden. Für den Zeitgeberauftrag ist eine Verwaltungsseite erforderlich, über die der Zeitgeberauftrag aktiviert und deaktiviert wird.

Beispielcode von:MVP-Mitwirkender Mathew McDermott, Catapult Systems | MVP-Mitwirkender Andrew Connell, Critical Path Training, LLC

Installieren Sie das Codebeispiel auf Ihrem Computer, indem Sie das Microsoft SharePoint 2010 Software Development Kit (SDK) herunterladen oder das Beispiel aus der Code Gallery herunterladen. Wenn Sie das SharePoint 2010 SDK herunterladen, wird das Beispiel im folgenden Speicherort im Dateisystem installiert: C:\Program Files\Microsoft SDKs\SharePoint 2010\Samples\Social Data and User Profiles.

Einführung in die LinkedIn-Integration

Da Unternehmen zunehmend thematische Computerfeatures unternehmensweit nutzen, müssen möglicherweise interne Informationen der Organisation für externe Anbieter von Plattformen für thematische Computerfeatures veröffentlicht werden. Da diese Anbieter größere Zielgruppen erreichen möchten, implementieren viele von ihnen offene APIs, die die Erstellung von Anwendungen ermöglichen, über die auf die Informationen des Benutzers zugegriffen wird.

Mit dieser Lösung wird der LinkedIn-Status eines Microsoft SharePoint Server 2010-Benutzers aktualisiert, wenn der Benutzer am Ende eines Statushinweises den Text #li anfügt. Eine große Herausforderung für die Anwendungsentwicklung ist bei allen Plattformen die sichere Authentifizierung aus der benutzerdefinierten Anwendung gegenüber dem Dienstanbieter. Bei LinkedIn wird für die Authentifizierung und den API-Zugriff der OAuth-Standard verwendet. Hierzu muss der Entwickler den API-Zugriff beantragen und ein API-Schlüsselpaar erhalten, das in der Anwendung verwendet wird. Nach Abschluss dieses Vorgangs kann die Lösung bereitgestellt werden, und Benutzer können sich anmelden, um die Anwendung zu verwenden.

Struktur der Lösung

Im ersten Teil der Lösung muss der Benutzer der SharePoint-Anwendung die Berechtigung erteilen, in seinem Auftrag zu handeln. Für diesen Bestätigungsvorgang wird der OAuth-Authentifizierungsstandard verwendet. Weitere Informationen zum Authentifizierungsvorgang von LinkedIn finden Sie in der Entwicklerdokumentation für die LinkedIn-API. Mit dieser Lösung wird eine Anmeldeseite implementiert, auf der sich die einzelnen Benutzer bei der Anwendung anmelden können. Über die Anmeldeseite (siehe Abbildung 1) wird das Token bezogen und gespeichert, das für die Aktualisierung der LinkedIn-Daten durch die Anwendung notwendig ist.

Abbildung 1. LinkedIn-Bestätigungsseite

LinkedIn-Bestätigungsseite

Der zweite Teil der Lösung ist ein Zeitgeberauftrag, mit dem Benutzerprofiländerungen abgefragt werden. Wenn das LinkedIn-Feld Status Note geändert wird und den Text #li enthält, wird dieses Update an das LinkedIn-Konto des Benutzers gesendet. Für den Zeitgeberauftrag ist eine Verwaltungsseite erforderlich (siehe Abbildung 2), über die der Zeitgeberauftrag aktiviert und deaktiviert wird.

Abbildung 2. Seite zum Verwalten des LinkedIn-Zeitgeberauftrags

Verwalten Sie die LinkedIn-Zeitgeberauftragsseite.

OAuth-Authentifizierung und LinkedIn

Die Details zum OAuth-Standard stehen auf der OAuth-Standardwebsite zur Verfügung. Die Interaktionen, die zum Implementieren der OAuth-Transaktionen erforderlich sind, werden über die Open Source .NET -Bibliothek DotNetOpenAuth ausgeführt. Die API-Details der Lösung werden vom LinkedIn-Toolkit behandelt, einem CodePlex-Projekt, in dem die DotNetOpenAuth-Bibliothek verwendet wird.

Verwenden der LinkedIn-API

Zu Beginn der Entwicklung mit der LinkedIn-API müssen Sie einen API-Schlüssel beantragen und die folgenden grundlegenden Schritte ausführen.

So führen Sie die ersten Schritte mit der LinkedIn-API aus

  1. Fordern Sie beim LinkedIn-Entwicklerportal einen API-Schlüssel an.

  2. Verwenden Sie den Schlüssel in der Anwendung, um Benutzern die Registrierung der Anwendung zu ermöglichen.

  3. Wenn sich die Benutzer angemeldet haben, müssen Sie das Benutzertoken speichern, damit die Statusupdates im Auftrag der Benutzer von der Anwendung ausgeführt werden können.

Erstellen des Zeitgeberauftrags

Weitere Informationen zu den erforderlichen Schritten für die Erstellung eines Zeitgeberauftrags finden Sie unter Creating Custom Timer Jobs in Windows SharePoint Services 3.0. In dieser Lösung werden einige der Implementierungsdetails aus diesem Artikel geändert, um die LinkedIn-Statusupdates auszuführen.

Speicherung des SharePoint-Benutzerprofils

Die erste Herausforderung ist bei dieser Lösung die Ausführung der Authentifizierung gegenüber dem externen System aus SharePoint und die Speicherung der Authentifizierungstoken, ohne dass das Kennwort des Benutzers benötigt wird. Mit Benutzerprofilen wird die Speicherung der Authentifizierungstoken der einzelnen Benutzer vereinfacht. Die in Tabelle 1 gezeigten Felder müssen vom Farmadministrator oder vom FeatureActivated-Ereignisempfänger konfiguriert werden. Das Tokenfeld sollte dem Benutzer nicht angezeigt werden.

Tabelle 1. Felder, die vom Farmadministrator oder vom "FeatureActivated"-Ereignisempfänger konfiguriert werden müssen.

Name

Anzeigename

Zweck

Typ

LI-Token

LinkedIn Secure Token

Speichern der für die Anwendung erforderlichen Authentifizierungstoken

Zeichenfolge

LI-Status

Update LinkedIn Status

Speichern der Einstellung des Benutzers für die Aktualisierung des LinkedIn-Status

Boolesch

Verwalten von Token

Das LinkedIn-Toolkit und die DotNetOpenAuth-Bibliothek erfordern, dass der Entwickler einen Token-Manager erstellt, der die Speicherung und das Abrufen der OAuth-Token behandelt. Diese Tokenverarbeitung wird von der benutzerdefinierten Klasse behandelt, mit der die IConsumerTokenManager-Schnittstelle implementiert wird. Die IConsumerTokenManager-Schnittstelle wird von der DotNetOpenAuth-Bibliothek aufgerufen, um die Speicherung und das Abrufen der für die Benutzerauthentifizierung nach dem OAuth-Standard erforderlichen Token zu behandeln.

Mit der SPTokenManager-Klasse dieser Lösung werden die Token in Benutzerprofileigenschaften gespeichert.

Für den Konstruktor wird das UserProfile-Objekt als Parameter akzeptiert.

public SPTokenManager(UserProfile userProfile, string consumerKey, string consumerSecret)
{      
  if (String.IsNullOrEmpty(consumerKey))
  {
    throw new ArgumentNullException("consumerKey");
  }
    this.userProfile = userProfile;
    this.ConsumerKey = consumerKey;
    this.ConsumerSecret = consumerSecret;
}

Mit der ExpireRequestTokenAndStoreNewAccessToken-Methode wird das zuvor gespeicherte Anforderungstoken gelöscht und ein neues Zugriffstoken gespeichert.

public void ExpireRequestTokenAndStoreNewAccessToken(string consumerKey, string requestToken, string accessToken, string accessTokenSecret)
{
//Clear the previous request token and its secret, and store the new access token and its secret.  
Debug.WriteLine(String.Format("[OAuth] ExpireRequestTokenAndStoreNewRequestToken : {0} {1}", accessToken, accessTokenSecret));
SetUserProfileValue(requestToken, "", TokenType.RequestToken);
SetUserProfileValue(accessToken, accessTokenSecret, TokenType.AccessToken);
}

Mit der StoreNewRequestToken-Methode werden die Authentifizierungsanforderung und -antwort akzeptiert und die erforderlichen Token extrahiert und gespeichert.

public void StoreNewRequestToken(UnauthorizedTokenRequest request, ITokenSecretContainingMessage response)
{
//Store the "request token" and "token secret" for later use
Debug.WriteLine(String.Format("[OAuth] StoreNewRequestToken : {0} {1}", response.Token, response.TokenSecret));
SetUserProfileValue(response.Token, response.TokenSecret, TokenType.RequestToken);
}

Mit der GetTokenSecret-Methode wird der Tokenschlüssel zurückgegeben, der dem angeforderten Token (Anforderungstoken oder Zugriffstoken) entspricht.

public string GetTokenSecret(string token)
{
  //Return the token secret for the request token OR access token that 
  //you are given.   
  return GetUserProfileValue(token);
}

Mit diesen Methoden wird eine von zwei Hilfsfunktionen aufgerufen, mit denen die Benutzerprofilwerte in einem Format gelesen und geschrieben werden, das später vom Zeitgeberauftrag abgerufen werden kann. Für die SetUserProfile-Methode können die Parameter des Tokenwerts, des Tokenschlüssels und des Tokentyps verwendet werden. Das Benutzerprofilfeld wird basierend auf dem Tokentyp aktualisiert, und die Werte werden als Array aus vier Elementen von Zeichenfolgenobjekten gespeichert. Wenn sich der Benutzer erstmals für den Dienst anmeldet, werden die Updates als GET-Anforderung ausgeführt. Von der Anwendung wird dies berücksichtigt, indem das AllowUnsafeUpdates-Attribut des aktuellen Webs anfangs für die Dauer der Transaktion auf true festgelegt wird.

private void SetUserProfileValue(string token, string tokenSecret, TokenType type)
{
  Debug.WriteLine(String.Format("[OAuth] Set the User Profile Value for {0} {1} ", token, tokenSecret));
  UserProfile profile = GetUserProfile();
  bool allowUnsafeUpdates = SPContext.Current.Web.AllowUnsafeUpdates;
  //The tokens are stored as an array of String.
  try
  {
    SPContext.Current.Web.AllowUnsafeUpdates = true;
    //Does out Profile Field exist?
    if (profile[Globals.MSDNLI_TokenField] != null)
    {
      string[] delim = { "|" };
      string[] strTokenArr = new string[4];
      //Does the field contain values?
      if (profile[Globals.MSDNLI_TokenField].Value != null)
      {
        //Get the values.
        strTokenArr = profile[Globals.MSDNLI_TokenField].Value.ToString().Split(delim, StringSplitOptions.None);
      }
      switch (type)
      {
        case TokenType.AccessToken:
          strTokenArr[0] = token;
          strTokenArr[1] = tokenSecret;
          break;
        case TokenType.InvalidToken:
          break;
        case TokenType.RequestToken:
          strTokenArr[2] = token;
          strTokenArr[3] = tokenSecret;
          break;
        default:
          break;
      }
      profile[Globals.MSDNLI_TokenField].Value = String.Format("{0}|{1}|{2}|{3}", strTokenArr);
      profile.Commit();
    }
  }
  catch (Exception ex)
  {
    Debug.WriteLine(String.Format("Failed to load the User Profile. The error was: {0}", ex.Message));
  }
  finally
  {
    SPContext.Current.Web.AllowUnsafeUpdates = allowUnsafeUpdates;
  }
}

Für die GetUserProfile-Methode kann ein Tokenwert verwendet werden, dessen zugeordneter Tokenschlüssel aus dem Speicher zurückgegeben wird.

private string GetUserProfileValue(string token)
{
  Debug.WriteLine("[OAuth] Get the User Profile Value for " + token);
  UserProfile profile = GetUserProfile();
  //Check the LinkedIn properties.
  try
  {
    if (profile[Globals.MSDNLI_TokenField] != null)
    {
      string[] delim = { "|" };
      string[] strTokenArr = new string[4];
      strTokenArr = profile[Globals.MSDNLI_TokenField].Value.ToString().Split(delim, StringSplitOptions.None);
      //Get the values.
      return strTokenArr[Array.IndexOf(strTokenArr, token)+1]; 
    }
    else
    {
      return null;
    }
  }
  catch (Exception ex)
  {
    Debug.WriteLine(String.Format("Failed to load the User Profile. The error was: {0}", ex.Message));
    return null;
  }
}

Erstellen der Bestätigungsseite

Wenn der Benutzer die Bestätigungsseite besucht, wird das UserProfile-Objekt untersucht, das den aktuellen Benutzer darstellt, und der LinkedIn-Authentifizierungsstatus dieses Benutzers wird ermittelt. Wenn das entsprechende Token gefunden wird, wird das zugeordnete LinkedIn-Profil geladen. Wenn das Token nicht gefunden wird, wird die Schaltfläche Opt In aktiviert. Für diese Interaktion werden vom Code der Seite Eigenschaften für die Objekte TokenManager, AccessToken und Authorization implementiert, die zum Erstellen der Transaktionen für LinkedIn erforderlich sind. Der TokenManager ruft die von LinkedIn erhaltenen globalen ApiKeys ab und stellt diese der benutzerdefinierten Anwendung bereit.

Das TokenManager-Objekt wird aus den Werten des Consumerschlüssels und den Werten der benutzerdefinierten UserProfile-Eigenschaft erstellt.

private SPTokenManager TokenManager
{
  get
  {
return new SPTokenManager(GetUserProfile(), consumerKey, consumerSecret);
   }

Beim Laden der Seite wird von der Anwendung zunächst der Status des Authorization-Objekts getestet. Mithilfe des TokenManager-Objekts und der AccessToken-Objekte wird ein neues Authorization-Objekt erstellt.

    //Begin by testing the Authorization state for the user.
    this.Authorization = new WebOAuthAuthorization(this.TokenManager, this.AccessToken);

Wenn es sich um den Rückweg von einem Besuch bei LinkedIn zur Initiierung der Authentifizierung handelt, muss der Autorisierungsvorgang auf der Seite abgeschlossen werden.

if (!IsPostBack)
{
  //Do we need to complete the Authorization rocess?
  string accessToken = this.Authorization.CompleteAuthorize();
                
  if (accessToken != null)
  {
    //If the AccessToken is not null, store it.
    this.AccessToken = accessToken;
    Debug.WriteLine("[OAuth] Redirect: " + Request.Path);
    //Get the user back to where they belong.
    Response.Redirect(Request.Path);
   }
  }

Wenn der Prozess in der Anwendung abgeschlossen ist, kann mithilfe des Zugriffstokens versucht werden, das LinkedIn-Profil für den aktuellen Benutzer abzurufen.

//Finally, if ready, get the LinkedIn profile.
if (this.AccessToken != null)
{
  try
  {
    LoadLinkedInProfile();
  }
  catch (Exception ex)
  {
    Debug.WriteLine("[MSDN] Error loading LinkedIn Profile: " + ex.Message);
  }
 }

Wenn der Benutzer dieses Feature noch nicht verwendet hat, ist die Schaltfläche Opt In aktiviert. Durch Klicken auf die Schaltfläche wird die BeginAuthorize-Methode aufgerufen. Die API-Schlüssel werden vom LinkedIn-Toolkit an LinkedIn übergeben. Von LinkedIn wird eine Seite zurückgegeben, auf der der Benutzer Anmeldeinformationen für die Authentifizierung eingeben kann. Alle diese Aktionen erfolgen über die LinkedIn-API.

protected void btnOptIn_Click(object sender, EventArgs e)
{
  //Initiate the authorization process.
  this.Authorization.BeginAuthorize();           
}

Aktualisieren des LinkedIn-Zeitgeberauftrags

Vom benutzerdefinierten Zeitgeberauftrag für die LinkedIn-Verbindung wird das SPTokenManager-Objekt verwendet, um die Statusupdates für den Benutzer zu lesen und zu autorisieren. Dabei werden in regelmäßigen Abständen vom Zeitgeberauftrag UserProfile-Objekte abgefragt, deren Status Note-Felder aktualisiert wurden. Vom Zeitgeberauftrag wird eine Liste von UserProfile-Objekten erstellt und der Methode übergeben, die für die Ausführung des Updates zuständig ist. In der RetrieveUserProfileChanges-Methode wird mit dem folgenden Code ermittelt, ob das Feld Status Note die Zeichenfolge #li enthält, und das Profil wird der Liste der Profile hinzugefügt, deren Status aktualisiert wurde.

//If the property has the token in it, add it to the list.
if (statusNote.Trim().EndsWith(Globals.MSDNLI_StatusTag))
{
  Debug.WriteLine("[MSDN] We found the change token in: " + statusNote);
  changedUsers.Add(propertyChange.AccountName, propertyChange.ChangedProfile);
}

Schließlich werden alle Benutzerprofile aufgezählt, und die Updates werden einzeln über die UpdateLinkedIn-Methode an LinkedIn gesendet.

private void UpdateLinkedIn(UserProfile profile)
{
  //Init AccessToken to null; we are going to get it from the profile.
  string AccessToken = null;

  //Use the UserProfile to fetch the LinkedIn tokens.
  try
  {
    if ((profile[Globals.MSDNLI_TokenField] != null) && (profile[Globals.MSDNLI_TokenField].Value != null))
    {
      string[] delim = {"|"};
      string[] strTokenArr = profile[Globals.MSDNLI_TokenField].Value.ToString().Split(delim, StringSplitOptions.None);
      //Get the values.
      if ((strTokenArr != null) || (strTokenArr.Length == 4))
      {
        //Retrieve the access token.
        AccessToken = strTokenArr[0];
        Debug.WriteLine(String.Format("[MSDN] Retrieved the LinkedIn token for user: {0}", profile.DisplayName));
       }
       else
       {
         throw new Exception(String.Format("[MSDN] LinkedIn update failed for user {0}. Profile token field is not formatted correctly.", profile.DisplayName));
        }
      }
    }
    catch (Exception ex)
    {
      Debug.WriteLine("[MSDN] " + ex.Message);
    }

    if ((AccessToken != null) && (AccessToken != String.Empty))
    {
      try
      {
        //Create a token manager.
        SPTokenManager TokenManager = new SPTokenManager(profile, Globals.liApiKey, Globals.liSecretKey);
        //Prep the Authorization state for the user.
        WebOAuthAuthorization Authorization = new WebOAuthAuthorization(TokenManager, AccessToken);
        //Get an instance of the service.
        LinkedInService service = new LinkedInService(Authorization);
        //Issue the update.
        string statusMessage = profile[Globals.MSDNLI_ProfileField].Value.ToString();
        Debug.WriteLine(String.Format("[MSDN] Sending status update to LinkedIn: {0}", statusMessage ));
        service.UpdateStatus(statusMessage);
                    
       }
       catch (LinkedInException li)
       {
         Debug.WriteLine(String.Format("[MSDN] LinkedIn threw an error: {0}", li.Message));
       }
       catch (Exception ex)
       {
         Debug.WriteLine(String.Format("[MSDN] Error updating LinkedIn for user {0} the error was: {1}", profile.DisplayName, ex.Message));
       }
                
     }
   }

Aktualisieren des Benutzerstatus

Die Benutzerumgebung für die Übermittlung eines Statusupdates an LinkedIn ist einfach. Der Benutzer fügt am Ende des SharePoint-Statustextfelds den Text #li hinzu. Nach der Ausführung des Zeitgeberauftrags wird das Update an LinkedIn gesendet.

Erstellen und Ausführen des Beispiels

Mit den folgenden Schritten wird veranschaulicht, wie Sie dieses Projekt auf einer Entwicklungs- oder Testwebsite testen können.

So erstellen Sie das Beispiel

  1. Erstellen Sie einen Ordner mit dem Namen Microsoft.SDK.Server.Samples, und entpacken Sie dann die Datei MSDN LinkedIn Code.zip in diesem Ordner.

  2. Fügen Sie die Dateien LinkedIn.dll und DotNetOpenAuth.dll (aus MSDN LinkedIn Code\Shared Libraries\) dem globalen Assemblycache hinzu. Anleitungen zur Ausführung dieser Schritte in einer Entwicklungs- oder Testumgebung finden Sie unter How to: Install an Assembly into the Global Assembly Cache.

  3. Starten Sie Visual Studio 2010, und öffnen Sie dann die Datei LinkedInConnection.sln, die sich in dem Ordner befindet, den Sie in Schritt 1 erstellt haben.

  4. Geben Sie im Fenster Eigenschaften den URL-Websitewert der absoluten Adresse der Entwicklungs- oder Testwebsite an (beispielsweise http://MeineWebsite/). Beachten Sie dabei den abschließenden Schrägstrich ("/"). Konfigurieren Sie außerdem die using-Anweisung (using (SPSite site = new SPSite("servername"))) in der Datei LinkedInConnection.EventReceiver.cs so, dass der gleiche URL-Wert verwendet wird.

  5. Geben Sie in der Datei SendColleagueURLNote.EventReceiver.cs in der SendColleagueURLNoteEventReceiver-Methode die URL für die Website für die SharePoint-Zentraladministration an.

  6. Fügen Sie dem Projekt gegebenenfalls Verweise auf die folgenden Assemblys hinzu:

    • Microsoft.SharePoint.dll

    • Microsoft.SharePoint.ApplicationPages.Administration

    • Microsoft.SharePoint.Security

    • Microsoft.Office.Server.dll

    • Microsoft.Office.Server.UserProfiles.dll

    • DotNetOpenAuth.dll

    • LinkedIn.dll

  7. Fordern Sie bei LinkedIn einen API-Schlüssel an.

  8. Konfigurieren Sie die OAuth-Umleitungs-URL für LinkedIn wieder mit Ihrer Seite in SharePoint. Beispiel: http://MeineWebsite/_layouts/msdn/lisettings.aspx.

  9. Konfigurieren Sie in der Datei Elements\menuItem.xml die Url-Eigenschaft des UrlAction-Elements so, dass diese auf die gleiche SharePoint-Seite zeigt (http://MeineWebsite/_layouts/msdn/lisettings.aspx).

  10. Konfigurieren Sie in der Datei Globals.cs die Werte MSDNLI_ConsumerKey und MSDNLI_SecretKey mit Ihren Schlüsseln.

  11. Klicken Sie im Menü Erstellen auf Projektmappe bereitstellen. Nach Abschluss der Erstellung wird die Anwendungsseite auf der Entwicklungs- oder Textwebsite installiert.

So führen Sie das Beispiel aus

  1. Gehen Sie nach der Erstellung und Bereitstellung der Lösung zur Website für die Zentraladministration, um den LinkedIn-Zeitgeberauftrag festzulegen (http://MeineZentraladminWebsite/_admin/msdn/LinkedInTimer.aspx).

  2. Gehen Sie zu http://MeineWebsite/_layouts/msdn/lisettings.aspx, und klicken Sie auf Opt In, um sich bei Ihrem LinkedIn-Konto anzumelden und den Konnektor zu bestätigen.

Siehe auch

Aufgaben

Gewusst wie: Erstellen und Bearbeiten einer Benutzerprofileigenschaft

Weitere Ressourcen

Creating Custom Timer Jobs in Windows SharePoint Services 3.0

Entwicklerdokumentation für die LinkedIn-API

LinkedIn-API-Schlüssel

OAuth-Standardwebsite

DotNetOpenAuth

LinkedIn-Toolkit