2010-11-30 14 views
1

J'ai donc une application Win Phone qui trouve une liste de compagnies de taxis et qui tire son nom et son adresse de Bing avec succès et qui remplit une liste déroulante qui est affichée aux utilisateurs. Maintenant ce que je veux faire est, pour rechercher chacun de ces termes sur Bing, trouver le nombre de coups chacun renvoie terme de recherche et de les classer en conséquence (une sorte lâche de classement de popularité)Lecture asynchrone XML dans Windows Phone 7

void findBestResult(object sender, DownloadStringCompletedEventArgs e) 
    { 
      string s = e.Result; 
      XmlReader reader = XmlReader.Create(new MemoryStream(System.Text.UTF8Encoding.UTF8.GetBytes(s))); 
      String name = ""; 
      String rName = ""; 
      String phone = ""; 
      List<TaxiCompany> taxiCoList = new List<TaxiCompany>(); 

      while (reader.Read()) 
      { 
       if (reader.NodeType == XmlNodeType.Element) 
       { 
        if (reader.Name.Equals("pho:Title")) 
        { 
         name = reader.ReadInnerXml(); 
         rName = name.Replace("&amp;","&"); 
        } 

        if (reader.Name.Equals("pho:PhoneNumber")) 
        { 
         phone = reader.ReadInnerXml(); 
        } 

        if (phone != "") 
        { 
         string baseURL = "http://api.search.live.net/xml.aspx?Appid=<MyAppID>&query=%22" + name + "%22&sources=web"; 
         WebClient c = new WebClient(); 
         c.DownloadStringAsync(new Uri(baseURL)); 
         c.DownloadStringCompleted += new DownloadStringCompletedEventHandler(findTotalResults); 
         taxiCoList.Add (new TaxiCompany(rName, phone, gResults)); 
        } 
        phone = ""; 
        gResults =""; 
       } 
      TaxiCompanyDisplayList.ItemsSource = taxiCoList; 
     } 
    } 

Alors que peu de code trouve la compagnie de taxi et lance une tâche asynchrone pour trouver le nombre de résultats de recherche (gResults) pour créer chaque objet teaxicompany.

//Parses search XML result to find number of results 
    void findTotalResults(object sender, DownloadStringCompletedEventArgs e) 
    { 
     lock (this) 
     { 
      string s = e.Result; 
      XmlReader reader = XmlReader.Create(new MemoryStream(System.Text.UTF8Encoding.UTF8.GetBytes(s))); 
      while (reader.Read()) 
      { 
       if (reader.NodeType == XmlNodeType.Element) 
       { 
        if (reader.Name.Equals("web:Total")) 
        { 
         gResults = reader.ReadInnerXml(); 
        } 

       } 
      } 
     } 
    } 

Le Snipped ci-dessus trouve le nombre de résultats de la recherche sur bing, mais le problème est, car il lance async il n'y a aucun moyen de mettre en corrélation les gResults obtenus dans la 2ème méthode avec la bonne compagnie dans la méthode 1. Y at-il quelque manière que ce soit:

1.) Faire passer les variables de nom et téléphone dans la 2ème méthode pour créer l'objet de taxi il

2.) passback la variable gResults et alors seulement créer l'objet taxicompany correspondant?

Répondre

8

Eh bien, il y a beaucoup à faire ici.

Obtenir un petit code aide

Tout d'abord, je veux vous indiquer quelques messages de blog appelé Simple Asynchronous Operation Runner Part 1 et Part 2. Je ne suggère pas que vous les lisiez réellement (bien que vous soyez les bienvenus aussi mais on m'a dit que ce n'était pas facile à lire). Ce dont vous avez besoin, c'est de quelques blocs de code pour les mettre dans votre application.

D'abord à partir de la partie 1, copiez le code depuis la boîte "AsyncOperationService", placez-le dans un nouveau fichier de classe dans votre projet appelé "AsyncOperationService.cs".

Ensuite, vous aurez besoin de la fonction "DownloadString" de la partie 2. Vous pouvez mettre cela n'importe où mais je vous recommande de créer une classe publique statique appelée "WebClientUtils" et de le mettre là.

