Este artigo foi traduzido por máquina.

O Silverlight

Criar linha de negócios da empresa aplicativos com o Silverlight, parte 2

Hanu Kommalapati

Este artigo discute:

  • O ambiente de tempo de execução do Silverlight
  • Programação assíncrona do Silverlight
  • Diretivas de domínio cruzado
  • Um aplicativo de empresa de exemplo
Este artigo usa as seguintes tecnologias:
O Silverlight 2

Download do código disponível na Galeria de código do MSDN
Procure o código on-line

Conteúdo

Integração com o Business Services
Chamada de serviço
Chamadas de serviço sincronizadas
Conversão de entidade de mensagem
Alteração de estado de Silverlight após as chamadas de serviço
Diretivas de entre domínios
Entre as diretivas de domínio para Web Services IIS hospedado externa
As diretivas de entre domínios para os serviços hospedados dentro do IIS
Segurança de aplicativo
Particionamento de aplicativo
Produtividade e posteriores

Durante a primeira parte desta série, Eu introduziu um cenário de centro de chamada e mostrou uma implementação de preenchimento de tela (tela pop) por meio dos soquetes conectados que utilizados nos soquetes TCP assíncronos Silverlight oferece suportados (por favor, consulte"Criar linha de negócios da empresa aplicativos com o Silverlight, parte 1").

O pop de tela foi implementada por meio de um distribuidor de chamada simulada que retirados uma chamada de uma fila interna e pressionado notificações através da conexão soquete aceita anteriormente em cache em uma lista genérica no servidor. Aqui eu irá concluir por implementar a segurança de aplicativo, integração com serviços comerciais e implementar diretivas de domínio cruzado para serviços de Web e a partição de aplicativo. A arquitetura lógica do aplicativo chamada centro é mostrada na Figura 1 . O serviço de autenticação será ser implementado no serviço utilitário enquanto os serviços de negócios, ICallService e IUserProfile, serão implementados dentro do projeto serviço comercial, como o nome sugere.

fig01.gif

Figura 1 arquitetura lógica do Silverlight Call Center

Mesmo que o diagrama mostra eventos de fluxo contínuo aos serviços de utilitário, o intuito de tempo, a demonstração de download não inclui essa funcionalidade. A implementação do recurso de serviço de captura de evento será semelhante à implementação de serviços de negócios. No entanto, eventos de negócios que não são erros críticos podem ser armazenadas em cache localmente no armazenamento isolado e despejados logon no servidor em um modo em lotes. VOU começar a discussão com a implementação de serviços comerciais e terminam com a partição de aplicativo.

Integração com o Business Services

Integração com os serviços é um dos aspectos importantes de um aplicativo de (LOB) de linha de negócios, e o Silverlight fornece um amplo componentes para acessar recursos com base na Web e serviços. Infra-estrutura proxy de HttpWebRequest, o WebClient e o WCF (Windows Communication Foundation) são alguns dos componentes rede normalmente usados para interação com base em HTTP. Neste artigo, VOU usar os serviços WCF para integrar processos de negócios de back-end.

Maioria de nós usar serviços de Web para integração com as fontes de dados back-end durante o desenvolvimento de aplicativos; acesso ao serviço da Web WCF com o Silverlight não é muito diferente com aplicativos tradicionais, como aplicativos ASP.NET, Windows Presentation Foundation (WPF) ou Windows Forms. As diferenças são o suporte de ligação e o modelo de programação assíncrona. O Silverlight oferecerá suporte apenas basicHttpBinding e PollingDuplexHttpBinding. Observe que HttpBinding é a ligação mais interoperável. Por esse motivo, VOU usá-lo para todos os integração neste artigo.

PollingDuplexHttpBinding permite o uso de contratos de retorno de chamada para enviar notificações sobre HTTP. Meu call center poderia ter usado essa ligação para notificações pop de tela. No entanto, a implementação requer o armazenamento em cache da conexão HTTP no servidor, assim, monopolizando uma das duas conexões HTTP simultâneas permitidas pelo navegadores como o Internet Explorer 7.0. Isso pode causar problemas de desempenho, pois todo o conteúdo da Web será precisam ser serializados através de uma conexão. Internet Explorer 8.0 permite seis conexões simultâneas por domínio e resolverá esses problemas de desempenho. (Notificações de envio usando PollingDuplexHttpBinding poderia ser um tópico para um artigo futuro quando 8.0 do Internet Explorer está amplamente disponível.)

