Partager via


Cet article a fait l'objet d'une traduction automatique.

Silverlight

Créer des applications d'entreprise ligne de Business avec Silverlight, partie 2

Hanu Kommalapati

Cet article présente :

  • L'environnement d'exécution Silverlight
  • Programmation asynchrone de Silverlight
  • Stratégies entre domaines
  • Un exemple d'application Entreprise
Cet article utilise les technologies suivantes :
Silverlight 2

Téléchargement de code disponible de la bibliothèque de code MSDN
Parcourir le code en ligne

Contenu

Intégration avec services professionnels
Invocation de service
Appels de service synchronisées
Conversion d'entité-message
Changement d'état de Silverlight après les appels de service
Stratégies entre domaines
Croix stratégies de domaine pour les services Web hébergés en dehors D'IIS
Stratégies entre domaines pour les services hébergés dans IIS
Sécurité des applications
Partitionnement d'application
La productivité et plus

Pendant le premier article de cette série J'AI introduit un scénario de centre d'appel et présenté une implémentation de remplissage d'écran (écran pop) via les sockets connectées qui employée les sockets TCP asynchrones pris en charge par Silverlight (consultez »Créer des applications d'entreprise ligne de Business avec Silverlight, partie 1").

Pop de l'écran a été implémentée via un Répartiteur d'appel simulée repris un appel à partir d'une file d'attente interne et envoyée notifications via la connexion socket précédemment accepté mis en cache dans une liste générique sur le serveur. Ici, je conclure par l'implémentation de sécurité de l'application, intégrer des services d'entreprise et l'implémentation de stratégies entre domaines pour services Web et applications partitionnement. L'architecture logique de l'application Centre appel est illustrée dans la figure 1 . Le service d'authentification à implémenter dans le service utilitaire tandis que les services métier, ICallService et IUserProfile, va être implémentés dans le projet de service professionnel, comme son nom le suggère.

fig01.gif

Figure 1 Architecture logique du centre d'appels Silverlight

Bien que le diagramme illustre événement de transmission en continu à utilitaire services, de temps, la démo téléchargeable n'inclut pas cette fonctionnalité. L'implémentation de la fonctionnalité de service événement capture sera identique à l'implémentation de services d'entreprise. Toutefois, des événements métier qui ne sont pas des erreurs critiques peuvent être mises en cache localement en stockage isolé et vidées sur le serveur dans un mode de traitement par lots. J'AI commencera la discussion avec l'implémentation des services d'entreprise et conclure avec application de partitionnement.

Intégration avec services professionnels

Intégration aux services est une des aspects importants d'une application (LOB) de métier et, Silverlight fournit beaucoup de composants pour accéder aux ressources basées sur le Web et services. Infrastructure de proxy HttpWebRequest, WebClient et Windows Communication Foundation (WCF) présente quelques une des composants réseau utilisées pour l'interaction basés sur HTTP. Dans cet article, je vais utiliser les services WCF pour intégrer avec les processus d'entreprise back-end.

La plupart d'entre nous utilisent les services Web pour l'intégration avec les sources de données back-end au cours du développement d'applications ; accès au service Web WCF d'avec Silverlight n'est pas très différent qu'avec les applications traditionnelles telles que les applications ASP.NET, Windows Presentation Foundation (WPF) ou Windows Forms. Les différences sont la prise en charge la liaison et le modèle de programmation asynchrone. Silverlight prendra en charge uniquement basicHttpBinding et PollingDuplexHttpBinding. Notez que HttpBinding est la liaison plus interopérable. Pour cette raison, je l'utiliserai pour l'intégration tous dans cet article.