Aperçu de la solution

Nous allons créer une classe (TaxiCompanyFinder) qui a une seule méthode qui se déclenche en dehors du travail asynchrone pour obtenir les résultats que vous êtes après et a alors un événement qui est soulevée quand le travail est terminé.

Alors commençons. Vous avez une classe TaxiCompany, je vais inventer ma propre ici afin que l'exemple est aussi complète que possible: -

public class TaxiCompany 
{ 
    public string Name { get; set; } 
    public string Phone { get; set; } 
    public int Total { get; set; } 
} 

Nous avons également besoin d'un EventArgs pour l'événement terminé qui porte le List<TaxiCompany> complété et également une propriété Error cela renverra toute exception qui pourrait avoir eu lieu. Cela ressemble à ceci: -

public class FindCompaniesCompletedEventArgs : EventArgs 
{ 
    private List<TaxiCompany> _results; 
    public List<TaxiCompany> Results 
    { 
     get 
     { 
      if (Error != null) 
       throw Error; 

      return _results; 
     } 
    } 

    public Exception Error { get; private set; } 

    public FindCompaniesCompletedEventArgs(List<TaxiCompany> results) 
    { 
     _results = results; 
    } 

    public FindCompaniesCompletedEventArgs(Exception error) 
    { 
     Error = error; 
    } 
} 

Maintenant, nous pouvons prendre un nouveau départ avec quelques os nus pour la TaxiCompanyFinder classe: -

public class TaxiCompanyFinder 
{ 
    protected void OnFindCompaniesCompleted(FindCompaniesCompletedEventArgs e) 
    { 
     Deployment.Current.Dispatcher.BeginInvoke(() => FindCompaniesCompleted(this, e)); 
    } 

    public event EventHandler<FindCompaniesCompletedEventArgs> FindCompaniesCompleted = delegate {}; 

    public void FindCompaniesAsync() 
    { 
     // The real work here 
    } 
} 

C'est assez en avant jusqu'à présent droite. Vous remarquerez l'utilisation de BeginInvoke sur le répartiteur, puisqu'il y aura une série d'actions asynchrones impliquées, nous voulons nous assurer que lorsque l'événement est réellement levé, il s'exécute sur le thread UI, ce qui facilite la consommation de cette classe.

analyse XML séparation

L'un des problèmes de votre code d'origine a est qu'il mélange XML avec l'énumération essayant de faire d'autres fonctions, ses tous un spagetti bits. La première fonction que j'ai identifiée est l'analyse du code XML pour obtenir le nom et le numéro de téléphone. Ajouter cette fonction à la classe: -

IEnumerable<TaxiCompany> CreateCompaniesFromXml(string xml) 
    { 
     XmlReader reader = XmlReader.Create(new StringReader(xml)); 
     TaxiCompany result = new TaxiCompany(); 

     while (reader.Read()) 
     { 
      if (reader.NodeType == XmlNodeType.Element) 
      { 
       if (reader.Name.Equals("pho:Title")) 
       { 
        result.Name = reader.ReadElementContentAsString(); 
       } 

       if (reader.Name.Equals("pho:PhoneNumber")) 
       { 
        result.Phone = reader.ReadElementContentAsString(); 
       } 

       if (result.Phone != null) 
       { 
        yield return result; 
        result = new TaxiCompany(); 
       } 
      } 
     } 
    } 

Notez que cette fonction donne un ensemble de TaxiCompany instances du xml sans essayer de faire quoi que ce soit d'autre. Également l'utilisation de ReadElementContentAsString qui fait pour la lecture plus propre. En outre, la consommation de la chaîne xml est beaucoup plus lisse.

Pour des raisons similaires ajouter cette fonction à la classe: -

private int GetTotalFromXml(string xml) 
    { 
     XmlReader reader = XmlReader.Create(new StringReader(xml)); 
     while (reader.Read()) 
     { 
      if (reader.NodeType == XmlNodeType.Element) 
      { 
       if (reader.Name.Equals("web:Total")) 
       { 
        return reader.ReadElementContentAsInt(); 
       } 
      } 
     } 
     return 0; 
    } 