Volta para o aplicativo. Quando o agente aceita uma chamada, o processo de tela pop preenche a tela com as informações do chamador — nesse caso, os detalhes da ordem do chamador. As informações do chamador devem conter informações necessárias para identificar exclusivamente a ordem em que o banco de dados back-end. Para esse cenário de demonstração, será Suponho que o número da ordem foi falado no sistema interativo de voz (IVR) de resposta. O aplicativo de Silverlight irá chamar WCF Web services com o número de ordem como o identificador exclusivo. A definição do contrato de serviço e a implementação é mostrado na Figura 2 .

A Figura 2 Business Service implementação

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>

A implementação dessas empresas de serviço não é realmente muito interessante, pois são implementações de WCF simples. Para o bem da simplicidade, eu não usará nenhum banco de dados para entidades comerciais mas usará objetos da lista na memória para armazenar objetos cliente, pedido e usuário. A classe de DataUtil (não mostrada aqui mas disponível no download de código) encapsula o acesso a esses objetos da lista na memória.

fig03.gif

A Figura 3 scripts de Agent com perguntas sobre segurança

Empresas de serviço WCF para consumo do Silverlight precisam acesso ao pipeline de ASP.NET e, portanto, requerem o atributo AspNetCompatibilityRequirements na implementação CallService. <servicehostingenvironment/>Isso tem a ser correspondido pela configuração no arquivo web.config.

Como mencionado anteriormente, o Silverlight somente oferece suporte à basicHttpBinding e PollingDuplexHttpBinding. Se você usar o modelo WCF Service Visual Studio, ele configura a ligação de ponto de extremidade para wsHttpBinding, que deve ser alterada manualmente a basicHttpBinding para o Silverlight possa adicionar uma referência de serviço para geração de proxy. O ASP.NET que hospeda as alterações de compatibilidade e alterações de ligação será automaticamente ser cuidar de se CallService.svc for adicionado ao projeto AdvBusinessServices usando um modelo de habilitado Silverlight WCF Service Visual Studio.

Chamada de serviço

Ter implementado um serviço que pode ser chamado do Silverlight, agora é hora para criar proxies de serviço e usá-los para conectar a interface do usuário para as implementações de serviço de back-end. Você pode apenas gerar proxies para os serviços WCF confiável usando Service References | seqüência de Add Service Reference no Visual Studio. Os proxies na minha demonstração foram gerados no namespace CallBusinessProxy. O Silverlight só permite chamadas assíncronas aos recursos da rede e invocação de serviço não é exceção. Quando uma chamada de cliente, o cliente do Silverlight será ouvir a notificação e exibirá uma caixa de diálogo Aceitar/Rejeitar.

Depois que uma chamada for aceita pelo agente, a próxima etapa no processo é chamar o serviço da Web para recuperar o script de agente que corresponde à situação de chamada. Para esta demonstração, estarão apenas usando um script como exibido na Figura 3 . O script exibido contém uma saudação, bem como uma lista de questões de segurança. O agente garantirá que um número mínimo de perguntas sejam respondido antes de mover frente com a Ajuda.

O script de agente é recuperado, acessando o ICallService.Get­AgentScript(), fornecendo o número da ordem como entrada. Consistente com o modelo de programação assíncrona aplicado pela pilha de serviços da Web do Silverlight, o GetAgentScript() está disponível como CallServiceClient.BeginGetAgentScript(). Ao fazer a chamada de serviço, você precisará fornecer um manipulador de retorno de chamada, Get­AgentScriptCallback, como mostrado na Figura 4 .

A Figura 4 chamada de serviço e alteração de interface do usuário do Silverlight

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));
   }
   ... 
}