PollingDuplexHttpBinding permet l'utilisation de contrats de rappel pour pousser les notifications sur HTTP. Mon centre d'appels aurions pu utiliser cette liaison pour les notifications pop de l'écran. Cependant, l'implémentation nécessite la mise en cache de la connexion HTTP sur le serveur, ainsi monopolizing parmi les deux connexions HTTP simultanées autorisées par les navigateurs comme Internet Explorer 7.0. Cela peut entraîner des problèmes de performances que tout le contenu Web va être sérialisés via une connexion. Internet Explorer 8.0 autorise les six connexions simultanées par domaine et répondre à ces problèmes de performances. (Les notifications de type Pousser à l'aide PollingDuplexHttpBinding peuvent être une rubrique pour un article futur lorsque Internet Explorer 8.0 est largement disponible.)

À l'application. Lorsque l'agent accepte un appel, le processus d'écran-pop remplit l'écran avec les informations de l'appelant, dans ce cas, les détails de commande de l'appelant. Les informations de l'appelant doivent contenir informations nécessaires pour identifier la commande dans la base de données back-end. Pour ce scénario démo, je suppose que le numéro de commande a été lu dans le système (IVR) réponse vocale interactif. L'application Silverlight appelle des services Web WCF avec le numéro de commande comme l'identificateur unique. La définition du contrat de service et l'implémentation est illustré figure 2 .

Implémentation de service figure 2 professionnel

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>

L'implémentation de ces points de terminaison de service n'est pas vraiment très intéressante que ces implémentations WCF simples. Pour sake de simplicité, J'AI utilisera pas une base de données pour les entités métier mais utiliseront les objets de liste en mémoire pour stocker des objets client, commandes et utilisateur. La classe DataUtil (non illustrée ici, mais disponible dans le téléchargement de code) encapsule accès à ces objets de liste en mémoire.

fig03.gif

La figure 3 script Agent avec des questions de sécurité

Points de terminaison WCF service pour la consommation de Silverlight besoin l'accès au pipeline ASP.NET et donc nécessiter l'attribut AspNetCompatibilityRequirements sur l'implémentation CallService. <servicehostingenvironment/>Cela équivaut à rapprocher par le paramètre dans le fichier web.config.

Comme déjà mentionné, Silverlight uniquement prend en charge basicHttpBinding et PollingDuplexHttpBinding. Si vous utilisez le modèle WCF Service Visual Studio, elle configure la liaison de point de terminaison pour wsHttpBinding, doit être modifié manuellement pour basicHttpBinding avant de Silverlight peut ajouter une référence de service pour la génération de proxy. ASP.NET héberger modifications de compatibilité et des modifications de liaison est automatiquement être pris soin de Si CallService.svc est ajoutée au projet AdvBusinessServices à partir d'un modèle WCF Service Visual Studio Silverlight-enabled.

Invocation de service

Avoir implémenté un service pouvant être appelé par Silverlight, il est temps maintenant pour créer des proxys de service et les utiliser pour associer l'interface utilisateur pour les implémentations de services back-end. Vous pouvez uniquement générer proxies pour les services WCF fiable en utilisant les références de service | ajouter une référence de service séquence dans Visual Studio. Les proxys dans mon démonstration ont été générés dans l'espace de noms CallBusinessProxy. Silverlight ne permet que des appels asynchrones aux ressources réseau et appel de service ne fait pas exception. Lorsqu'un appel du client arrive, le client de Silverlight est écouter de la notification et affiche une boîte de dialogue accepter/rejeter.

Une fois qu'un appel est accepté par l'agent, l'étape suivante dans le processus consiste à appeler le service Web pour extraire le script agent qui correspond à la situation d'appel. Pour cette démonstration, j'uniquement utiliser un script tel qu'affiché dans la figure 3 . Le script affiché contient un message d'accueil, ainsi qu'une liste de questions de sécurité. L'agent garantit qu'un nombre minimal de questions est réponse avant de progresser avec l'assistance.

Le script agent est extrait en accédant à ICallService.Get­AgentScript(), fournissant le numéro de commande comme entrée. Cohérentes avec le modèle de programmation asynchrone appliqué par la pile de services Web Silverlight, le GetAgentScript() est disponible sous CallServiceClient.BeginGetAgentScript(). Lorsque vous effectuez l'appel de service, vous devez fournir un gestionnaire de rappel, Get­AgentScriptCallback, comme illustré figure 4 .

La figure 4 service invocation et modifier l'interface utilisateur de 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));
   }
   ... 
}

Étant donné que le résultat de l'appel de service peut uniquement être récupéré à partir du Gestionnaire de rappel, toute modification apportée à l'état de l'application Silverlight devez se produire dans le Gestionnaire de rappel. CallServiceClient.BeginGetAgentScript() est appelé par _notifyCallPopup_OnAccept exécuté sur le thread d'interface utilisateur et les files d'attente de la requête asynchrone et renvoie immédiatement l'instruction suivante. Étant donné que le script d'agent n'est pas encore disponibles, vous devez attendre jusqu'à ce que le rappel se déclenche avant vous cache le script et les données lier à l'interface utilisateur.