La fonction principale

Ajoutez la fonction suivante à la classe, c'est la fonction qui fait tout le travail réel async :

private IEnumerable<AsyncOperation> FindCompanies(Uri initialUri) 
    { 
     var results = new List<TaxiCompany>(); 

     string baseURL = "http://api.search.live.net/xml.aspx?Appid=<MyAppID>&query=%22{0}%22&sources=web"; 

     string xml = null; 
     yield return WebClientUtils.DownloadString(initialUri, (r) => xml = r); 

     foreach(var result in CreateCompaniesFromXml(xml)) 
     { 
      Uri uri = new Uri(String.Format(baseURL, result.Name), UriKind.Absolute); 
      yield return WebClientUtils.DownloadString(uri, r => result.Total = GetTotalFromXml(r)); 
      results.Add(result); 
     } 

     OnFindCompaniesCompleted(new FindCompaniesCompletedEventArgs(results)); 
    } 

Il semble réellement assez simple, presque comme le code synchonous qui est le point. Il récupère le fichier XML initial contenant l'ensemble dont vous avez besoin, crée l'ensemble des objets TaxiCompany. Il foreaches à travers l'ensemble en ajoutant la valeur Total de chacun. Enfin, l'événement terminé est renvoyé avec l'ensemble des sociétés.

Nous avons juste besoin de remplir la méthode FindCompaniesAsync: -

public void FindCompaniesAsync() 
    { 
     Uri initialUri = new Uri("ConstructUriHere", UriKind.Absolute); 

     FindCompanies(initialUri).Run((e) => 
     { 
      if (e != null) 
       OnFindCompaniesCompleted(new FindCompaniesCompletedEventArgs(e)); 
     }); 
    } 

Je ne sais pas ce que le premier Uri est ou si vous devez paramatise d'une certaine façon, mais vous auriez juste besoin de modifier cette fonction .La vraie magie se produit dans la méthode d'extension Run, cela parcourt toutes les opérations asynchrones, s'il y en a, renvoyez une exception, puis l'événement terminé se déclenche avec l'ensemble de propriétés Error.

Utilisation de la classe

maintenant en vous pouvez consommer cette classe comme ceci:

var finder = new TaxiCompanyFinder(); 
finder.FindCompaniesCompleted += (s, args) => 
{ 
    if (args.Error == null) 
    { 
     TaxiCompanyDisplayList.ItemsSource = args.Results; 
    } 
    else 
    { 
     // Do something sensible with args.Error 
    } 
} 
finder.FindCompaniesAsync(); 

Vous pourriez également envisager d'utiliser

 TaxiCompanyDisplayList.ItemsSource = args.Results.OrderByDescending(tc => tc.Total); 

si vous voulez obtenir la compagnie le total le plus élevé en haut de la liste.

1

Vous pouvez transmettre n'importe quel objet en tant que "UserState" dans le cadre de votre appel asynchrone, qui sera alors disponible dans le rappel asynchrone. Donc, dans votre premier bloc de code, le changement:

c.DownloadStringAsync(new Uri(baseURL)); 
c.DownloadStringCompleted += new DownloadStringCompletedEventHandler(findTotalResults); 

à:

TaxiCompany t = new TaxiCompany(rName, phone); 
c.DownloadStringAsync(new Uri(baseURL), t); 
c.DownloadStringCompleted += new DownloadStringCompletedEventHandler(findTotalResults); 

Ce qui devrait vous permettre de le faire:

void findTotalResults(object sender, DownloadStringCompletedEventArgs e) 
{ 
    lock (this) 
    { 
     TaxiCompany t = e.UserState; 
     string s = e.Result; 

     ... 
    } 
} 

Je ne l'ai pas testé ce code par -se, mais l'idée générale de passer des objets à des callbacks asynchrones en utilisant UserState de eventarg devrait fonctionner malgré tout. Pour plus d'informations, consultez la section AsyncCompletedEventArgs.UserState definition on MSDN pour plus d'informations.

+0

merci, UserState est exactement ce que je cherchais – varunsrin