Desde que o resultado da chamada serviço só pode ser recuperado do manipulador de retorno de chamada, quaisquer alterações o estado de aplicativo do Silverlight terá que ocorrem no manipulador de retorno de chamada. CallServiceClient.BeginGetAgentScript() é chamado pela _notifyCallPopup_OnAccept em execução no segmento de interface do usuário e coloca a solicitação assíncrona e retorna imediatamente para a próxima instrução. Desde que o script de agente não é ainda disponível, você precisará aguardar o retorno de chamada é disparado antes de você cache o script e dados ligá-la para a interface do usuário.

Conclusão bem-sucedida do serviço de chamada disparadores GetAgentScriptCallback, que recupera o script de agente, preenche uma variável global e ajusta a interface do usuário por dados vinculando o script de agente aos elementos de interface do usuário apropriados. Ao ajustar a interface do usuário, o GetAgentScriptCallback irá se certificar que ele será atualizado no thread da interface do usuário por meio do uso de Dispatcher.CheckAccess().

UIElement.Dispatcher.CheckAccess() comparará a identificação de segmento de interface do usuário com que o thread de trabalho e retornar true se ambos os segmentos são as mesmas; caso contrário, ela retorna false. Quando Get­AgentScriptCallback executa em um segmento de trabalho (na verdade, já que isso sempre irá executar em um thread de trabalho você poderia simplesmente chamar Dispatcher.BeginInvoke), CheckAccess() retornará false e a interface do usuário será atualizado por distribuir um delegado anônimo por meio de Dispatcher.Invoke().

Chamadas de serviço sincronizadas

Devido à natureza assíncrona do ambiente de rede do Silverlight, é quase impossível fazer um serviço assíncrono chamar no segmento de interface do usuário e esperar para que ele concluir com a intenção de alterar o estado de aplicativo com base nos resultados da chamada. Na Figura 4 , _notifyCallPopup_OnAccept precisa recuperar detalhes de pedidos, transformar a mensagem de saída em uma entidade de cliente e salvá-lo em uma variável global de uma forma thread-safe. Para fazer isso, um pode ser tempted escrever o código de manipulador conforme mostrado aqui:

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

Mas esse código irá congelar o aplicativo quando ele acessa a instrução this._orderDetailDownloadHandle.WaitOne(). Isso ocorre porque a instrução WaitOne() bloqueia o segmento de interface do usuário receba as mensagens expedidas de outros threads. Em vez disso, você pode agendar o thread de trabalho para executar a chamada de serviço, aguarde a chamada para concluir e concluir o processamento de postagem da saída do serviço em sua totalidade no segmento de trabalho. Essa técnica é mostrada na Figura 5 . Para impedir o uso inadvertido de bloqueio de chamadas no segmento de interface do usuário, quebradas ManualResetEvent dentro de um SLManualResetEvent personalizado e teste para segmento de interface do usuário quando uma chamada para WaitOne() é feita.

Detalhes do pedido recuperar a Figura 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();
  }

Como SLManualResetEvent é uma classe de uso geral, você não pode depender o Dispatcher.CheckAccess() de um controle específico. ApplicationHelper.IsUiThread() pode verificar Application.RootVisual.Dispatcher.CheckAccess(); no entanto, acesso a esse método irá disparar uma exceção de acesso de thread cruzado em inválido. Portanto, a forma só confiável de testar isso em um trabalho segmento, quando não houver nenhum acesso a uma instância de UIElement, é usar Deployment.Current.Dispatcher.CheckAccess() conforme mostrado aqui:

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

Para a execução de plano de fundo de tarefas, em vez de usar ThreadPool.QueueUserWorkItem, você pode usar BackGroundWorker, que também usará o ThreadPool, mas permite que você conectar manipuladores que podem executar o segmento de interface do usuário. Esse padrão permite a execução de vários serviços chamadas em paralelo e aguarda a todas as chamadas concluir usando SLManualResetEvent.WaitOne() antes dos resultados são agregados para processamento adicional.

Conversão de entidade de mensagem

O GetAgentScriptCallback também converte as entidades de mensagem de saída (também conhecido como DataContracts) do serviço em uma entidade do lado do cliente que representa a semântica de uso do lado do cliente. Por exemplo, o design de entidades de mensagem do lado do servidor não pode cuidado sobre vinculação de dados enquanto prestando atenção à natureza multiuse o serviço que será têm que atender a uma ampla variedade de usos, não apenas a Central de atendimento.