Réussite du service appel déclenche GetAgentScriptCallback, qui extrait le script agent, remplit une variable globale et décale l'interface utilisateur de liaison de données le script agent pour les éléments d'interface utilisateur appropriés. Tout en ajustant l'interface utilisateur, le GetAgentScriptCallback vous assurer que qu'il est mis à jour sur le thread UI via l'utilisation de Dispatcher.CheckAccess().

UIElement.Dispatcher.CheckAccess() va comparer L'IDENTIFICATEUR de thread de l'interface utilisateur avec celle de la thread de travail et renvoyer true si les deux threads sont identiques ; sinon, elle renvoie false. Lorsque Get­AgentScriptCallback s'exécute sur un thread de travail (en fait, étant donné que cela est toujours exécuter sur un thread de travail vous pouvez simplement appeler Dispatcher.BeginInvoke), CheckAccess() renverra false et l'interface utilisateur sera mis à jour à distribuer un délégué anonyme via Dispatcher.Invoke().

Appels de service synchronisées

En raison de la nature asynchrone de l'environnement réseau de Silverlight, il est presque impossible rendre un service asynchrone appeler sur le thread d'interface utilisateur et d'attendre qu'elle effectuer à l'intention de modifier l'état en fonction des résultats de l'appel de l'application. Dans la figure 4 , _notifyCallPopup_OnAccept a besoin pour extraire les détails de commande, transformer le message de sortie une entité client et enregistrez-le à une variable globale d'une manière thread-safe. Pour ce faire, un peut être tenté à écrire le code Gestionnaire comme illustré ici :

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

Mais ce code est figer l'application lorsqu'il rencontre l'instruction this._orderDetailDownloadHandle.WaitOne(). Cela est dû au fait que l'instruction WaitOne() bloque le thread d'interface utilisateur de recevoir des messages expédiés d'autres threads. Au lieu de cela, vous pouvez planifier le thread de travail pour exécuter l'appel de service, attendez que l'appel et de fin du traitement de publication de la sortie de service dans son intégralité sur le thread de travail. Cette technique est illustrée figure 5 . Pour empêcher l'utilisation par inadvertance de blocage des appels dans le thread d'interface utilisateur, J'AI encapsulé ManualResetEvent dans un SLManualResetEvent personnalisé et de test pour le thread UI lorsqu'un appel à WaitOne() est effectuée.

Détails des commandes récupérer la figure 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 étant une classe à usage général, vous ne pouvez pas dépendent de la Dispatcher.CheckAccess() d'un contrôle particulier. ApplicationHelper.IsUiThread() pouvez vérifiez Application.RootVisual.Dispatcher.CheckAccess() ; toutefois, l'accès à cette méthode déclenche une exception accès inter-thread non valide. Donc la uniquement fiable de ce test dans un traitement thread, lorsqu'il n'existe aucun accès à une instance de UIElement, consiste à utiliser Deployment.Current.Dispatcher.CheckAccess() comme illustré ici :

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

Pour l'exécution en arrière-plan des tâches, au lieu d'utiliser ThreadPool.QueueUserWorkItem, vous pouvez utiliser BackGroundWorker qui utilise également un pool de threads, mais vous permet d'associer des gestionnaires de pouvant être exécutées sur le thread d'interface utilisateur. Ce modèle permet l'exécution de plusieurs services appelle en parallèle et attend que tous les appels terminer l'aide de SLManualResetEvent.WaitOne() avant les résultats sont agrégées pour un traitement approfondi.

Conversion d'entité-message

Le GetAgentScriptCallback également traduit les entités de message de sortie (également connu sous le nom DataContracts) à partir du service d'une entité côté client qui représente la sémantique de l'utilisation côté client. Par exemple, la conception d'entités de messages côté serveur peut occupe pas sur la liaison de données tandis que les attention de la nature multiuse du service de paiement qui portent répondre à un large éventail d'utilise, non seulement le centre d'appels.

