我們必須在使用 REST 介面傳送訊息時向 Salesforce 進行驗證。 Salesforce 支援的 REST 呼叫驗證方法並非原生提供,需透過 WCF-WebHttp 轉接器,我們將使用它來呼叫 Salesforce 的 REST 介面。 因此,我們將建立自定義WCF端點行為,然後將它附加至 WCF-WebHttp 傳送配接器,我們將設定為叫用 Salesforce REST 介面。
同樣地,從 Salesforce 收到的 XML 回應將不會包含任何命名空間。 若要讓 BizTalk Server 針對回應架構處理回應消息,回應訊息必須包含目標命名空間。 因此,我們將使用 bizTalk ESB 工具組Microsoft建立自定義管線,以將命名空間新增至回應訊息。 接著,我們將使用此自定義管線作為要求與回應的接收管線,適用於 WCF-WebHttp 傳送埠。
新增 Salesforce 驗證的自定義 WCF 行為
Salesforce 為用戶端應用程式提供不同的選項,以向 Salesforce 進行驗證。 我們將使用 Salesforce 所稱的 使用者名稱-密碼 流程。 在用戶端上,WCF 可讓您建立 訊息偵測器 ,以在訊息傳送之前或接收訊息之後變更訊息。 消息檢查器是客戶端運行時的擴展模組,設置為一種行為。 行為被新增至行為擴展元素。 此行為延伸專案會在 machine.config 中註冊,且具有專案名稱,讓其可在WCF埠上設定為自定義行為。 如需詳細資訊,請參閱 使用自定義行為擴充 WCF。 我們將使用此方法來納入使用者名稱驗證流程,以向 Salesforce 進行驗證。 我們將遵循的廣泛步驟如下:
建立訊息偵測器,對 Salesforce 驗證端點進行 HTTP POST 呼叫,並接收存取令牌。 然後,訊息偵測器會將驗證令牌新增為傳送至 Salesforce 之查詢訊息的 Authorization 標頭值。 訊息偵測器也會將 Accept 標頭新增至要求訊息,讓收到的回應採用 XML 格式。 否則,Salesforce 會以預設 JSON 格式傳送訊息。
建立端點行為,以將訊息偵測器套用至用戶端端點。
建立一個行為擴充元素來配置端點行為。
建立訊息偵測器
在 Visual Studio 中作為 BtsSalesforceIntegration 解決方案的一部分,建立 C# 類別庫。 將它
Microsoft.BizTalk.Adapter.Behaviors命名為 ,並新增下列命名空間:using System.ServiceModel.Description; using System.ServiceModel.Dispatcher; using System.ServiceModel.Channels; using System.Xml; using System.Net; using System.IO; using System.ServiceModel.Configuration; using System.Configuration; using System.Web.Script.Serialization;從 [方案總管] 中,新增
System.ServiceModel、System.Web.Extensions和System.Configuration的參考。新增具有下列屬性的類別
SalesforceOAuthToken。 這個類別代表從 Salesforce 收到的授權令牌。public class SalesforceOAuthToken { public string id { get; set; } public string issued_at { get; set; } public string refresh_token { get; set; } public string instance_url { get; set; } public string signature { get; set; } public string access_token { get; set; } }新增實作
IClientMessageInspector和的IEndpointBehavior類別SalesforceRESTSecurityBehavior。 這個類別包括HttpPost()和FetchOAuthToken()方法,這些方法會對 Salesforce 驗證端點進行 HTTP POST 呼叫,以擷取授權令牌。因為類別
SalesforceRESTSecurityBehavior會實作IClientMessageInspector介面,所以它必須實作兩種方法和AfterReceiveReply()BeforeSendRequest()。 在此案例中,我們不需要在方法AfterReceiverReply()中執行任何動作。 不過,BeforeSendRequest()方法會調用FetchOAuthToken()方法,接著FetchOAuthToken()方法會呼叫HttpPost()方法。 然後,方法BeforeSendRequest()會將授權令牌新增為訊息標頭的一部分。 它也會新增 Accept 標頭,以確保從 Salesforce 收到的回應是 XML 格式。實作
IEndpointBehavior僅將訊息檢查器類別新增至用戶端端點行為。謹慎
此範例或指引會參考敏感性資訊,例如連接字串或使用者名稱和密碼。 請勿在程式代碼中硬式編碼這些值,並確定您使用最安全的驗證來保護機密數據。 如需詳細資訊,請參閱下列文件:
public class SalesforceRESTSecurityBehavior : IClientMessageInspector, IEndpointBehavior { // Some constants private static string SalesforceAuthEndpoint = "https://login.salesforce.com/services/oauth2/token"; // Configuration Properties private string username_; private string password_; private string consumerKey_; private string consumerSecret_; private int sessionTimeout_; // Private Properties private SalesforceOAuthToken token_; private DateTime tokenExpiryTime_; public SalesforceRESTSecurityBehavior( string username, string password, string consumerKey, string consumerSecret, int sessionTimeout) { this.username_ = username; this.password_ = password; this.consumerKey_ = consumerKey; this.consumerSecret_ = consumerSecret; this.sessionTimeout_ = sessionTimeout; } private void FetchOAuthToken() { if ((tokenExpiryTime_ == null) || (tokenExpiryTime_.CompareTo(DateTime.Now) <= 0)) { StringBuilder body = new StringBuilder(); body.Append("grant_type=password&") .Append("client_id=" + consumerKey_ + "&") .Append("client_secret=" + consumerSecret_ + "&") .Append("username=" + username_ + "&") .Append("password=" + password_); string result; try { result = HttpPost(SalesforceAuthEndpoint, body.ToString()); } catch (WebException) { // do something return; } // Convert the JSON response into a token object JavaScriptSerializer ser = new JavaScriptSerializer(); this.token_ = ser.Deserialize<SalesforceOAuthToken>(result); this.tokenExpiryTime_ = DateTime.Now.AddSeconds(this.sessionTimeout_); } } public string HttpPost(string URI, string Parameters) { WebRequest req = WebRequest.Create(URI); req.ContentType = "application/x-www-form-urlencoded"; req.Method = "POST"; // Add parameters to post byte[] data = Encoding.ASCII.GetBytes(Parameters); req.ContentLength = data.Length; System.IO.Stream os = req.GetRequestStream(); os.Write(data, 0, data.Length); os.Close(); // Do the post and get the response. System.Net.WebResponse resp = null; resp = req.GetResponse(); StreamReader sr = new StreamReader(resp.GetResponseStream()); return sr.ReadToEnd().Trim(); } #region IClientMessageInspector public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState) { // do nothing } public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel) { // We are going to send a request to Salesforce // Overview: // This behavior will do the following: // (1) Fetch Token from Salesforce, if required // (2) Add the token to the message // (3) Insert an Http Accept header so we get XML data in response, instead of JSON, which is default // Reference: http://www.salesforce.com/us/developer/docs/api_rest/index.htm // // (1) Authentication with Salesforce // (2) The token is added to the HTTP Authorization Header // (3) Add the Accept Header // HttpRequestMessageProperty httpRequest = null; if (request.Properties.ContainsKey(HttpRequestMessageProperty.Name)) { httpRequest = request.Properties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty; } if (httpRequest == null) { // NOTE: Ideally, we shouldn’t get here for WebHttp httpRequest = new HttpRequestMessageProperty() { Method = "GET", SuppressEntityBody = true }; request.Properties.Add(HttpRequestMessageProperty.Name, httpRequest); } WebHeaderCollection headers = httpRequest.Headers; FetchOAuthToken(); headers.Add(HttpRequestHeader.Authorization, "OAuth " + this.token_.access_token); headers.Add(HttpRequestHeader.Accept, "application/xml"); return null; } #endregion IClientMessageInspector #region IEndpointBehavior public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) { // do nothing } public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { clientRuntime.MessageInspectors.Add(this); } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { // do nothing } public void Validate(ServiceEndpoint endpoint) { // do nothing } #endregion IEndpointBehavior }在上一個步驟中,我們建立了將訊息偵測器套用至用戶端端點的行為類別。 我們現在需要將此行為提供給 WCF-WebHttp 適配卡的設定體驗,以將要求訊息傳送給 Salesforce。 若要讓此行為可供設定使用,我們需要執行兩件事:
建立一個類別,並使其衍生自
BehaviorExtensionElement使用元素名稱,在 machine.config 的 <extensions>\<behaviorExtensions> 元素中註冊 BehavaiorExtensionElement。
我們也會將組態屬性新增至此類別,使其可從 WCF-WebHttp 配接器組態 UI 取得。
public class SalesforceRESTBehaviorElement : BehaviorExtensionElement { public override Type BehaviorType { get { return typeof(SalesforceRESTSecurityBehavior); } } protected override object CreateBehavior() { return new SalesforceRESTSecurityBehavior(Username, Password, ConsumerKey, ConsumerSecret, SessionTimeout); } [ConfigurationProperty("username", IsRequired = true)] public string Username { get { return (string)this["username"]; } set { this["username"] = value; } } [ConfigurationProperty("password", IsRequired = true)] public string Password { get { return (string)this["password"]; } set { this["password"] = value; } } [ConfigurationProperty("consumerKey", IsRequired = true)] public string ConsumerKey { get { return (string)this["consumerKey"]; } set { this["consumerKey"] = value; } } [ConfigurationProperty("consumerSecret", IsRequired = true)] public string ConsumerSecret { get { return (string)this["consumerSecret"]; } set { this["consumerSecret"] = value; } } [ConfigurationProperty("sessionTimeout", IsRequired = false, DefaultValue = 300)] public int SessionTimeout { get { return (int)this["sessionTimeout"]; } set { this["sessionTimeout"] = value; } } }將強名稱金鑰檔案新增至專案並建置專案。 成功建置項目之後,請將元件
Microsoft.BizTalk.Adapter.Behaviors新增至 GAC。在 System.ServiceModel\Extensions\BehaviorExtensions 下的 machine.config 中新增下列項目。
<add name="Microsoft.BizTalk.Adapter.Behaviors.Demo.Salesforce" type="Microsoft.BizTalk.Adapter.Behaviors.SalesforceRESTBehaviorElement, Microsoft.BizTalk.Adapter.Behaviors, Version=1.0.0.0, Culture=neutral, PublicKeyToken=45bd7fe67ef032db"/>您可以從 GAC 擷取元件的公鑰令牌。 此外,如果您要在64位元電腦上建立此應用程式,則必須將這個項目新增到32位元和64位元版本的 machine.config。
新增自定義管線以將命名空間新增至 Salesforce 回應
從 Salesforce 收到的回應不包含命名空間。 不過,若要依照回應架構 (QueryResult.xsd) 處理回應訊息,我們必須在 Salesforce 回應中包含命名空間。 在本節中,我們會建立自定義管線,並使用隨附於 Microsoft BizTalk ESB Toolkit 的自定義管線元件,將命名空間新增至回應訊息。 此自定義管線會與 BizTalk Server 應用程式一起部署,並且會在設定 WCF-WebHttp 配接器時當做接收管線使用。
執行此程式中的步驟之前,請確定您正在建立此應用程式的計算機上已安裝並設定Microsoft BizTalk ESB 工具組。
若要新增自定義管線
在 BtsSalesforceIntegration 解決方案內,建立新的 BizTalk Server 專案。 將專案命名為
CustomPipeline。在 CustomPipeline 專案中,新增接收管線。 將管線命名為
AddNamespace.btp。在管線的 譯碼 階段中,從工具箱中拖曳 ESB 新增命名空間 管線元件。 在 反組譯 階段內,拖放 XML 反組譯程式 管線元件。
備註
如果 ESB 新增命名空間 管線元件未列在工具箱中,您可以新增它。 以滑鼠右鍵點擊 BizTalk 管線元件 索引標籤,然後點選 選擇項目。 在 [ 選擇工具箱專案] 對話框中,按兩下 [BizTalk 管線元件 ] 索引標籤,選取 [ESB 新增命名空間 ] 元件的複選框,然後按兩下 [ 確定]。
將強名稱金鑰檔案新增至專案並儲存專案。
以滑鼠右鍵按下 CustomPipeline 專案,然後選取 [ 屬性]。 在 [ 部署] 索引標籤的 [ 應用程式名稱] 中, 輸入
SalesforceIntegration。儲存專案的變更。
在本主題中,新增自定義行為以向 Salesforce 進行驗證,以及自定義管線,以將命名空間新增至 Salesforce 回應。 我們會在 BizTalk Server 管理控制台中設定實體埠時使用這些自定義元件。