Além disso, ele é uma boa prática não têm o acoplamento forte com as entidades de mensagem, porque as alterações para as entidades mensagem não serão dentro do controle do cliente. A prática de tradução de entidades de mensagem para entidades do lado do cliente não é aplicável somente ao Silverlight, mas é geralmente aplicável para qualquer consumidor de serviço da Web quando aguardando evitar o tempo de design grande união.

Decidi manter a implementação dos tradutores de entidade muito simples — sem exóticos genéricos aninhados, as expressões lambda ou inversão de recipientes de controle. ClientEntityTranslator é uma classe abstrata que define o método ToClientEntity(), que deve substituir cada subclasse:

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

Cada classe filha é exclusivo para um tipo de troca de serviço; portanto, criarei tradutores quantos conforme necessário. Na minha demonstração, fez três tipos de chamadas de serviço: IUserProfile.GetUser(), ICallService.GetAgentScript() e ICallService.GetOrderDetails(). Portanto, criei três conversores, conforme mostrado na Figura 6 .

A Figura 6 entidade de mensagem para o conversor de entidade do lado do cliente

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  ...
    }
}

Se você observado, os tradutores acima são sem monitoração de estado e empregam um padrão de singleton. O conversor deve ser capaz de herdar ClientEntityTranslator para verificar a consistência e precisa ser um singleton para evitar a coleta de lixo agitações.

Eu manter reutilizar a mesma instância sempre que a chamada do respectivo serviço é feita. EU também poderia criar ServiOutputEntityTranslator para interação do serviço que requer a mensagens de entrada grandes (que geralmente é o caso de invocação de serviço transacional) com a seguinte definição de classe:

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

Se você observar o valor de retorno da função acima, é o " objeto", como não controlar a classe base das entidades mensagem (nesta demonstração, que eu poderia, mas não no mundo real). A segurança de tipo será implementada pelos tradutores respectivos. Para simplificar de demonstração, não salvar os dados novamente no servidor, para esta demonstração não inclui os conversores para converter as entidades do cliente para entidades de mensagem.

Alteração de estado de Silverlight após as chamadas de serviço

Alteração de estado visual do Silverlight somente pode ser executada, o código executando no thread da interface do usuário. Como a execução assíncrona do serviço chama sempre retorna os resultados no manipulador de retorno de chamada, o manipulador será o lugar certo para alterar o estado visual ou não-visuais do aplicativo.

Alterações de estado não-visuais devem ser trocadas em uma forma thread-safe se existe pode vários serviços tentando modificar o estado compartilhado de forma assíncrona. É sempre recomendável que você verificar Deployment.Current.Dispatcher.CheckAccess() antes de modificar a interface do usuário.

Diretivas de entre domínios

Ao contrário dos aplicativos de mídia e os aplicativos que mostram os anúncios em faixa, aplicativos de LOB de classe empresarial real requerem integração com uma variedade de serviço de hospedagem ambientes. Por exemplo, o aplicativo de Central chamada referido por todo o artigo é típico de aplicativo de negócios. Este aplicativo hospedado em um site da Web acessa um servidor de soquete com monitoração de estado para tela-pop, com base em WCF Web services para acessar dados LOB, e ele pode baixar pacotes XAP adicionais (pacotes de implantação de Silverlight compactado) de um domínio diferente. Ele usará ainda outro domínio para transmitir dados de instrumentação.

O seguro do Silverlight não por padrão permite acesso de rede a qualquer domínio seja o domínio de origem — advcallclientweb que você viu novamente na Figura 1 . O tempo de execução do Silverlight verifica as diretivas de opt-in quando o aplicativo acessa qualquer domínio seja o domínio de origem. Aqui está uma lista típica dos cenários de hospedagem de serviço que precisa oferecer suporte a solicitações de diretiva de domínio cruzado pelo cliente:

  • Serviços da Web hospedados em um processo de serviço (ou um aplicativo de console para manter a simplicidade)
  • Os serviços da Web hospedado no IIS ou outra Web servidores
  • Serviços TCP hospedados em um processo de serviço (ou um aplicativo de console)