En outre, il est une bonne ne pas avoir associant une étroite avec les entités message, car les modifications aux entités message ne seront pas dans contrôle le client. Les exercices pratiques de conversion des entités de message aux entités côté client ne sont pas uniquement applicable à Silverlight, mais il sont généralement applicable à tout utilisateur du service Web qui veulent éviter de conception couplage.

J'AI décidé de conserver l'implémentation de traducteurs entité très simple, aucun exotique génériques imbriqués, les expressions lambda ou inversion de conteneurs de contrôles. ClientEntityTranslator est une classe abstraite qui définit la méthode ToClientEntity(), dont chaque sous-classe doit remplacer :

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

Chaque classe enfant est unique à un type de taux de service, donc je créerai traducteurs autant que nécessaire. Dans mon démonstration, J'AI effectuées trois types d'appels de service : IUserProfile.GetUser(), ICallService.GetAgentScript() et ICallService.GetOrderDetails(). Ainsi, J'AI créé trois traducteurs, comme illustré figure 6 .

La figure 6 message entité pour conversion entité côté client

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

Si vous remarqué, les traducteurs ci-dessus sont sans état et utilisent un motif unique. Le convertisseur doit pouvoir hériter de ClientEntityTranslator pour assurer la cohérence et doit être un singleton afin d'éviter nombre-garbage collection.

J'AI conserver réutiliser la même instance chaque fois que l'appel de service correspondant est effectué. Je peux également créer ServiOutputEntityTranslator pour interaction de service qui requiert des messages volumineux entrées (qui est généralement le cas d'appel de service transactionnelle) la définition de classe suivante :

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

Si vous notez la valeur renvoyée de la fonction ci-dessus, il est « objet », comme la classe de base des entités message (dans cette démonstration que je pourrait, mais pas dans le monde réel) n'est pas le contrôle. La sécurité de type va être implémentée par les traducteurs respectifs. Pour simplifier de la démo, je n'est pas enregistrer toutes les données revenir sur le serveur, donc cette démonstration n'inclut pas les traducteurs de conversion client entités d'entités de message.

Changement d'état de Silverlight après les appels de service

Changement d'état visual Silverlight peut uniquement être effectuée en le code exécutant sur le thread d'interface utilisateur. Étant donné que l'exécution asynchrone du service appelle toujours renvoie les résultats sur le Gestionnaire de rappel, le gestionnaire sera l'emplacement approprié pour apporter des modifications à l'état de l'application visual ou non visuels.

Changements d'état non visuels doivent échanger de manière thread-safe si il peut être plusieurs services tente de modifier l'état partagé en mode asynchrone. Il est toujours recommandé Deployment.Current.Dispatcher.CheckAccess() avant de modifier l'interface utilisateur.

Stratégies entre domaines

Contrairement des applications multimédias et les applications qui affichent des bannières publicitaires, applications d'entreprise réels Entreprise nécessitent Intégration avec une variété de service-hébergement environnements. Par exemple, l'application Centre appel référencée dans l'article est généralement de l'application d'entreprise. Cette application hébergée sur un site Web accède à un serveur socket avec état pour pop de l'écran, en fonction de WCF services Web pour accéder aux données MÉTIER, il peut télécharger les packages XAP supplémentaires (packages de déploiement de Silverlight compressé) à partir d'un autre domaine. Il utilise un autre domaine pour la transmission de données d'instrumentation.

Le sandbox Silverlight n'est pas par défaut autoriser l'accès réseau à aucun domaine autre que le domaine d'origine, advcallclientweb que vous l'avez vu revenir dans la figure 1 . Le runtime de Silverlight vérifie pour les stratégies opt-dans l'application accède à n'importe quel domaine autre que le domaine d'origine. Voici une liste typique des scénarios d'hébergement de service qui doivent prendre en charge les demandes de stratégie de domaines par le client :

  • Services Web hébergés dans un processus de service (ou une application de console pour des raisons de simplicité)
  • Services Web hébergés sur IIS ou autre Web serveurs
  • TCP services hébergés dans un processus de service (ou une application de console)

J'abordés implémentation de stratégie entre domaines pour les services TCP le mois dernier et donc concentrerai sur les services Web hébergés dans des processus personnalisés et dans IIS.

Bien qu'il soit simple à implémenter des stratégies entre domaines pour les points de terminaison de service Web hébergés dans IIS, les autres cas nécessitent des connaissances de la nature de la stratégie demandes et réponses.

Croix stratégies de domaine pour les services Web hébergés en dehors D'IIS

Pour la gestion efficace d'état, peut-être cas où un souhaiterez ordinateur hôte services dans un système d'exploitation processus IIS extérieur. Pour l'accès interdomaine de tels services WCF, le processus devrez stratégies ordinateur hôte au niveau de la racine du point de terminaison HTTP. Lorsqu'un service Web entre domaines est appelé, Silverlight effectue une demande HTTP GET pour clientaccesspolicy.xml. Si le service est hébergé dans IIS, le fichier client­accesspolicy.xml peut être copié dans la racine du site Web et services Internet (IIS) fera le reste en vous servant du fichier. En cas de l'hébergement personnalisé sur l'ordinateur local http://localhost:<port>/clientaccesspolicy.xml doit être une URL valide.

Puisque la démo de centre d'appel n'utilise pas les services Web hébergés personnalisés, je vais utiliser une TimeService simple dans une application de console pour illustrer les concepts. La console s'exposer un point de transfert (REST) de représentation état terminaison utilisant les nouvelles fonctionnalités REST de Microsoft .NET Framework 3.5. Propriété UriTemplate a à définir précisément à l'opérateur montre la figure 7 .

Implémentation de la figure 7 pour services hébergés sur Param 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;
    }
}

