代码示例:SharePoint-to-LinkedIn 连接器
上次修改时间: 2015年3月9日
适用范围: SharePoint Server 2010
本文内容
LinkedIn 集成简介
解决方案的结构
OAuth Authentication 和 LinkedIn
使用 LinkedIn API
创建计时器作业
SharePoint 用户配置文件存储
管理令牌
创建加入页
更新 LinkedIn 计时器作业
更新用户状态
生成并运行示例
该示例演示如何从其他社交网站将社交数据添加到您的 MySite,如何创建和使用新用户配置文件属性以及如何实现在您的 SharePoint 管理中心网站上托管的新计时器作业。解决方案的第一部分需要用户授予 SharePoint 应用程序代表用户执行操作的权限。此"加入"过程由 OAuth 身份验证标准处理,LinkedIn 和许多其他社会计算网站使用该标准。申请页处理允许应用程序更新 LinkedIn 数据所需的令牌的获取和存储。解决方案的第二部分是作为查询用户配置文件更改的计时器作业实现的。计时器作业需要一个管理页以启用和禁用计时器作业。
示例代码提供者:Catapult Systems 的 Mathew McDermott |
Critical Path Training, LLC 的 Andrew Connell
通过下载 Microsoft SharePoint 2010 软件开发工具包(该链接可能指向英文页面) (SDK) 或者从代码库(该链接可能指向英文页面)下载示例,将此代码示例安装到您自己的计算上。如果下载的是 SharePoint 2010 SDK,则示例将安装在您的文件系统中的以下位置:C:\Program Files\Microsoft SDKs\SharePoint 2010\Samples\Social Data and User Profiles。
LinkedIn 集成简介
当公司在整个企业范围内进行社会计算时,可能需要从组织内部向社会计算平台的外部提供程序发布信息。由于这些提供程序会接触更广泛的受众,因此许多提供程序正在实现能够创建访问用户信息的应用程序的 Open API。
当用户在 status note 末尾包含文本 #li 时,此解决方案会更新 Microsoft SharePoint Server 2010 用户的 LinkedIn 状态。从任何平台开发应用程序面临的一大挑战是从自定义应用程序到服务提供程序的安全身份验证。LinkedIn 使用 OAuth 标准执行身份验证和 API 访问。这要求开发人员申请 API 访问并接收要在应用程序中使用的 API 密钥对。该过程完成后可以部署解决方案,然后用户可以申请使用该应用程序。
解决方案的结构
解决方案的第一部分需要用户授予 SharePoint 应用程序代表用户执行操作的权限。此"加入"过程由 OAuth 身份验证标准处理。有关 LinkedIn 身份验证过程的详细信息,请参阅 LinkedIn 开发人员 API 文档。此解决方案为单独用户加入我们的应用程序实现申请页。申请页(图 1 中所示)需要处理允许应用程序更新 LinkedIn 数据所需的令牌的获取和存储。
图 1. LinkedIn 加入页
解决方案的第二部分是查询用户配置文件更改的计时器作业。如果 LinkedIn Status Note 字段更改,并且包含文本 #li,则解决方案会将该更新发送到用户的 LinkedIn 帐户。计时器作业需要一个管理页(图 2 中所示)以启用和禁用计时器作业。
图 2. 管理 LinkedIn 计时器作业
OAuth Authentication 和 LinkedIn
OAuth 标准的详细信息可以在 OAuth 标准网站(该链接可能指向英文页面)上找到。一个开源 .NET 库 DotNetOpenAuth(该链接可能指向英文页面) 处理实现 Oauth 事务所需的交互。LinkedIn 工具包是一个使用 DotNetOpenAuth 库的 CodePlex 项目,它处理此解决方案的 LinkedIn API 详细信息。
使用 LinkedIn API
若要开始使用 LinkedIn API 开发,必须申请 API 密钥,然后按以下基本步骤操作。
开始使用 LinkedIn API
从 LinkedIn 开发人员门户(该链接可能指向英文页面)请求 API 密钥。
在您的应用程序中使用该密钥,以使用户可以注册您的应用程序。
用户申请后,存储用户令牌,以使应用程序可以代表用户执行状态更新。
创建计时器作业
有关创建计时器作业所需步骤的详细信息,请参阅Creating Custom Timer Jobs in Windows SharePoint Services 3.0。此解决方案会更改该文章中的一些实现详细信息,以执行 LinkedIn 状态更新。
SharePoint 用户配置文件存储
此解决方案的第一项挑战是执行从 SharePoint 到外部系统的身份验证以及在不需要用户密码的情况下存储身份验证令牌。用户配置文件可简化存储每个用户的身份验证令牌的任务。表 1 中显示的字段必须由服务器场管理员或 FeatureActivated 事件接收器配置。令牌字段不应向用户显示。
表 1. 字段必须由服务器场管理员或 FeatureActivated 事件接收器配置
名称 |
显示名称 |
用途 |
类型 |
---|---|---|---|
LI-Token |
LinkedIn 安全令牌 |
存储应用程序所需的身份验证令牌。 |
字符串 |
LI-Status |
更新 LinkedIn 状态 |
存储用户更新 LinkedIn 状态的首选项。 |
布尔值 |
管理令牌
LinkedIn 工具包和 DotNetOpenAuth(该链接可能指向英文页面) 库需要开发人员创建一个处理 OAuth 令牌的存储和检索的令牌管理器。此令牌处理过程由实现 IConsumerTokenManager 接口的自定义类处理。IConsumerTokenManager 接口由 DotNetOpenAuth 库调用,以处理 OAuth 标准中用户身份验证所需的令牌的存储和检索。
此解决方案的 SPTokenManager 类在用户配置文件属性中存储令牌。
构造函数将 UserProfile 对象作为参数。
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;
}
ExpireRequestTokenAndStoreNewAccessToken 方法清除以前存储的请求令牌,然后存储一个新存取令牌。
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);
}
StoreNewRequestToken 方法接受身份验证请求和响应,然后提取和存储所需的令牌。
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);
}
GetTokenSecret 方法返回与请求的令牌(请求令牌或存取令牌)相对应的令牌密钥。
public string GetTokenSecret(string token)
{
//Return the token secret for the request token OR access token that
//you are given.
return GetUserProfileValue(token);
}
这些方法调用两个实用程序函数之一,这些函数通过以后可以由计时器作业检索的格式读取和写入用户配置文件值。SetUserProfile 方法采用令牌值、令牌密钥和令牌类型参数。根据令牌类型更新用户配置文件字段,值存储为字符串对象的四项数组。当用户最初申请服务时,更新作为 GET 请求执行。因此,应用程序最初在事务的持续时间内将当前 Web 的 AllowUnsafeUpdates 属性设置为 true。
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;
}
}
GetUserProfile 方法采用令牌值,并从存储中返回关联的令牌密钥。
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;
}
}
创建加入页
当用户访问加入页时,检查表示当前用户的 UserProfile 对象,并确定该用户的 LinkedIn 身份验证状态。如果找到适当令牌,则加载关联的 LinkedIn 配置文件。如果找不到令牌,则启用"加入"按钮。该页的代码通过实现 TokenManager 对象、AccessToken 对象和创建 LinkedIn 的事务所需的 Authorization 对象的属性来处理此交互。TokenManager 提取从 LinkedIn 获取的全局 ApiKeys,然后将它们提供给您的自定义应用程序。
TokenManager 对象是根据使用方密钥值和自定义 UserProfile 属性值创建的。
private SPTokenManager TokenManager
{
get
{
return new SPTokenManager(GetUserProfile(), consumerKey, consumerSecret);
}
当该页加载时,应用程序首先测试 Authorization 对象的状态。它使用 TokenManager 对象和 AccessToken 对象创建一个新 Authorization 对象
//Begin by testing the Authorization state for the user.
this.Authorization = new WebOAuthAuthorization(this.TokenManager, this.AccessToken);
如果这是访问 LinkedIn 以启动身份验证的回程,则该页需要完成身份验证过程。
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);
}
}
应用程序完成该过程后,它可以采用存取令牌,并尝试检索当前用户的 LinkedIn 配置文件。
//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);
}
}
如果用户以前从未使用过此功能,则启用"加入"按钮。单击时,它会调用 BeginAuthorize 方法。LinkedIn 工具包采用 API 密钥,并将它们传递到 LinkedIn。LinkedIn 返回一个页面,用户可以在其中输入身份验证凭据。所有这些都由 LinkedIn API 处理。
protected void btnOptIn_Click(object sender, EventArgs e)
{
//Initiate the authorization process.
this.Authorization.BeginAuthorize();
}
更新 LinkedIn 计时器作业
自定义 LinkedIn 连接计时器作业使用 SPTokenManager 对象读取和授权用户的状态更新。计时器作业定期查询 Status Note 字段已更新的 UserProfile 对象。计时器作业生成 UserProfile 对象的列表,然后将它们传递到负责执行更新的方法。在 RetrieveUserProfileChanges 方法中,以下代码确定"Status Note"是否包含 #li 字符串,然后将配置文件添加到已更新其状态的配置文件列表中。
//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);
}
最后,枚举所有用户配置文件,然后由 UpdateLinkedIn 方法逐个将更新发送到 LinkedIn。
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));
}
}
}
更新用户状态
将状态更新发布到 LinkedIn 的用户体验过程非常简单。用户只需将文本 #li 添加到 SharePoint 状态文本框的末尾。在执行计时器作业后,更新将被发送到 LinkedIn。
生成并运行示例
以下步骤演示如何在您的开发或测试网站上测试该项目。
生成示例
创建一个名为 Microsoft.SDK.Server.Samples 的文件夹,然后将"MSDN LinkedIn Code.zip"文件解压缩到该文件夹中。
将"LinkedIn.dll"文件和"DotNetOpenAuth.dll"文件(位于 MSDN LinkedIn Code\Shared Libraries\ 中)添加到全局程序集缓存。有关如何在您的开发或测试环境中执行此操作的指导,请参阅How to: Install an Assembly into the Global Assembly Cache。
启动 Visual Studio 2010,然后打开您在步骤 1 中创建的文件夹中的"LinkedInConnection.sln"文件。
在"属性"窗口中,指定您的开发或测试网站的绝对地址的网站 URL 值(例如 http://mysite/)。确保包含结束左斜线。另外,在"LinkedInConnection.EventReceiver.cs"文件中配置 using 语句 (using (SPSite site = new SPSite("servername"))),以便它使用此相同 URL 值。
在"SendColleagueURLNote.EventReceiver.cs"文件中,在 SendColleagueURLNoteEventReceiver 方法中指定您的 SharePoint 管理中心网站的 URL。
将对以下程序集的引用添加到项目中(如果它们尚不存在):
Microsoft.SharePoint.dll
Microsoft.SharePoint.ApplicationPages.Administration
Microsoft.SharePoint.Security
Microsoft.Office.Server.dll
Microsoft.Office.Server.UserProfiles.dll
DotNetOpenAuth.dll
LinkedIn.dll
从 LinkedIn(该链接可能指向英文页面) 获取 API 密钥。
配置 LinkedIn OAuth 重定向 URL 使其转回 SharePoint 中的页面。例如 http://mysite/_layouts/msdn/lisettings.aspx。
在"Elements\menuItem.xml"文件中,将 UrlAction 元素的 Url 属性配置为指向该相同 SharePoint 页 (http://mysite/_layouts/msdn/lisettings.aspx)。
在 Globals.cs 文件中,使用您的密钥配置 MSDNLI_ConsumerKey 值和 MSDNLI_SecretKey 值。
在"生成"菜单上,选择"部署解决方案"。生成完成后,应用程序页面将安装到您的开发或测试网站上。
运行示例
生成和部署解决方案后,请转到管理中心网站以设置 LinkedIn 计时器作业 (http://myCentralAdminSite/_admin/msdn/LinkedInTimer.aspx)。
转到 http://mysite/_layouts/msdn/lisettings.aspx,然后单击"加入"以登录您的 LinkedIn 帐户,然后加入到连接器。
请参阅
任务
其他资源
Creating Custom Timer Jobs in Windows SharePoint Services 3.0