Eu discutidas a implementação de diretiva de domínio cruzado para serviços TCP no mês passado e, portanto, concentraremos em serviços da Web hospedados em processos personalizados e dentro do IIS.

Embora seja simples de implementar diretivas de domínio cruzado para pontos de extremidade do serviço da Web hospedados no IIS, os outros casos exigem conhecimento da natureza das solicitações de diretiva e as respostas.

Entre as diretivas de domínio para Web Services IIS hospedado externa

Para gerenciamento de estado eficiente, pode haver casos em que um talvez queira hospedar serviços em um processo OS fora do IIS. Para acesso de domínio cruzado de tais serviços WCF, o processo terá às diretivas de host na raiz do ponto de extremidade HTTP. Quando um serviço da Web de domínio cruzado é chamado, o Silverlight faz uma solicitação HTTP GET para clientaccesspolicy.xml. Se o serviço estiver hospedado dentro do IIS, o arquivo client­accesspolicy.xml pode ser copiado para a raiz do site da Web e IIS fará o restante em servir o arquivo. No caso de hospedagem personalizada na máquina local, http://localhost:<port>/clientaccesspolicy.xml deve ser uma URL válida.

Desde que a demonstração do Centro de chamada não usa os serviços da Web hospedados personalizados, usarei um TimeService simples em um aplicativo de console para demonstrar os conceitos. O console será expor um ponto de extremidade de transferência (REST) representações de estado usando os novos recursos REST do Microsoft .NET Framework 3.5. Propriedade de UriTemplate tem que ser definida com precisão o literal mostrado na Figura 7 .

Implementação da Figura 7 para serviços WCF hospedados personalizado

[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;
    }
}

O nome de interface ou o nome do método tem não relação com o resultado; você pode escolher que desejar. WebInvoke tem outras propriedades, como RequestFormat e ResponseFormat, que são, por padrão definido como XML; nós não precisará especificá-los explicitamente. Nós também são contar com o valor padrão da propriedade BodyStyle a ser BodyStyle.bare, que significa que a resposta não ser empacotada.

A implementação de serviço é muito simples; ele simplesmente fluxos o clientaccesspolicy.xml em resposta a solicitação do cliente do Silverlight. O nome do arquivo de diretiva pode ser de sua escolha e você desejar pode chamá-lo. A implementação do serviço de diretiva é mostrada na Figura 7 .