Le nom d'interface ou le nom de méthode n'a aucun impact sur le résultat ; vous pouvez choisir tout ce que vous le souhaitez. WebInvoke possède des autres propriétés telles que RequestFormat et ResponseFormat, qui sont par défaut définie sur XML ; nous ne devez les spécifier explicitement. Nous sont également compter sur la valeur par défaut de la propriété BodyStyle à BodyStyle.Bare, ce qui signifie que la réponse n'est pas être encapsulée.

L'implémentation du service est très simple ; il transmet simplement le clientaccesspolicy.xml en réponse à la demande du client Silverlight. Le nom de fichier de stratégie peut être de vos propres choix, et vous pouvez l'appeler tout ce que vous le souhaitez. L'implémentation du service de stratégie est illustrée dans la figure 7 .

Nous devons maintenant configurer le IPolicyService pour fourniture REST-style de demandes HTTP. Le App.config de l'application de console (ConsoleWebServices) est illustré figure 8 . Il y a quelques choses à noter concernant la nécessité d'une configuration spéciale : la liaison du point de terminaison ConsoleWebServices.IPolicyServer doit être défini à webHttpBinding. En outre, le comportement de point de terminaison IPolicyService doit être configuré avec WebHttpBehavior comme indiqué dans le fichier de configuration. L'adresse de base de la PolicyService doit être définie à l'URL racine (comme dans http://localhost:3045/) et l'adresse du point de terminaison doit être vide (par exemple.

Figure 8 paramètres WCF pour personnaliser hébergement environnement

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

Enfin, les services hébergés de console, tels que le TimeService indiqué dans les exemples de code, ainsi que la configuration, doivent être configurés pour avoir une URL semblable à leurs homologues de services Internet (IIS). Par exemple, l'URL d'un point final TimeService IIS hébergés sur par défaut HTTP peut ressembler à ce qui suit : http://localhost/TimeService.svc. Dans ce cas, les métadonnées peuvent être obtenues via http://localhost/TimeService.svc?WSDL.

Toutefois, dans le cas d'hébergement de console, les métadonnées peuvent être obtenues en ajoutant «? WSDL » à l'adresse base de la ordinateur hôte service. Dans la configuration illustrée figure 8 , vous constatez que l'adresse de base de la TimeService est définie sur http://localhost:3045/TimeService.svc, donc les métadonnées peuvent être obtenues via http://localhost:3045/TimeService.svc?WSDL.

Cette URL est semblable à ce que nous utilisons dans IIS qui héberge. Si vous affectez l'adresse de base ordinateur hôte http://localhost:3045/TimeService.svc/, l'URL de métadonnées sera http://localhost:3045/TimeService.svc/?WSDL, qui ressemble un peu étrange. Ainsi, regarder à ce problème comme il peut vous faire gagner temps de découvrir l'URL de métadonnées.

Stratégies entre domaines pour les services hébergés dans IIS

Comme nous l'avons vu précédemment, déploiement des stratégies entre domaines pour les services hébergés de IIS est simple : copier uniquement le fichier clientaccesspolicy.xml dans la racine du site sur lequel les services Web sont hébergés. Comme vous l'avez vu dans la figure 1 , l'application Silverlight est hébergée sur adv­callclientweb (localhost:1041) et accède à des services d'entreprise à partir de AdvBusinessServices (localhost:1043). Le runtime de Silverlight nécessite clientaccesspolicy.xml pour être déployé à la racine de adv­BusinessServices ce site avec le code illustré figure 9 .

La figure 9 Clientaccesspolicy.xml pour les services Web hébergés sur IIS

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

Si vous vous rappelez le format de stratégie de domaines pour le serveur socket (advpolicyserver) le premier article de cette série, le format de <allow-from> est semblable. La différence est dans la section <grant-to> où le serveur socket nécessite un paramètre <socket-resource> avec les attributs de plage et le protocole port, comme illustré ici :

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

Si vous créez le site d'hébergement de service WCF utilisant le modèle de site ASP.NET Web et ajoutez des points de terminaison WCF ultérieurement, le test de serveur Web mappera le répertoire virtuel sur le nom du projet (tels que « / AdvBusinessServices »). Il doit être modifié à "/" dans les pages de propriétés du projet afin que le clientaccesspolicy.xml peut être servies à partir la racine. Si vous ne changez ce, le clientaccesspolicy.xml ne sera pas à la racine, et les applications Silverlight obtiendrez erreurs de serveur lorsque le service est accessible. Notez que ce ne pas être un problème pour les sites Web créés à l'aide le modèle de projet de service WCF Web.

Contrôle de connexion figure 10 à l'aide de 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";
    }

  }
}

