本文章是由機器翻譯。

Silverlight

建置使用 Silverlight,第 2 部的 Line-Of-Business 企業應用程式

Hanu Kommalapati

本文將告訴您:

  • Silverlight 的執行階段環境
  • Silverlight 的非同步程式設計
  • 跨網域原則
  • 範例企業應用程式
本文將使用下列技術:
Silverlight 2

可從 MSDN 程式庫 的程式碼下載
瀏覽線上的程式碼

內容

與商務服務的整合
服務引動過程
同步處理的服務呼叫
訊息的實體轉譯
在服務呼叫之後的 Silverlight 狀態變更
跨網域原則
Web 服務之外裝載的 IIS 的跨網域原則
在 IIS 內裝載的服務的跨網域原則
應用程式的安全性
應用程式磁碟分割
生產力和 Beyond

在第一個的期,可這的一系列的期間我引入呼叫 Center 案例,並顯示在螢幕的擴展 (快顯的螢幕) 的實作透過利用非同步 Silverlight 所支援的 TCP 通訊端的連線通訊端 (請參閱 <建置使用 Silverlight,第 1 部的 Line-Of-Business 企業應用程式").

螢幕的快顯已實作,透過模擬呼叫發送器時,挑選呼叫,以從內部佇列,並推入通知,透過快取在伺服器上為泛型清單的先前接受的通訊端連接。此處我將會結束由實作應用程式的安全性、 整合與商務服務,和為 Web 服務和應用程式磁碟分割中實作跨網域原則。呼叫中心的應用程式的邏輯架構如 [圖 1 ] 所示。驗證服務會在公用程式服務,商務服務、 ICallService 和 IUserProfile 時中實作,實將被作商務服務的專案,內顧名思義。

fig01.gif

[圖 1 Silverlight 呼叫中心邏輯架構

雖然圖中顯示公用程式的服務,在時間的串流處理的事件可下載的示範中不包含這項功能。事件擷取服務功能的實作會類似商務服務的實作。不過,不是嚴重錯誤的商業事件可以在本機快取至隔離儲存區和傾印到以批次模式伺服器。我將開始討論實作商務服務,並結束與應用程式磁碟分割。

與商務服務的整合

整合服務可是其中一個,Line-of-Business (LOB) 應用程式的重要層面,並 Silverlight 提供足夠的元件存取 Web 架構資源 」 和 「 服務。HttpWebRequest、 WebClient 和 Windows Communication Foundation (WCF) Proxy 基礎結構,是一些常用的網路元件的 HTTP 架構的互動。在這的篇文章我將使用 WCF 服務,來整合與後端商務程序。

大多數的我們使用的應用程式開發的過程中整合與後端資料來源的 Web 服務 ; 不會許多不同的傳統等應用程式在 ASP.NET,Windows Presentation Foundation (WPF)] 或 [Windows Form 應用程式使用 Silverlight 的 WCF Web 服務存取。差異是,繫結支援 」 和 「 非同步程式撰寫模型。Silverlight 會只支援,basicHttpBinding] 和 [PollingDuplexHttpBinding。請注意 HttpBinding 最具互通性的繫結。基於此理由我將使用它進行本文中所有的整合。

PollingDuplexHttpBinding 會允許要推入通知,透過 HTTP 的回呼合約的使用。我的撥接中心無法有用於這個繫結快顯畫面的通知。不過,作需要 HTTP 連線,藉此獨占其中兩個同時 HTTP 連線允許瀏覽器,例如 Internet Explorer 7.0 在伺服器上的快取中。這可能會造成效能的問題,因為所有 Web 內容將會都有一個連線透過序列化。Internet Explorer 8.0 會讓每個網域的六個同時連線,並將解決這類的效能問題。(推入通知使用 PollingDuplexHttpBinding 無法是主題為未來的發行 Internet Explorer 8.0 最常用的可用時)。

傳回至應用程式。快顯畫面的處理序時,代理程式會接受呼叫,填入螢幕與呼叫者資訊 — 在這裡將呼叫者的順序詳細資料。呼叫者的資訊應該包含所需的資訊,來唯一識別後端資料庫中的順序。這個示範案例,我將假設訂單編號語音至互動式語音回應 (IVR) 系統。Silverlight 應用程式會以順序編號呼叫 WCF Web 服務,做為唯一的識別項。服務合約定義和實作 [圖 2 ] 所示。

[圖 2 商務服務實作

ServiceContracts.cs

[ServiceContract]
public interface ICallService
{
    [OperationContract]
    AgentScript GetAgentScript(string orderNumber);
    [OperationContract]
    OrderInfo GetOrderDetails(string orderNumber);
}

[ServiceContract]
public interface IUserProfile    
{
    [OperationContract]
    User GetUser(string userID);
}

CallService.svc.cs

 [AspNetCompatibilityRequirements(RequirementsMode = 
                            AspNetCompatibilityRequirementsMode.Allowed)]
public class CallService:ICallService, IUserProfile
{
  public AgentScript GetAgentScript(string orderNumber)
  {
    ... 
    script.QuestionList = DataUtility.GetSecurityQuestions(orderNumber);
    return script;
  }

  public OrderInfo GetOrderDetails(string orderNumber)
  {
    ... 
    oi.Customer = DataUtility.GetCustomerByID(oi.Order.CustomerID);
    return oi;
  }

  public User GetUser(string userID)
  {
    return DataUtility.GetUserByID(userID);
  }
 }

Web.config

<system.servicemodel> 
   <services>
     <endpoint binding="basicHttpBinding"                contract="AdvBusinessServices.ICallService"/>
     <endpoint binding="basicHttpBinding"                contract="AdvBusinessServices.IUserProfile"/>
   </services>       
   <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
<system.servicemodel>

這些服務端點的實作不是真的很有趣的因為這些都是簡單的 WCF 實作。為簡單的起見中,,我不會將任何資料庫使用的商業項目,但是會儲存客戶、 順序,以及使用者物件中使用記憶體中清單物件。(未顯示在此但會程式碼下載中可用) [DataUtil 類別會封裝這些記憶體中清單物件的存取。

fig03.gif

[圖 3 代理程式的指令碼的安全性問題

Silverlight 消耗的 WCF 服務端點需要到 ASP.NET 管線存取,並因此需要 CallService 實作上 AspNetCompatibilityRequirements 屬性中。<servicehostingenvironment/>這必須符合的 Web.config 檔中設定。

如同先前提到,Silverlight 只支援 basicHttpBinding 和 PollingDuplexHttpBinding。如果您使用 WCF 服務的 Visual Studio 範本,它就會設定的則必須以手動方式變更為 basicHttpBinding Silverlight 可以新增服務參考 Proxy 產生之前的 wsHttpBinding 端點繫結。ASP.NET 裝載的相容性的變更和繫結的變更將會自動被處理的如果 CallService.svc 加入 AdvBusinessServices 專案使用 Silverlight 啟用 WCF 服務的 Visual Studio 範本。

服務引動過程

需要實作 Silverlight 可呼叫的服務,目前它是時間建立服務的 Proxy,並使用這些連結至後端服務的實作 UI 了。您可以只產生 WCF 服務的 Proxy 可靠地使用服務參考 | Visual Studio 的 [新增服務參考順序。在我示範 Proxy 產生至命名空間 CallBusinessProxy。Silverlight 只允許在的網路資源的非同步呼叫],而服務引動過程會也不例外。在客戶呼叫時 Silverlight 用戶端將通知接聽,然後顯示一個接受 / 拒絕] 對話方塊。

一旦呼叫會接受代理程式下, 一個步驟處理序中的就會是以呼叫 Web 服務擷取代理程式指令碼對應到呼叫情況。為這個示範,我將只會使用一個指令碼如 [圖 3 顯示。顯示的指令碼會包含在問候語,以及安全性問題的清單。代理程式會確保向前移動,協助之前會回答最小值的一些問題。

藉由存取 ICallService.get­AgentScript(),並提供與輸入訂單編號擷取代理程式的指令碼。與非同步程式設計模型,強制使用 Silverlight Web 服務堆疊一致,the GetAgentScript() 是 CallServiceClient.BeginGetAgentScript()。服務呼叫時,您必須提供 get­AgentScriptCallback,一個回呼處理常式,如 [圖 4 ] 所示。

[圖 4 服務引動過程] 和 [Silverlight UI 變更

class Page:UserControl
{   
   ... 
   void _notifyCallPopup_OnAccept(object sender, EventArgs e)
   {
     AcceptMessage acceptMsg = new AcceptMessage();
     acceptMsg.RepNumber = ClientGlobals.currentUser.RepNumber;
     ClientGlobals.socketClient.SendAsync(acceptMsg);
     this.borderCallProgressView.DataContext = ClientGlobals.callInfo;
     ICallService callService = new CallServiceClient();
     IAsyncResult result = 
        callService.BeginGetAgentScript(ClientGlobals.callInfo.OrderNumber, 
                     GetAgentScriptCallback, callService);
     //do a preemptive download of user control
     ThreadPool.QueueUserWorkItem(ExecuteControlDownload);
     //do a preemptive download of the order information
     ThreadPool.QueueUserWorkItem(ExecuteGetOrderDetails, 
                ClientGlobals.callInfo.OrderNumber);
   }

   void GetAgentScriptCallback(IAsyncResult asyncReseult)
   {

     ICallService callService = asyncReseult.AsyncState as ICallService;
     CallBusinessProxy.AgentScript svcOutputAgentScript = 
                     callService.EndGetAgentScript(asyncReseult);
     ClientEntityTranslator astobas =  
                               SvcScriptToClientScript.entityTranslator;
     ClientEntities.AgentScript currentAgentScript =  
                             astobas.ToClientEntity(svcOutputAgentScript)
                             as ClientEntities.AgentScript;
     Interlocked.Exchange<ClientEntities.AgentScript>(ref 
                   ClientGlobals.currentAgentScript, currentAgentScript);
     if (this.Dispatcher.CheckAccess())
     {
       this.borderAgentScript.DataContext = ClientGlobals.agentScript;
       ... 
       this.hlVerifyContinue.Visibility = Visibility.Visible;
     }
     else
     {
       this.Dispatcher.BeginInvoke(
        delegate()
        {
          this.borderAgentScript.DataContext = ClientGlobals.agentScript;
          ...
          this.hlVerifyContinue.Visibility = Visibility.Visible;

        } );
       }
     }
   private void ExecuteControlDownload(object state)
   {
     WebClient webClient = new WebClient();
     webClient.OpenReadCompleted += new   
       OpenReadCompletedEventHandler(OrderDetailControlDownloadCallback);
     webClient.OpenReadAsync(new Uri("/ClientBin/AdvOrderClientControls.dll", 
                                                     UriKind.Relative));
   }
   ... 
}

因為在服務呼叫的結果只能擷取從回呼處理常式中,Silverlight 應用程式狀態的任何變更必須在回呼處理常式中發生。 CallServiceClient.BeginGetAgentScript() 會叫用在 UI 執行緒上執行的 _notifyCallPopup_OnAccept 和佇列非同步要求,並立即下一個陳述式所傳回。 因為代理程式的指令碼尚未使用,您必須等到回呼會觸發您的快取之前,指令碼及資料繫結它至 UI。

成功完成,服務的呼叫的觸發程序 GetAgentScriptCallback 擷取代理程式的指令碼,會填入一個全域的變數,並調整 UI 資料繫結至適當的 UI 項目的代理程式的指令碼。 時調整 UI,the GetAgentScriptCallback 會確定它更新 Dispatcher.CheckAccess() 透過使用的 UI 執行緒上。

UIElement.Dispatcher.CheckAccess() 會比較與背景工作執行緒的 UI 執行緒識別碼,並傳回 true,如果兩個執行緒相同,否則它傳回 false 就可以了。 get­AgentScriptCallback 背景工作執行緒上執行的時 (實際上,因為這會在背景工作執行緒上永遠執行您無法直接呼叫 Dispatcher.BeginInvoke),CheckAccess() 將會傳回 False,而且 UI 可以更新分派 Dispatcher.Invoke() 透過匿名委派。

同步處理的服務呼叫

因此,Silverlight 的網路環境的非同步性質,它是幾乎不可能進行的同步服務,如果要在 UI 執行緒上呼叫,並等候它完成根據呼叫的結果,應用程式狀態變更的目的。 在 [圖 4 ,_notifyCallPopup_OnAccept 需要擷取訂單詳細資料,轉換為用戶端,實體的輸出訊息並將它儲存至全域變數在以安全執行緒 (Thread-Safe) 的方式。 若要完成這個工作可能是一個 tempted 撰寫處理常式程式碼,如下所示:

CallServiceClient client = new CallServiceClient();
client.GetOrderDetailsAsync(orderNumber);
this._orderDetailDownloadHandle.WaitOne();
//do something with the results

但這段程式碼將會凍結應用程式時叫用 this._orderDetailDownloadHandle.WaitOne() 陳述式。 這是因為封鎖 UI 執行緒從接收來自其他執行緒的任何分派的訊息,WaitOne () 陳述式。 而,您可以排定執行服務的呼叫,等候呼叫完成,以及完成後處理服務輸出的整個背景工作執行緒上的背景工作執行緒。 這項技術如 [圖 5 ] 所示。 若要避免不小心使用封鎖 UI 執行緒中的呼叫,我會包裝在自訂的 SLManualResetEvent ManualResetEvent,並進行測試,當 WaitOne () 呼叫時,UI 執行緒的。

[圖 5 擷取訂單詳細資料

void _notifyCallPopup_OnAccept(object sender, EventArgs e)
{
  ... 
  ThreadPool.QueueUserWorkItem(ExecuteGetOrderDetails, 
        ClientGlobals.callInfo.OrderNumber);
}
private SLManualResetEvent _ orderDetailDownloadHandle = new 
        SLManualResetEvent();
  private void ExecuteGetOrderDetails(object state)
{
  CallServiceClient client = new CallServiceClient();
  string orderNumber = state as string;
  client.GetOrderDetailsCompleted += new
        EventHandler<GetOrderDetailsCompletedEventArgs>
        (GetOrderDetailsCompletedCallback);
  client.GetOrderDetailsAsync(orderNumber);
  this._orderDetailDownloadHandle.WaitOne();
  //translate entity and save it to global variable
  ClientEntityTranslator oito = SvcOrderToClientOrder.entityTranslator;
  ClientEntities.Order currentOrder = 
        oito.ToClientEntity(ClientGlobals.serviceOutputOrder)
        as ClientEntities.Order;
  Interlocked.Exchange<ClientEntities.Order>(ref ClientGlobals.
       currentOrder, currentOrder);
}

void GetOrderDetailsCompletedCallback(object sender, 
        GetOrderDetailsCompletedEventArgs e)
  {
    Interlocked.Exchange<OrderInfo>(ref ClientGlobals.serviceOutputOrder, 
         e.Result);
    this._orderDetailDownloadHandle.Set();
  }

因為 SLManualResetEvent 一般用途的類別,您無法依賴特定控制項的 [Dispatcher.CheckAccess()。 ApplicationHelper.IsUiThread() 可以檢查 application.RootVisual.Dispatcher.CheckAccess() ; 不過,對這個方法的存取將會觸發無效跨執行緒存取例外。 因此,只有可靠方法的測試,這在背景工作執行緒,T: UIElement 執行個體無法存取時是使用 Deployment.current.Dispatcher.CheckAccess() 如下所示:

public static bool IsUiThread()
    {
        if (Deployment.Current.Dispatcher.CheckAccess())
            return true;
        else
            return false;
    }

背景執行,而非使用 ThreadPool.QueueUserWorkItem 的工作可以使用 BackGroundWorker 也會使用 ThreadPool,但可讓您可以在 UI 執行緒執行的處理常式的連結。 這個模式會允許執行數個服務的呼叫中的平行,和等待完成使用 SLManualResetEvent.WaitOne() 供進一步處理彙總結果的所有呼叫。

訊息的實體轉譯

會將 [GetAgentScriptCallback 也在輸出訊息的實體 (也稱為 DataContracts) 從服務轉譯為用戶端的實體,表示在用戶端使用的語意。 例如,伺服器端郵件項目的設計可能會不處理有關資料繫結,而注意關閉 multiuse 服務的本質,將會提供各式各樣的使用中,不只是撥接中心。

而且,最好在無法將訊息實體 (Entity) 與緊密連接因為用戶端的控制項中,並不會變更郵件實體。 轉譯訊息至用戶端的實體的實體的練習不只適用於 Silverlight 的但想要避免在設計階段的緊密連結時通常適用於任何 Web 服務消費者。

我決定要保留在實體轉譯器的實作很簡單 — 沒有異國的巢狀泛型、 Lambda 的運算式或反向的控制項的容器。 ClientEntityTranslator 會是抽象類別,定義每一個子類別必須覆寫 ToClientEntity() 方法:

public abstract class ClientEntityTranslator
{
  public abstract ClientEntities.ClientEntity ToClientEntity(object 
                                                 serviceOutputEntity);
}

每一個子類別是唯一服務的 Exchange 型別,因此我將在這裡建立多個轉譯器視。 在我示範,我做服務呼叫的三種: IUserProfile.GetUser() ICallService.GetAgentScript(),ICallService.GetOrderDetails()。 因此我建立三個轉譯器,如 [圖 6 ] 所示。

[圖 6 到用戶端的實體轉譯器的郵件實體

public class SvcOrderToClientOrder : ClientEntityTranslator
{
  //singleton
  public static ClientEntityTranslator entityTranslator = new                 
                                           SvcOrderToClientOrder();
  private SvcOrderToClientOrder() { }
  public override ClientEntities.ClientEntity ToClientEntity(object                   
                                                  serviceOutputEntity)
  {
    CallBusinessProxy.OrderInfo oi = serviceOutputEntity as 
                                         CallBusinessProxy.OrderInfo;
    ClientEntities.Order bindableOrder = new ClientEntities.Order();
    bindableOrder.OrderNumber = oi.Order.OrderNumber;
    //code removed for brevity  ... 
    return bindableOrder;
  }
}

public class SvcUserToClientUser : ClientEntityTranslator
{
    //code removed for brevity  ... 
}

public class SvcScriptToClientScript : ClientEntityTranslator
{
    //code removed for brevity  ...
    }
}

如果您注意到上述的轉譯就會是沒有狀態 (Stateless),並使用單一模式。 在轉譯器必須能夠繼承 ClientEntityTranslator 的一致性,並且必須是單一以避免記憶體回收的變換。

個別的服務呼叫時,我將重複使用相同的執行個體。 我也無法建立的服務互動,需要較大的輸入的郵件 (這通常是交易式服務引動過程的) ServiOutputEntityTranslator,使用下列類別定義:

public abstract class ServiOutputEntityTranslator
{
  public abstract object ToServiceOutputEntity(ClientEntity  
                                                      clientEntity);
}

如果您發現上述函式的傳回值時,我不控制郵件實體 (我無法,這個示範中,但未在真實世界) 的基底類別是 「 物件 」。 型別安全將由個別的轉譯實作。 將為了示範的簡化,我沒有任何資料儲存回在的伺服器讓這個示範轉換訊息實體 (Entity) 的用戶端項目不包含任何轉譯器。

在服務呼叫之後的 Silverlight 狀態變更

Silverlight Visual 狀態變更,只能藉由程式碼執行在 UI 執行緒上執行。 因為服務的非同步執行永遠會傳回結果上呼叫回呼處理常式,處理常式將會是正確的位置來變更應用程式的 Visual] 或 [非 Visual 狀態。

如果有可能是嘗試以非同步方式修改共用的狀態的多個服務,Non-Visual) 狀態的變更應該交換以安全執行緒 (Thread-的方式。 它是永遠都建議您檢查 Deployment.current.Dispatcher.CheckAccess() 修改 UI 之前的動作。

跨網域原則

與媒體應用程式和應用程式顯示橫幅廣告,不同的是真實企業級的 LOB 應用程式都需要與各種服務的裝載環境的整合。 例如,「 呼叫中心整個文件所參考的應用程式 」 是典型的企業應用程式。 在網站上所裝載這個應用程式可設定狀態的通訊端伺服器的快顯畫面,WCF 基礎 Web 服務存取企業營運 (LOB 的資料和它可能會下載其他的 XAP 套件 (壓縮 Silverlight 部署套件) 從存取不同的網域。 它會使用另一個網域的傳輸檢測資料中。

Silverlight 的沙箱不預設允許的來源網域以外的其他任何網域的網路存取 — advcallclientweb,當您看到 [圖 1 ] 中的上一步。 當應用程式存取的來源網域以外的其他任何網域,Silverlight Runtime 會檢查選擇的原則。 以下是支援跨網域的原則要求,用戶端所需要的服務的裝載案例的一般清單:

  • 在服務的處理程序 (或主控台應用程式為了簡單起見) 中所裝載的 Web 服務
  • IIS 或其他 Web 伺服器裝載的 Web 服務
  • 在服務的處理程序 (或主控台應用程式) 中所裝載的 TCP 服務

我會討論跨網域原則實作 TCP 服務的最後一個月,因此將著重在自訂處理序,並在 IIS 內所裝載的 Web 服務。

正在直接實作的 Web 服務端點裝載於 IIS 的跨網域原則時,其他的情況下會需要知道的本質,原則要求和回應。

Web 服務之外裝載的 IIS 的跨網域原則

有效的狀態管理,可能有一個可能會想要在作業系統處理序 IIS 之外裝載服務的情況。 處理序的這類的 WCF 服務的跨網域存取,將必須在 HTTP 端點的根目錄的主應用程式原則。 當叫用跨網域的 Web 服務時 Silverlight 會讓 HTTP 取得 clientaccesspolicy.xml 的要求。 如果服務裝載在 IIS 內,請 client­accesspolicy.xml 檔案可以被複製,到網站的根目錄,並 IIS 會執行其餘的服務檔案。 情況下自訂主控本機電腦上,http://localhost:<port>/clientaccesspolicy.xml 應該是有效的 URL。

因為呼叫 Center 示範不使用任何自訂的裝載的 Web 服務,我會在主控台應用程式中使用簡單的 TimeService 來示範概念。 主控台,將會公開使用新的 REST) 功能,Microsoft.NET Framework 3.5 的 representational 狀態 Transfer (REST) 端點。 UriTemplate 屬性必須精確地設定為 [圖 7 ] 所示的常值。

[圖 7 的實作自訂的裝載 WCF 服務

[ServiceContract]
public interface IPolicyService
{
    [OperationContract]            
    [WebInvoke(Method = "GET", UriTemplate = "/clientaccesspolicy.xml")]  
    Stream GetClientAccessPolicy();
}
public class PolicyService : IPolicyService
{
    public Stream GetClientAccessPolicy()
    {
        FileStream fs = new FileStream("PolicyFile.xml", FileMode.Open);
        return fs;
    }
}

介面名稱] 或 [方法名稱有沒有關係,結果,; 您也可以選擇您要的任何項目]。 WebInvoke 有其他的屬性,例如 RequestFormat 和 ResponseFormat,依預設會設定為 XML,; 我們不需要明確地指定它們。 我們也會依賴 BodyStyle) 屬性,將這表示回應不被包裝的 BodyStyle.bare 預設值。

服務實作很簡單 ; 它只是資料流,以回應在 Silverlight 用戶端要求 clientaccesspolicy.xml。 原則檔案名稱可以是您自己的選擇,而且您可以任何您要呼叫它。 原則服務的實作如 [圖 7 ] 所示。

現在我們必須設定 [IPolicyService REST-樣式的 HTTP 要求的服務的。 主控台應用程式 (ConsoleWebServices) 的 app.config 如 [圖 8 ] 所示。 有一些有關特殊組態需要注意的事情: 具有設定為 webHttpBinding ConsoleWebServices.IPolicyServer 端點的繫結。 而且,IPolicyService 端點行為應該設定 WebHttpBehavior 與組態檔中所示。 在 PolicyService 的基底位址應該設為根目錄 (為中的 http://localhost:3045/) URL,並且將端點位址應保留空白 (例如。

[圖 8 自訂裝載環境的 WCF 設定

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <!-- IPolicyService end point should be configured with 
           webHttpBinding-->
      <service name="ConsoleWebServices.PolicyService">
         <endpoint address="" 
               behaviorConfiguration="ConsoleWebServices.WebHttp"
               binding="webHttpBinding" 
               contract="ConsoleWebServices.IPolicyService" />
         <host>
           <baseAddresses>
             <add baseAddress="http://localhost:3045/" />
           </baseAddresses>
         </host>
      </service>
      <service behaviorConfiguration="ConsoleWebServices.TimeServiceBehavior"
               name="ConsoleWebServices.TimeService">
         <endpoint address="TimeService" binding="basicHttpBinding" 
               contract="ConsoleWebServices.ITimeService">
         </endpoint>
         <host>
            <baseAddresses>
              <add baseAddress="http://localhost:3045/TimeService.svc" />
            </baseAddresses>
         </host>
       </service>
     </services>
     <behaviors>
        <endpointBehaviors>
          <!--end point behavior is used by REST endpoints like 
              IPolicyService described above-->
          <behavior name="ConsoleWebServices.WebHttp">
            <webHttp />
          </behavior>
        </endpointBehaviors>
       ... 
      </behaviors>
    </system.serviceModel>
</configuration>

最後,在主控台的主控服務,(例如,程式碼範例,以及的組態中顯示 [TimeService 應該設定將 URL,看起來類似 IIS 對應。 例如,HTTP 可能看起來如下所示的預設值上的 IIS 裝載 TimeService] 端點的 URL: http://localhost/TimeService.svc。 在這種情況下可以從 http://localhost/TimeService.svc?WSDL 取得中繼資料。

不過,在主機的主控台的情況下中, 繼資料可以取得藉由附加 「? WSDL 」 至服務主應用程式的基底位址。 在 [圖 8 ] 所示的組態,即可查看的 [TimeService 的基底位址設定為 http://localhost:3045/TimeService.svc],[可因此從 http://localhost:3045/TimeService.svc?WSDL 取得中繼資料。

這個 URL,就像我們在裝載的 IIS 中使用。 如果您將主應用程式基底位址設定 http://localhost:3045/TimeService.svc/ 則中繼資料 URL 會是看起來有點奇怪的 http://localhost:3045/TimeService.svc/?WSDL。 因此小心為這個行為,它可以節省您的理解中繼資料 (Metadata) URL 的時間。

在 IIS 內裝載的服務的跨網域原則

討論先前部署的 IIS 裝載服務的跨網域原則是簡單: 您只將 clientaccesspolicy.xml 檔案複製到 Web 服務裝載在其的網站的根目錄。 如您所見 [圖 1 ] 中,Silverlight 的應用程式就會裝載在 adv­callclientweb (localhost:1041) 上,並從 AdvBusinessServices (localhost:1043) 存取商務服務。 Silverlight Runtime,需要 clientaccesspolicy.xml 以 [圖 9 ] 所示的程式碼部署在 adv­BusinessServices 網站的根目錄。

[圖 9 Clientaccesspolicy.xml,IIS 裝載的 Web 服務

<?xml version="1.0" encoding="utf-8"?>
<access-policy>
  <cross-domain-access>
    <policy>
      <allow-from http-request-headers="*">
        <!--allows the access of Silverlight application with localhost:1041
           as the domain of origin-->  
        <domain uri="http://localhost:1041"/>
        <!--allows the access of call simulator Silverlight application
           with localhost:1042 as the domain of origin-->  
        <domain uri="http://localhost:1042"/>
      </allow-from>
      <grant-to>
        <resource path="/" include-subpaths="true"/>
      </grant-to>
    </policy>
  </cross-domain-access>
</access-policy>

如果您還記得,這個系列的第一個單元通訊端伺服器 (advpolicyserver) 的跨網域原則格式,<allow-from> 的格式為類似的。 不同之處在 <grant-to> 一節的通訊端伺服器需要以連接埠範圍和通訊協定屬性的一個 <socket-resource>] 設定,如下所示:

<grant-to>
  <socket-resource port="4530" protocol="tcp" />
</grant-to>

如果您建立使用 ASP.NET Web 網站範本的 WCF 服務裝載網站,並稍後加入 WCF 端點時,測試 Web 伺服器會對應至的名稱,專案的虛擬目錄 (例如 「 / AdvBusinessServices 」)。 這應該變更以 「 / 」 在專案的屬性頁中,讓 clientaccesspolicy.xml 可提供從根目錄。 如果您不要變更此,the clientaccesspolicy.xml 將無法在根目錄,並且 Silverlight 應用程式在存取服務時,會出現伺服器錯誤。 請注意這將不會是以 WCF Web 服務專案範本建立的 Web 網站的問題。

[圖 10 登入控制項,使用 PasswordBox

<UserControl x:Class="AdvCallCenterClient.Login">
  <Border x:Name="LayoutRoot" ... >
    <Grid x:Name="gridLayoutRoot">
     <Border x:Name="borderLoginViw" ...>
       <TextBlock Text="Pleae login.." Style="{StaticResource headerStyle}"/>
       <TextBlock Text="Rep ID" Style="{StaticResource labelStyle}"/>
       <TextBox x:Name="txRepID" Style="{StaticResource valueStyle}"/>
       <TextBlock Text="Password" Style="{StaticResource labelStyle}"/>
       <PasswordBox x:Name="pbPassword" PasswordChar="*"/>
       <HyperlinkButton x:Name="hlLogin" Content="Click to login"  
            ToolTipService.ToolTip="Clik to login" Click="hlLogin_Click" />
     </Border>
     <TextBlock x:Name="tbLoginStatus" Foreground="Red" ... />
      ...
</UserControl>

public partial class Login : UserControl
{
  public Login()
  {
    InitializeComponent();
  }
  public event EventHandler<EventArgs> OnSuccessfulLogin;
  private void hlLogin_Click(object sender, RoutedEventArgs e)
  {
    //validate the login
    AuthenticationProxy.AuthenticationServiceClient authService 
                  = new AuthenticationProxy.AuthenticationServiceClient();
    authService.LoginCompleted += new 
                EventHandler< AuthenticationProxy.LoginCompletedEventArgs>
                                           (authService_LoginCompleted);
    authService.LoginAsync(this.txRepID.Text, this.pbPassword.Password, 
                                                          null, false);     
  }

  void authService_LoginCompleted(object sender, 
                           AuthenticationProxy.LoginCompletedEventArgs e)
  {
    if (e.Result == true)
    {
       if (OnSuccessfulLogin != null)
          OnSuccessfulLogin(this, null);
    }
    else
    {
      this.tbLoginStatus.Text = "Invalid user id or password";
    }

  }
}

應用程式的安全性

其中一個重要的需求,LOB 應用程式的驗證 ; 呼叫中心代理程式開始移位之前,他將提供使用者 ID 和密碼驗證。 在 ASP.NET Web 應用程式中中, 這可以輕鬆辦到利用的成員資格提供者和伺服器端 ASP.NET 登入控制項。 在 Silverlight 中,,有強制執行驗證兩種方法: 之外的驗證和內部的驗證。

外部驗證是非常直接了當也是類似的 ASP.NET 應用程式驗證實作。 這的種作法驗證發生在 ASP.NET Web 網頁中會顯示 Silverlight 應用程式之前。 Silverlight 應用程式會載入,或透過自訂 Web 服務呼叫 (擷取驗證的狀態資訊) 之後,應用程式會載入之前,驗證內容可以傳輸到 Silverlight 應用程式透過 InitParams 參數。

Silverlight 應用程式會是較大的 ASP.NET / HTML 架構系統的一部分時,這個方法便會有其位置。 不過,在其中 Silverlight 是主應用程式的驅動程式的情況下,很自然執行驗證,在 Silverlight。 我將使用 Silverlight 2 PasswordBox 控制項,以擷取密碼] 和 [驗證使用 ASP.NET AuthenticationService WCF 端點,來驗證使用者的認證。 AuthenticationService 」、 「 ProfileService 和 「 RoleService 都是的新的一部分 namespace—System.Web.ApplicationServices—which 是.NET Framework 3.5 的新。 [圖 10 ] 顯示這個目的而建立的登入控制項的 XAML。 在登入控制項會呼叫 ASP.NET AuthenticationService.LoginAsync(),使用者 ID 和已輸入的密碼。

fig11.gif

[圖 11 登入自訂 Silverlight 控制項

呼叫中心, [圖 11 ,] 所示的登入螢幕會不複雜,但示範的用途。 我實作處理常式處理 LoginCompleted 事件,在控制項,以便它是獨立的以顯示無效的登入訊息,並將密碼重設的 dialogues 的複雜的實作。 在一個成功的登入將會觸發事件 OnSuccessfulLogin 告知父控制項 (在本例為 Application.RootVisual) 顯示填入使用者的資訊的第一個應用程式畫面。

位於主要 Silverlight 的頁面內 LoginCompleted (ctrlLoginView_OnSuccessfulLogin) 處理常式會叫用如 [圖 12 ] 所示,商務服務網站上裝載服務設定檔。 預設的 AuthenticationService 未對應至任何的.svc 端點,因此,我會將對應.svc 檔案至在實際的實作如下所示:

<!-- AuthenticationService.svc -->
<%@ ServiceHost Language="C#" Service="System.Web.ApplicationServices.  
    AuthenticationService" %>

圖 12 的內部,Page.xaml Login.xaml 的使用方式

<!-- Page.xaml of the main UserControl attached to RootVisual-->
<UserControl x:Class="AdvCallCenterClient.Page" ...>
   <page:Login x:Name="ctrlLoginView" Visibility="Visible"   
         OnSuccessfulLogin="ctrlLoginView_OnSuccessfulLogin"/>
   ...
</UserControl>
<!-- Page.xaml.cs of the main UserControl attached to RootVisual-->
public partial class Page : UserControl
{       
   ... 

   private void ctrlLoginView_OnSuccessfulLogin(object sender, EventArgs e)
   {
     Login login = sender as Login;
     login.Visibility = Visibility.Collapsed;
     CallBusinessProxy.UserProfileClient userProfile 
                           = new CallBusinessProxy.UserProfileClient();
     userProfile.GetUserCompleted += new  
     EventHandler<GetUserCompletedEventArgs>(userProfile_GetUserCompleted);
     userProfile.GetUserAsync(login.txRepID.Text);
   }
   ... 
   void userProfile_GetUserCompleted(object sender, 
                                             GetUserCompletedEventArgs e)
   {
     CallBusinessProxy.User user = e.Result;
     UserToBindableUser utobu = new UserToBindableUser(user);
     ClientGlobals.currentUser = utobu.Translate() as ClientEntities.User;
     //all the time the service calls will be complete on a worker thread 
     //so the following check is redunant but done to be safe
     if (!this.Dispatcher.CheckAccess())
     {
       this.Dispatcher.BeginInvoke(delegate()
       {
         this.registrationView.DataContext = ClientGlobals.currentUser;
         this.ctrlLoginView.Visibility = Visibility.Collapsed;
         this.registrationView.Visibility = Visibility.Visible;
       });
      }
    }
}

Silverlight 只能呼叫已設定為由指令碼的環境,例如 AJAX 呼叫 Web 服務。 像所有 AJAX 呼叫服務 AuthenticationService 服務會需要存取 ASP.NET 執行階段環境。 我提供 <system.servicemodel> 節點下,直接設定這項存取。 為了驗證服務,會呼叫由 Silverlight 登入處理程序 (或由 AJAX 呼叫),web.config 必須設定,根據中的指示,以" HOW TO: 啟用 WCF 的驗證服務." 服務將會自動設定的 Silverlight 如果它們建立使用 Silverlight 啟用 WCF 服務範本位於 [Silverlight] 類別目錄。

[圖 13 ] 顯示編輯組態與重要的項目所需驗證服務。 除了服務組態,我也被取代 aspnetdb 儲存驗證資訊的 SQL Server 組態的設定。 Machine.config 會定義為預期是內嵌到網站的 App_Data 目錄的 asp­netdb.mdf LocalSqlServer 設定。 此組態設定會移除預設的設定,並指出它連接到 SQL Server 執行個體,aspnetdb。 這輕鬆地可以變更為指向不同的電腦上執行之執行資料庫的執行個體。

[圖 13 個設定 ASP.NET 驗證服務

//web.config
<Configuration>  
  <connectionStrings>
  <!-- removal and addition of LocalSqlServer setting will override the   
   default asp.net security database used by the ASP.NET Configuration tool
   located in the Visul Studio Project menu-->
  <remove name="LocalSqlServer"/>
    <add name="LocalSqlServer" connectionString="Data 
             Source=localhost\SqlExpress;Initial Catalog=aspnetdb; ... />
</connectionStrings>
<system.web.extensions>
   <scripting>
     <webServices>
   <authenticationService enabled="true" requireSSL="false"/>
     </webServices>
   </scripting>
</system.web.extensions>
... 
<authentication mode="Forms"/>
... 
<system.serviceModel>
   <services>
     <service name="System.Web.ApplicationServices.AuthenticationService" 
              behaviorConfiguration="CommonServiceBehavior">
    <endpoint 
              contract="System.Web.ApplicationServices.AuthenticationService" 
              binding="basicHttpBinding" bindingConfiguration="useHttp" 
              bindingNamespace="http://asp.net/ApplicationServices/v200"/>
     </service>
   </services>
   <bindings>
     <basicHttpBinding>
    <binding name="useHttp">
          <!--for production use mode="Transport" -->
      <security mode="None"/>
     </binding>
     </basicHttpBinding>
   </bindings>
   ... 
   <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
</system.serviceModel>
</configuration>

要保留的登入控制項封裝 (Encapsulation),並保持連接與父控制項的設計階段鬆散,成功的登入處理程序是由觸發 OnSuccessfulLogin 事件傳送。 在 application.RootVisual (這是頁面類別) 會執行必要的商務程序以顯示第一個在成功的登入畫面。 第一個成功的登入會是的 registrationView,如所示的 [圖 12 userProfile_GetUserCompleted 方法之後,顯示的畫面中。 之前這個檢視會顯示先我將會呼叫 CallBusinessProxy.UserProfileClient.GetUserAsync(),以擷取使用者資訊。 請注意非同步的服務呼叫,類似於商務服務整合,我稍後會討論。

要注意先前的組態不使用 Secure Sockets Layer (SSL),因為您必須修改的生產系統在建置時,使用 SSL。

fig14.gif

[圖 14 OrderDetails.xaml 控制項,以填入的 Order Details 資料

應用程式磁碟分割

其中一個因素造成 Silverlight 應用程式的啟動時間是初始套件的大小。 XAP 封裝的大小準則是 Web 應用程式的頁面重量比不不同的。 頻寬會是有限的資源。 Web 應用程式的嚴格的回應時間會需要您請密切注意 Silverlight 應用程式的啟動時間。

除了處理時間會顯示第一個的 UserControl 之前,應用程式封裝的大小也會有直接影響應用程式的這個重要的品質。 為了改善啟動速度,您必須避免有數十名 MB 大小的複雜的應用程式能增加的整合型 XAP 檔案。

Silverlight 應用程式可以是 XAP 檔案的集合,個別的 DLL,或個別的 XML 檔案、 影像,和任何其他型別與可辨認的 MIME 類型的細分。 呼叫中心的案例中以示範細微化應用程式磁碟分割,我將部署 OrderDetail Silverlight 控制項為個別的 DLL (AdvOrderClientControls.dll) 一起 AdvCallCenterClient.xap 到 ClientBin AdvCallClientWeb 專案的目錄 (請參閱上一步 [圖 1 )。

DLL 將會下載預先背景工作執行緒上時,代理程式會接受傳入的呼叫。 您會看到 [圖 4 —ThreadPool.Queue­UserWorkItem (ExecuteControlDown­load) 中呼叫 — 負責這。 一旦呼叫者為您解答安全性問題,我將使用反映 (Reflection) 建立 OrderDetail 控制項的執行個體並將它至控制項樹狀結構顯示它在螢幕上之前。 [圖 14] 顯示 OrderDetail.xaml 控制項順序的詳細資料填入 (Populate) 載入至控制項樹狀結構。

包含控制項部署至相同的網站為呼叫的是典型的 DLL 屬於相同的應用程式的中心用戶端,OrderDetail DLL 所以我不會遇到任何跨網域問題在這種情況下。 不過,這可能無法使用服務,大小寫,因為 Silverlight 應用程式可能會存取部署在包括本機的多個定義域和雲架構圖表中所示的服務 (一次,參考到 [圖 1 )。

ExecuteControlDownload 方法 (請參閱 [圖 4 ) 在背景工作執行緒上執行,並下載在 DLL 中使用 WebClient 類別中。 WebClient,根據預設,假設下載是發生從原始的網域,因此只會使用相對的 URI。

OrderDetailControlDownloadCallback 處理常式會接收 DLL 資料流,並建立使用 [圖 15 ] 所示的 ResourceUtility.GetAssembly() 組件。 因為建立的組件必須在 UI 執行緒上發生的我會發送,GetAssembly() 和組件至全域變數至 UI 執行緒,(安全執行緒) 指派:

void OrderDetailControlDownloadCallback(object sender,
       OpenReadCompletedEventArgs e)
  {
    this.Dispatcher.BeginInvoke(delegate() {
    Assembly asm = ResourceUtility.GetAssembly(e.Result);
    Interlocked.Exchange<Assembly>(ref 
        ClientGlobals.advOrderControls_dll, asm ); });
  }

[圖 15 個公用程式函式,以擷取資源

public class ResourceUtility
{ 
  //helper function to retrieve assembly from a package stream
  public static Assembly GetAssembly(string assemblyName, Stream 
                                                        packageStream)
  {
    StreamResourceInfo srInfo =
    Application.GetResourceStream(
              new StreamResourceInfo(packageStream, "application/binary"),
              new Uri(assemblyName, UriKind.Relative));
    return GetAssembly(srInfo.Stream);
  }
  //helper function to retrieve assembly from a assembly stream
  public static Assembly GetAssembly(Stream assemblyStream)
  {
    AssemblyPart assemblyPart = new AssemblyPart();
    return assemblyPart.Load(assemblyStream);
  }
  //helper function to create an XML document from the stream
  public static XElement GetXmlDocument(Stream xmlStream)
  {
    XmlReader reader = XmlReader.Create(xmlStream);
    XElement element = XElement.Load(reader);
    return element;
  }
  //helper function to create an XML document from the default package
  public static XElement GetXmlDocumentFromXap(string fileName)
  {
    XmlReaderSettings settings = new XmlReaderSettings();
    settings.XmlResolver = new XmlXapResolver();
    XmlReader reader = XmlReader.Create(fileName);
    XElement element = XElement.Load(reader);
    return element;
  }
  //gets the UIElement from the default package
  public static UIElement GetUIElementFromXaml(string xamlFileName)
  {
    StreamResourceInfo streamInfo = Application.GetResourceStream(new 
                                  Uri(xamlFileName, UriKind.Relative));
    string xaml = new StreamReader(streamInfo.Stream).ReadToEnd();
    UIElement uiElement = null;
    try
    {
      uiElement = (UIElement)XamlReader.Load(xaml);
    }
    catch
    {
      throw new SLApplicationException(string.Format("Can't create 
                                  UIElement from {0}", xamlFileName));
    }
    return uiElement;
  }
}

由於在不同的執行緒,比回呼處理常式上執行分派的委派,您必須是從匿名委派所存取的物件的狀態的瞭解。 在先前的程式碼的已下載的 DLL 資料流狀態都是真正重要的。 不,您可能會撰寫程式碼,回收 OrderDetailControlDownloadCallback 函式內的資料流的資源。 UI 執行緒有機會建立組件之前,這類程式碼會提前下載的資料流的處置。 我會使用反映 (Reflection) 建立 [OrderDetail 使用者控制項的執行個體中,並加入至台如以下所示:

_orderDetailContol = ClientGlobals.advOrderControls_dll.CreateInstance
                  ("AdvOrderClientControls.OrderDetail") as UserControl;
spCallProgressPanel.Children.Add(_orderDetailContol);

在 ResourceUtility[圖 15也會顯示不同的公用程式函式,從 [XAML] 和 [XML 文件從下載的資料流和預設的封裝中解壓縮 UIElement。

生產力和 Beyond

我已探討 Silverlight 從傳統的企業應用程式觀點來看,涉及許多層面架構應用程式。使用 Silverlight 的通訊端的推入通知的實作會為的 LOB 案例的 Enabler 例如呼叫中心。即將推出的 Internet Explorer 8.0-的預定包含六個同時的 HTTP 連線,主機每 — 使用雙工的 WCF 繫結時,推入通知實作,透過網際網路將會是更吸引人。與 LOB 資料和程序整合是一樣容易,因為它是在傳統的桌上型電腦應用程式中大小寫。

這會是大型的生產力提升比較 AJAX 與其他豐富的網際網路應用程式 (RIA) 的平台時。Silverlight 應用程式,可以使用 ASP.NET 的最新版本中提供 WCF 驗證和授權端點來保護。我希望使用 Silverlight 的 LOB 應用程式開發的這個小探索將激勵您 Silverlight 媒體和廣告案例之外。

Hanu Kommalapati 是平台策略顧問 Microsoft,和這個角色,他建議建置可擴充的 Line-of-Business 應用程式,在 Silverlight 和 Azure 服務的平台上的企業客戶。