Agora temos de configurar o IPolicyService para estilo de REST disponibilização de solicitações HTTP. O app.config do aplicativo de console (ConsoleWebServices) é mostrada na Figura 8 . Existem algumas coisas a Observação sobre a necessidade de configuração especial: a ligação o ponto de extremidade ConsoleWebServices.IPolicyServer precisa ser definido para webHttpBinding. Além disso, o comportamento de ponto de extremidade IPolicyService deve ser configurado com WebHttpBehavior conforme mostrado no arquivo de configuração. O endereço base do PolicyService deve ser definido para a URL raiz (como em http://localhost:3045/) e o endereço do ponto de extremidade deve ser deixado vazio (como.

Figura 8 Configurações do WCF para personalizar hospedando o ambiente

<?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>

Por fim, os serviços de console-hospedado, como o TimeService mostrada exemplos de código bem como a configuração, devem ser configurados com uma URL que se parece com suas contrapartes do IIS. Por exemplo, a URL de uma empresa TimeService hospedado pelo IIS padrão HTTP pode parecer da seguinte forma: http://localhost/TimeService.svc. Nesse caso, os metadados podem ser obtidos de http://localhost/TimeService.svc?WSDL.

No entanto, no caso de hospedagem do console, os metadados podem ser obtidos por meio do acréscimo "? WSDL " para o endereço base do host de serviço. Na configuração mostrada na Figura 8 , você pode ver que o endereço base do TimeService é definido como http://localhost:3045/TimeService.svc, portanto, os metadados podem ser obtido no http://localhost:3045/TimeService.svc?WSDL.

Essa URL é semelhante ao que usamos no IIS que hospeda. Se você definir o endereço base do host para http://localhost:3045/TimeService.svc/, em seguida, a URL de metadados será http://localhost:3045/TimeService.svc/?WSDL, que parece um pouco estranho. Portanto, cuidado para esse comportamento como ele pode economizar tempo em descobrir a URL de metadados.

As diretivas de entre domínios para os serviços hospedados dentro do IIS

Como foi discutido anteriormente, a implantação de diretivas entre domínios para os serviços hospedados pelo IIS é simples: você apenas copie o arquivo clientaccesspolicy.xml para a raiz do site no qual os serviços da Web são hospedados. Como você viu na Figura 1 , o aplicativo de Silverlight está hospedado em adv­callclientweb (localhost:1041) e acessa serviços comerciais de AdvBusinessServices (localhost:1043). O tempo de execução do Silverlight requer clientaccesspolicy.xml a serem implantados na raiz do site da Adv­BusinessServices com o código mostrado na Figura 9 .

A Figura 9 Clientaccesspolicy.xml para hospedados pelo IIS Web Services

<?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>

Se você se lembrar o formato de diretiva de domínio cruzado para o servidor de soquete (advpolicyserver) da primeira parte desta série, o formato de <allow-from> é semelhante. A diferença está na seção <grant-to> onde o servidor de soquete requer uma configuração <socket-resource> com atributos de intervalo e o protocolo da porta, como mostrado aqui:

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

Se você criar o site de hospedagem do serviço do WCF usando o modelo de site da Web do ASP.NET e adicionar pontos de extremidade do WCF posteriormente, o teste de servidor Web mapeará o diretório virtual para o nome do projeto (como " / AdvBusinessServices "). Isso deve ser alterado para "/" nas páginas de propriedades do projeto para que o clientaccesspolicy.xml podem ser atendidas da raiz. Se você não alterar isso, o clientaccesspolicy.xml não será na raiz, e aplicativos do Silverlight receberá erros de servidor quando o serviço for acessado. Observe que isso não será um problema para os sites criados usando o modelo de projeto serviço da Web do WCF.

Controle de login de Figura 10 usando 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";
    }

  }
}

Segurança de aplicativo

Um dos requisitos importantes de um aplicativo de LOB é autenticação; antes que o agente de centro de chamada possa começar a mudança, ele autenticará, fornecendo uma identificação de usuário e senha. Em aplicativos da Web do ASP.NET, isso pode ser feito facilmente, aproveitando o provedor de associação e os controles de login ASP.NET do lado do servidor. No Silverlight, há duas maneiras para impor a autenticação: autenticação fora e autenticação dentro.

Autenticação fora é muito simples e é semelhante à implementação de autenticação de aplicativos ASP.NET. Com essa abordagem, a autenticação ocorre em uma página de Web do ASP.NET-com base antes que o aplicativo de Silverlight seja exibido. O contexto de autenticação pode ser transferido para o aplicativo Silverlight através de parâmetro InitParams antes que um aplicativo do Silverlight seja carregado ou por meio de uma chamada de serviço da Web personalizada (para extrair as informações de estado de autenticação) após o aplicativo é carregado.

Essa abordagem tem seu lugar, quando o aplicativo de Silverlight faz parte de um sistema com base em ASP.NET/HTML maior. No entanto, nos casos em que o Silverlight é o driver principal do aplicativo, é natural para executar a autenticação no Silverlight. VOU usar o controle do Silverlight 2 PasswordBox para capturar a senha e autenticar usando o ponto de extremidade ASP.NET AuthenticationService WCF para validar as credenciais do usuário. AuthenticationService, ProfileService e RoleService são parte do novo namespace—System.Web.ApplicationServices—which foi novo com o .NET Framework 3.5. a Figura 10 mostra o XAML para o controle de login criado para esta finalidade. O controle de login chama AuthenticationService.LoginAsync() ASP.NET com o ID de usuário e senha que foi inserida.

fig11.gif

A Figura 11 login personalizado Silverlight controle