Sécurité des applications

Une des exigences essentielles d'une application MÉTIER est l'authentification ; pour que l'agent de centre d'appel puisse commencer le travail d'équipe, il s'authentifier en attribuant un nom d'utilisateur et mot de passe. Dans les applications Web ASP.NET, ceci facilement possible en tirant parti du fournisseur d'appartenances et les contrôles de connexion ASP.NET côté serveur. Dans Silverlight, il existe deux façons pour forcer l'authentification : authentification à l'extérieur et l'authentification à l'intérieur.

L'authentification à l'extérieur est très simple et est similaire à l'implémentation d'authentification d'applications ASP.NET. Avec cette approche, l'authentification s'effectue dans une page Web ASP.NET avant que l'application Silverlight ne s'affiche. Le contexte de l'authentification peut être transféré dans l'application Silverlight via paramètre InitParams avant une application Silverlight est chargée ou via un personnalisé appel de service Web (pour extraire les informations d'état de l'authentification) après l'application est chargée.

Cette approche a sa place lorsque l'application Silverlight fait partie d'un plus grand système ASP.NET/HTML. Toutefois, dans les cas où Silverlight est le pilote principal de l'application, il est naturel pour effectuer une authentification dans Silverlight. Je vais utiliser le contrôle Silverlight 2 PasswordBox pour capturer le mot de passe et s'authentifier en utilisant l'extrémité de WCF AuthenticationService ASP.NET pour valider les informations d'identification de l'utilisateur. AuthenticationService, ProfileService et RoleService font partie de la nouvelle namespace—System.Web.ApplicationServices—which a nouveau avec .NET Framework 3.5. la figure 10 illustre le code XAML pour le contrôle login créé dans ce but. Le contrôle de connexion appelle AuthenticationService.LoginAsync() ASP.NET avec les ID d'utilisateur et mot de passe qu'a été entré.

fig11.gif

La figure 11, le contrôle Silverlight connexion personnalisé

L'écran d'ouverture de session du centre d'appel, illustré à la figure 11 , n'est pas sophistiqué mais joue le rôle de la démo. J'AI implémenté un gestionnaire pour affaire à l'événement LoginCompleted à l'intérieur du contrôle afin que qu'il soit autonome pour afficher les messages de connexion non valides et dialogues mot de passe réinitialiser pour sophistiqué implémentations. Lors d'une connexion réussie, l'événement OnSuccessfulLogin sera déclenchée pour indiquer le contrôle parent (Application.RootVisual dans ce cas) pour afficher le premier écran application rempli avec les informations utilisateur.

Le gestionnaire LoginCompleted (ctrlLoginView_OnSuccessfulLogin) situé dans le siège page Silverlight appelle le service de profil hébergé sur le site Web des services métier, comme illustré figure 12 . AuthenticationService par défaut n'est pas mappé à n'importe quel point de terminaison .svc ; par conséquent, J'AI mappera fichier .svc à l'implémentation physique, comme illustré ici :

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

Utilisation de la figure 12 de Login.xaml dans le 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;
       });
      }
    }
}

Silverlight peut uniquement appeler les services Web qui sont configurés pour être appelée par environnements de script tels que AJAX. Comme tous les services pouvant être appelées AJAX, le service AuthenticationService doit accéder à l'environnement d'exécution ASP.NET. Je fournis cet accès par le paramètre directement sous le nœud <system.servicemodel>. Pour que le service d'authentification pour être pouvant être appelée par le processus de connexion Silverlight (ou être appelée par AJAX), le fichier web.config doit valeur selon les instructions de » Comment : activer le service d'authentification WCF." Les services sont automatiquement configurés pour Silverlight s'ils sont créés à l'aide le modèle WCF Service Silverlight-enabled situé dans la catégorie de Silverlight.

la figure 13 affiche la la configuration modifiée avec les éléments importants nécessaires pour le service d'authentification. Avec la configuration du service, J'AI remplacé également le paramètre de configuration SQL Server pour aspnetdb qui stocke des informations d'authentification. Machine.config définit un paramètre LocalSqlServer qui attend asp­netdb.mdf pour être incorporé dans le répertoire App_Data du site Web. Ce paramètre de configuration supprime le paramètre par défaut et qu'il pointe vers le aspnetdb associé à l'instance de SQL Server. Il peut facilement être modifié pour pointer vers une instance de base de données s'exécutant sur un ordinateur distinct.

Figure 13 paramètres de service d'authentification 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="https://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>

Pour conserver l'encapsulation du contrôle de connexion et pour conserver conception libre associant avec le contrôle parent, succès du processus de connexion d'est communiqué par le déclenchement de l'événement OnSuccessfulLogin. Le Application.RootVisual (qui est une classe de page) exécute le processus métier nécessaires pour afficher le premier écran lors de connexion réussie. Le premier écran affiché après qu'une connexion réussie est le registrationView, comme illustré dans la méthode userProfile_GetUserCompleted de figure 12 . Avant affichage de ce mode, J'AI va extraire informations utilisateur en appelant CallBusinessProxy.UserProfileClient.GetUserAsync(). Veuillez prenez note de l'appel asynchrone de service, qui est similaire à l'intégration du service métier que j'aborderai plus tard.

N'oubliez pas que la configuration précédente n'utilise pas SSL (secure Sockets layer); vous devez modifier pour utiliser SSL lorsque vous créez pour les systèmes de production.

fig14.gif

La figure 14 le OrderDetails.xaml avec détails commande remplis

Partitionnement d'application

Un des facteurs qui contribuent à l'heure de démarrage d'application Silverlight est la taille du package initiale. Les instructions de la taille du package XAP sont non différents que le poids de page des applications Web. Bande passante est limité. Les heures de réponse strictes des applications Web requièrent que vous attentivement à l'heure de démarrage d'application Silverlight.

Outre le temps de traitement passé avant que le premier objet UserControl ne s'affiche, la taille du package d'applications a influence directement sur cette qualité importante de l'application. Afin d'améliorer la vitesse de démarrage, vous devez éviter monolithiques fichiers XAP qui peuvent augmenter à des dizaines de méga-octets de taille pour les applications complexes.

L'application Silverlight peut être divisée en un ensemble de fichiers XAP ; DLL individuelle, ou des fichiers XML individuels, images et tout autre type avec des types MIME reconnus. Dans le scénario Centre appel, pour illustrer l'application précise de partitionnement, J'AI déploiera le contrôle Silverlight OrderDetail en tant que distinct DLL (AdvOrderClientControls.dll) avec AdvCallCenterClient.xap dans le répertoire ClientBin du projet AdvCallClientWeb (reportez-vous à la figure 1 ).