A tela de logon de central de atendimento, mostrado na Figura 11 , não é sofisticada, mas serve ao propósito de demonstração. Implementei um manipulador para lidar com o evento LoginCompleted dentro do controle para que ele é independente para exibir mensagens de logon inválidas e dialogues de redefinição de senha para sofisticados implementações. Após um logon bem-sucedido, o evento OnSuccessfulLogin será acionado para informar o controle pai (Application.RootVisual neste caso) para exibir a primeira tela de aplicativo preenchida com as informações do usuário.

O manipulador de LoginCompleted (ctrlLoginView_OnSuccessfulLogin) localizado dentro a principal página de Silverlight irá chamar o serviço de perfil hospedado no site do business serviços, como mostrado na Figura 12 . AuthenticationService por padrão não está mapeado para qualquer ponto de extremidade .svc; portanto, será mapeado arquivo .svc para a implementação física, como mostrado aqui:

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

A Figura 12 o uso de Login.xaml dentro a Page.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;
       });
      }
    }
}

O Silverlight só pode chamar serviços da Web que são configurados para ser chamado por ambientes de scripts, como AJAX. Como todos os serviços que pode ser chamado pelo AJAX, o serviço AuthenticationService precisa acessar o ambiente de tempo de execução do ASP.NET. EU fornecer esse acesso definindo diretamente sob o nó de <system.servicemodel>. Para que o serviço de autenticação para ser acessível pelo processo de logon do Silverlight (ou a ser chamado pelo AJAX), o arquivo web.config deve ser definida conforme como as instruções no " Como: ativar o serviço de autenticação do WCF." Os serviços serão automaticamente configurados para o Silverlight se eles forem criados usando o modelo do serviço de WCF de habilitado Silverlight localizado na categoria Silverlight.

a Figura 13 mostra a configuração editada com elementos importantes necessárias para o serviço de autenticação. Com a configuração do serviço, também substituídas a configuração do SQL Server para aspnetdb que armazena informações de autenticação. Machine.config define uma configuração LocalSqlServer que espera asp­netdb.mdf para ser incorporado no diretório App_Data do site da Web. Esta configuração remove a configuração padrão e aponta-lo para o aspnetdb anexado a instância do SQL Server. Isso pode ser alterado facilmente para apontar para uma instância de banco de dados em execução em um computador separado.

Figura 13 configurações para o serviço de autenticação do 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>

Para preservar o encapsulamento do controle Login e manter o tempo de design ampliada união com o controle pai, sucesso do processo de logon é comunicado por disparo o evento OnSuccessfulLogin. O Application.RootVisual (que é uma classe de página) executará o processo de negócios necessários para exibir a primeira tela após logon bem-sucedida. A primeira tela exibida após um logon bem-sucedido é registrationView, como mostra o método userProfile_GetUserCompleted da Figura 12 . Antes que este modo de exibição seja exibido, irá recuperar informações do usuário chamando CallBusinessProxy.UserProfileClient.GetUserAsync(). Por favor, anote a chamada de serviço assíncrono, que é semelhante à integração de serviço comercial que discutirei posteriormente.

Esteja ciente de que a configuração anterior não usa camada de soquetes (de segurança SSL); você deve modificá-lo para usar o SSL durante a criação de sistemas de produção.

fig14.gif

A Figura 14 controle OrderDetails.xaml com detalhes do pedido preenchidas

Particionamento de aplicativo

Um dos fatores que contribuem para o tempo de inicialização de aplicativo do Silverlight é o tamanho do pacote inicial. As diretrizes para o tamanho do pacote XAP não são diferentes do peso de página para aplicativos da Web. Largura de banda é um recurso limitado. Os horários de rigorosas resposta dos aplicativos da Web exigem que você preste atenção ao tempo de inicialização do aplicativo de Silverlight.

Juntamente com o tempo de processamento gasto antes que o primeiro UserControl seja exibido, o tamanho do pacote do aplicativo tem influência direta nessa qualidade importante do aplicativo. Para melhorar a velocidade de inicialização, você deve evitar arquivos XAP monolíticos que podem aumentar a dezenas de megabytes de tamanho para aplicativos complexos.