La DLL est preemptively téléchargée sur le thread de travail lorsque l'agent accepte l'appel entrant. L'appel que vous avez vu dans la figure 4 —ThreadPool.Queue­UserWorkItem (ExecuteControlDown­load), est responsable de ceci. Une fois que l'appelant répond aux questions de sécurité, j'utilise la réflexion pour créer une instance du contrôle OrderDetail et l'ajouter à l'arborescence de contrôle avant d'afficher à l'écran. figure 14 illustre OrderDetail.xaml contrôle chargé dans l'arborescence des contrôles avec des détails commande remplis.

La DLL contenant le contrôle OrderDetail est déployée sur le même site Web que le client de centre appel, qui est courant des DLL qui appartiennent à la même application, donc je n'exécute dans des problèmes entre domaines dans ce cas. Toutefois, cela peut être pas le cas avec les services, car applications Silverlight peuvent accéder aux services déployés sur plusieurs domaines, y compris locales et dans le nuage, comme illustré dans le diagramme d'architecture (là encore, référer figure 1 ).

La méthode ExecuteControlDownload (voir figure 4 ) s'exécute sur un thread de travail en arrière-plan et utilise la classe WebClient pour télécharger la DLL. Suppose WebClient, par défaut, qu'il le téléchargement se produit à partir du domaine d'origine et par conséquent n'utilise que les URI relatifs.

Le gestionnaire OrderDetailControlDownloadCallback reçoit le flux DLL et crée l'assembly à l'aide ResourceUtility.GetAssembly() illustré figure 15 . Car la création de l'assembly doit se produire sur le thread d'interface utilisateur, j'est distribuer le GetAssembly() et l'affectation (thread-safe) de l'assembly de la variable globale pour le thread d'interface utilisateur :

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

Figure 15 fonctions utilitaires pour extraire les ressources

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

Puisque le délégué expédiée s'exécute sur un thread différent que le gestionnaire de rappel, vous devez être conscient de l'état des objets qui sont accessibles depuis le délégué anonyme. Dans le code précédent, l'état du flux DLL téléchargé est très important. Vous ne pouvez pas écrire du code qui récupère les ressources du flux dans la fonction OrderDetailControlDownloadCallback. Ce code se prématurément céder le flux téléchargé avant que le thread d'interface utilisateur a une occasion pour créer l'assembly. J'utilise la réflexion pour créer une instance du contrôle utilisateur OrderDetail et ajouter le Panneau de configuration comme indiqué ici :

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

ResourceUtility dans La figure 15 indique également diverses fonctions utilitaires pour extraire UIElement le XAML et le document XML à partir du flux téléchargés et packages par défaut.

La productivité et plus

J'AI ont étudié Silverlight du point d'entreprise classique application vue, toucher plusieurs aspects architecturales de l'application. Implémentation de notifications de type Pousser avec les sockets Silverlight est un outil de scénarios MÉTIER tels que les centres d'appel. Avec la prochaine version de Internet Explorer 8.0, qui est détailler pour inclure les six connexions HTTP simultanées par ordinateur hôte, implémentation de notification de commande par le biais d'Internet sera plus intéressante lorsque recto verso liaison WCF. Intégration avec les données MÉTIER et de processus est aussi simple que dans les applications de bureau traditionnelles.

Il s'agit d'une augmentation de la productivité énorme lorsqu'elle est comparée avec AJAX et autres plates-formes de l'application (FINS) Internet riches. Applications Silverlight peuvent être sécurisées à l'aide de points de l'authentification et d'autorisation terminaison WCF fournies par ASP.NET dans sa version plus récente. J'espère que ce petit exploration du développement d'applications MÉTIER avec Silverlight motivate vous permet de prendre Silverlight au-delà de média et scénarios de publicité.

Hanu Kommalapati est un Gestionnaire de stratégie de plate-forme chez Microsoft et dans ce rôle il avertit des clients d'entreprise dans la création des applications évolutives de métier sur des plates-formes Silverlight et services Azure.