O aplicativo de Silverlight pode ser dividido em uma coleção de arquivos XAP; DLLs individuais; ou arquivos XML individuais, imagens e qualquer outro tipo com tipos MIME reconhecidos. No cenário de centro de chamada, para demonstrar o aplicativo granular de particionamento, será implantado o controle do Silverlight OrderDetail como um DLL separado (AdvOrderClientControls.dll) junto com AdvCallCenterClient.xap no diretório do projeto AdvCallClientWeb ClientBin (consulte novamente a Figura 1 ).

A DLL será baixada preemptively no segmento de trabalho quando o agente de aceita a chamada de entrada. A chamada que você viu na Figura 4 —ThreadPool.Queue­UserWorkItem (ExecuteControlDown­load) — é responsável por isso. Depois que o chamador responde a perguntas sobre segurança, VOU usar reflexão para criar uma instância do controle OrderDetail e adicioná-lo à árvore de controle antes de exibi-lo na tela. Figura 14 mostra controle OrderDetail.xaml carregados na árvore de controle com detalhes de pedido preenchidas.

A DLL que contém o controle OrderDetail é implantada para o mesmo site da Web como o cliente Centro chamada, que é comum das DLLs pertencentes ao mesmo aplicativo, então não executo em quaisquer problemas de domínio cruzado nesse caso. No entanto, isso pode não ser o caso com os serviços, porque aplicativos do Silverlight podem acessar serviços implantados em vários domínios, incluindo os locais e na nuvem, como mostra o diagrama da arquitetura (novamente, consulte novamente a Figura 1 ).

O método ExecuteControlDownload (consulte a Figura 4 ) é executado em um thread de trabalho de segundo plano e usa a classe WebClient para baixar a DLL. WebClient, por padrão, presume que o download acontece do domínio de origem e, portanto, só usa URIs relativos.

O manipulador OrderDetailControlDownloadCallback recebe o fluxo DLL e cria o assembly usando ResourceUtility.GetAssembly() mostrado na Figura 15 . Porque a criação do assembly deve ocorrer no thread da interface do usuário, irá distribuir o GetAssembly() e a atribuição de thread-safe do assembly para a variável global para o segmento de interface do usuário:

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

Figura 15 funções de utilitário para extrair recursos

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;
  }
}

Como o delegado expedido executa em um thread diferente que o manipulador de retorno de chamada, você precisa estar consciente do estado de objetos que são acessados a partir do delegado anônimo. No código anterior, o estado do fluxo de DLL baixado é realmente importante. Você não pode escrever código que recupera os recursos de fluxo dentro a função OrderDetailControlDownloadCallback. Esse código será prematuramente descarte do fluxo baixado antes do thread da interface do usuário tem a oportunidade de criar o assembly. VOU usar reflexão para criar uma instância do controle de usuário OrderDetail e adicioná-lo para o painel conforme mostrado aqui:

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

ResourceUtility em A Figura 15 também mostra várias funções de utilitário para extrair UIElement o XAML e o documento XML do fluxos baixados e pacotes de padrão.

Produtividade e posteriores

EU ter examinamos o Silverlight da perspectiva do aplicativo empresarial tradicional, tocando vários aspectos de arquiteturais do aplicativo. Implementação de notificações de envio com soquetes do Silverlight é um ativador dos cenários LOB, como centrais de atendimento. Com a futura versão do Internet Explorer 8.0 — que é destinada para incluir seis conexões HTTP simultâneas por host — implementação de notificação de envio através da Internet serão mais atraente quando usando ligação duplex do WCF. Integração com dados LOB e processos é tão fácil quanto em aplicativos da área de trabalho tradicionais.

Isso será um aumento de produtividade enorme quando comparadas com AJAX e outras plataformas de aplicativo (RIA) Internet sofisticadas. Aplicativos do Silverlight podem ser protegidos usando as empresas de autenticação e autorização de WCF fornecidas pelo ASP.NET em sua versão mais recente. Espero que essa exploração pouco do desenvolvimento de aplicativos LOB com o Silverlight irá motivar a você se Silverlight além da mídia e cenários de anúncios.

Hanu Kommalapatié o Supervisor de estratégia de plataforma na Microsoft, e nessa função ele aconselha os clientes corporativos na criação de escalonáveis aplicativos de linha de negócios em plataformas do Silverlight e Azure serviços.