A 3rd party has supplied an interface which allows me to search their database for customers and retrieve their details. Eg. Pictures, date of birth etc.
I imported their WSDL into Visual Studio and am using the Async methods to retrieve the customer details.
MyClient Client = new MyClient();
Client.FindCustomersCompleted += FindCustomersCompleted;
Client.GetCustomerDetailsCompleted += GetCustomerDetailsCompleted;
Client.FindCustomersAsync("Jones");
Below are the two events which deal with the responses.
void FindCustomersCompleted(object sender, FindCustomersCompletedEventArgs e)
{
foreach(var Cust in e.Customers)
{
Client.GetCustomerDetailsAsync(Cust.ID);
}
}
void GetCustomerDetailsCompleted(object sender, GetCustomerDetailsCompletedEventArgs e)
{
// Add the customer details to the result box on the Window.
}
So lets assume that my initial search for "Jones" returns no results or causes an error. Its fairly straight forward to tell the user that there was an error or no results found as I will only receive a single response.
However if i say get 50 results for "Jones" then i make 50 GetCustomerDetailsAsync calls and get 50 responses.
Lets say that something goes wrong on the server side and i don't get any valid responses. Each GetCustomerDetailsCompleted event will receive an error/timeout and i can determine that that individual response has failed.
What is the best way to determine that All of my responses have failed and i need to inform the user that there has been a failure?
Alternatively what if 1 out of 50 succeeds?
Should i keep track of my requests and flag them as successful as i receive the response?
Should i keep track of my requests and flag them as successful as i
receive the response?
This is also how I manage multiple requests. Flag if the returned result is without fault and track the flags, evaluating after each return if you already processed all returns. I do not of another way.
I would start by converting Event-based Asynchronous Pattern model to Task based. This would allow to use built in await/async keywords resulting in much easier to use code.
Here is a simple implementation: https://stackoverflow.com/a/15316668/3070052
In your case I would not update UI on each event but gathered all the information in a single variable and only displayed only when I get all the results.
Here is a code to get you going:
public class CustomerDetails
{
public int Id {get; set;}
public string Name {get; set;}
}
public class FindCustomersResult
{
public FindCustomersResult()
{
CustomerDetails = new List<CustomerDetails>();
}
public List<CustomerDetails> CustomerDetails {get; set;}
}
public class ApiWrapper
{
public Task<FindCustomersResult> FindCustomers(string customerName)
{
var tcs = new TaskCompletionSource<FindCustomersResult>();
var client = new MyClient();
client.FindCustomersCompleted += (object sender, FindCustomersCompletedEventArgs e) =>
{
var result = new FindCustomersResult();
foreach(var customer in e.Customers)
{
var customerDetails = await GetCustomerDetails(customer.ID);
result.CustomerDetails.Add(customerDetails);
}
tcs.SetResult(result);
}
client.FindCustomersAsync(customerName);
return tcs.Task;
}
public Task<CustomerDetails> GetCustomerDetails(int customerId)
{
var tcs = new TaskCompletionSource<CustomerDetails>();
var client = new MyClient();
client.GetCustomerDetailsCompleted += (object sender, GetCustomerDetailsCompletedEventArgs e) =>
{
var result = new CustomerDetails();
result.Name = e.Name;
tcs.SetResult(result);
}
client.GetCustomerDetailsAsync(customerId);
return tcs.Task;
}
}
Then you call this by:
var api = new ApiWrapper();
var findCustomersResult = await api.FindCustomers("Jones");
This would fail if any request fails.
PS. I wrote this example in notepad, so bear with me if it does not compiles or contains syntax errors. :)
Related
If I have to much activities, does it cause blocking resources or request time out?
Here is my scenario:
I have an api controller which sends an Order request to consumer; I use Request/Response patern to recieve ErrorMessage property from consumer and base on that property response back, if it's null I would want to return OK() otherwise, return BadRequest or Ok but with a message like: Product out of stock to notify to the client.
In my consumer, I have build a routing slip which have 2 activities:
CreateOrderActivity: Which creates an order with order details.
ReserveProductActivity: Which reduces the quantity of product in stock, if product quantity < 0 I'll publish a message with an ErrorMessage back to the consumer and compensate the previous activity.
public async Task Consume(ConsumeContext<ProcessOrder> context)
{
try
{
if (!string.IsNullOrEmpty(context.Message.ErrorMessage))
{
await context.RespondAsync<OrderSubmitted>(new
{
context.Message.OrderId,
context.Message.ErrorMessage
});
return;
}
RoutingSlipBuilder builder = new RoutingSlipBuilder(context.Message.OrderId);
// get configs
var settings = new Settings(_configuration);
// Add activities
builder.AddActivity(settings.CreateOrderActivityName, settings.CreateOrderExecuteAddress);
builder.SetVariables(new { context.Message.OrderId, context.Message.Address, context.Message.CreatedDate, context.Message.OrderDetails });
builder.AddActivity(settings.ReserveProductActivityName, settings.ReserveProductExecuteAddress);
builder.SetVariables(new { context.Message.OrderDetails });
await context.Execute(builder.Build());
await context.RespondAsync<OrderSubmitted>(new
{
context.Message.OrderId
});
}
catch (Exception ex)
{
_log.LogError("Can not create Order {OrderId}", context.Message.OrderId);
throw new Exception(ex.Message);
}
}
Code for ReserveProductActivity:
public async Task<ExecutionResult> Execute(ExecuteContext<ReserveProductArguments> context)
{
var orderDetails = context.Arguments.OrderDetails;
foreach (var orderDetail in orderDetails)
{
var product = await _productRepository.GetByProductId(orderDetail.ProductId);
if (product == null) continue;
var quantity = product.SetQuantity(product.QuantityInStock - orderDetail.Quantity);
if (quantity < 0)
{
var errorMessage = "Out of stock.";
await context.Publish<ProcessOrder>(new
{
ErrorMessage = errorMessage
});
throw new RoutingSlipException(errorMessage);
}
await _productRepository.Update(product);
}
return context.Completed(new Log(orderDetails.Select(x => x.ProductId).ToList()));
}
This line of code in a consumer method await context.Execute(builder.Build())
At first I thought it would build the routing slip and execute all activities first before going to the next line but it's not. Instead it's immediately going to the next line of code (which responses back to controller) and then after execute activities, which is not what I want. I need to check the quantity of product in 2nd activity first and base on that return back to the controller.
(In current, it always responses back to controller first - the line after buider.Buid(), and then if quantity < 0 it still goes to the very first if condition of the consume method but since it already responses, I cannot trigger response inside that if statement again).
So in short, if product is still available in 2nd activity I can send the reponse back like normal (which executes the code after context.Execute(builder.Build()), but if quantity < 0 - which I publish back to the consumer method with ErrorMessage, I would like it to jump to the very first if condition of Consume method (if(!string.IsNullOrEmpty(context.Message.ErrorMessage)) ...) and base on the ErrorMessage notify the client.
Is there something wrong with this approach? How can I achieve something like this?
Thanks
It isn't documented, but it is possible to use a proxy to execute a routing slip, and response to the request with the result of the routing slip. You can see the details in the unit tests:
https://github.com/MassTransit/MassTransit/blob/master/tests/MassTransit.Tests/Courier/RequestRoutingSlip_Specs.cs#L20
You could create the proxy, which builds the routing slip and executes it, and the response proxy - both of which are then configured on a receive endpoint as .Instance consumers.
class RequestProxy :
RoutingSlipRequestProxy<Request>
{
protected override void BuildRoutingSlip(RoutingSlipBuilder builder, ConsumeContext<Request> request)
{
// get configs
var settings = new Settings(_configuration);
// Add activities
builder.AddActivity(settings.CreateOrderActivityName, settings.CreateOrderExecuteAddress);
builder.SetVariables(new { context.Message.OrderId, context.Message.Address, context.Message.CreatedDate, context.Message.OrderDetails });
builder.AddActivity(settings.ReserveProductActivityName, settings.ReserveProductExecuteAddress);
builder.SetVariables(new { context.Message.OrderDetails });
}
}
class ResponseProxy :
RoutingSlipResponseProxy<Request, Response>
{
protected override Response CreateResponseMessage(ConsumeContext<RoutingSlipCompleted> context, Request request)
{
return new Response();
}
}
You could then call it from the consumer, or put the ordering logic in the proxy - whichever makes sense, and then use the request client from your controller to send the request and await the response.
I have some code that loads up and AppDomain(call it domain) calling an object function within the domain. The purpose is to get a list of items from a usb device using the device API to retrieve the information. The API requires a callback to return the information.
var AppDomain.CreateDomain(
$"BiometricsDomain{System.IO.Path.GetRandomFileName()}");
var proxy = domain.CreateInstanceAndUnwrap(proxy.Assembly.FullName, proxy.FullName
?? throw new InvalidOperationException()) as Proxy;
var ids = obj.GetIdentifications();
The proxy code loaded into the domain is as follows
public class Proxy : MarshalByRefObject
{
public List<String> GetIdentifications()
{
var control = new R100DeviceControl();
control.OnUserDB += Control_OnUserDB;
control.Open();
int nResult = control.DownloadUserDB(out int count);
// need to be able to return the list here but obviously that is not
// going to work.
}
private void Control_OnUserDB(List<String> result)
{
// Get the list of string from here
}
}
Is there a way to be able to wait on the device and return the information as needed when the callback is called? Since the GetIdentifications() has already returned I don't know how to get the
You can consider wrapping the Event-Based Asynchronous Pattern (EAP) operations as one task by using a TaskCompletionSource<TResult> so that the event can be awaited.
public class Proxy : MarshalByRefObject {
public List<String> GetIdentifications() {
var task = GetIdentificationsAsync();
return task.Result;
}
private Task<List<String>> GetIdentificationsAsync() {
var tcs = new TaskCompletionSource<List<string>>();
try {
var control = new R100DeviceControl();
Action<List<string>> handler = null;
handler = result => {
// Once event raised then set the
// Result property on the underlying Task.
control.OnUserDB -= handler;//optional to unsubscribe from event
tcs.TrySetResult(result);
};
control.OnUserDB += handler;
control.Open();
int count = 0;
//call async event
int nResult = control.DownloadUserDB(out count);
} catch (Exception ex) {
//Bubble the error up to be handled by calling client
tcs.TrySetException(ex);
}
// Return the underlying Task. The client code
// waits on the Result property, and handles exceptions
// in the try-catch block there.
return tcs.Task;
}
}
You can also improve on it by adding the ability to cancel using a CancellationToken for longer than expected callbacks.
With that the proxy can then be awaited
List<string> ids = proxy.GetIdentifications();
Reference How to: Wrap EAP Patterns in a Task
NOTE: Though there may be more elegant solutions to the problem of asynchronous processing, the fact that this occurs in a child AppDomain warrants child AppDomain best practices. (see links below)
i.e.
do not allow code meant for a child AppDomain to be executed in the parent domain
do not allow complex types to bubble to the parent AppDomain
do not allow exceptions to cross AppDomain boundaries in the form of custom exception types
OP:
I am using it for fault tolerance
First I would probably add a Open or similar method to give time for the data to materialise.
var proxy = domain.CreateInstanceAndUnwrap(proxy.Assembly.FullName, proxy.FullName
?? throw new InvalidOperationException()) as Proxy;
proxy.Open(); // <------ new method here
.
. some time later
.
var ids = obj.GetIdentifications();
Then in your proxy make these changes to allow for data processing to occur in the background so that by the time you call GetNotifications data may be ready.
public class Proxy : MarshalByRefObject
{
ConcurrentBag<string> _results = new ConcurrentBag<string>();
public void Open()
{
var control = new R100DeviceControl();
control.OnUserDB += Control_OnUserDB;
control.Open();
// you may need to store nResult and count in a field?
nResult = control.DownloadUserDB(out int count);
}
public List<String> GetIdentifications()
{
var copy = new List<string>();
while (_results.TryTake(out var x))
{
copy.Add(x);
}
return copy;
}
private void Control_OnUserDB(List<String> result)
{
// Get the list of string from here
_results.Add (result);
}
}
Now you could probably improve upon GetNotifications to accept a timeout in the event either GetNotifications is called before data is ready or if you call it multiply but before subsequent data to arrive.
More
How to: Run Partially Trusted Code in a Sandbox
Not sure why you just don't maintain a little state and then wait for the results in the call:
public class Proxy : MarshalByRefObject
{
bool runningCommand;
int lastResult;
R100DeviceControl DeviceControl { get{ if(deviceControl == null){ deviceControl = new R100DeviceControl(); deviceControl.OnUserDB += Control_OnUserDB; } return deviceControl; } }
public List<String> GetIdentifications()
{
if(runningCommand) return null;
DeviceControl.Open();
runningCommand = true;
lastResult = control.DownloadUserDB(out int count);
}
private void Control_OnUserDB(List<String> result)
{
runningCommand = false;
// Get the list of string from here
}
}
Once you have a pattern like this you can easily switch between async and otherwise whereas before it will look a little harder to understand because you integrated the async logic, this way you can implement the sync method and then make an async wrapper if you desire.
I am currently using the Change Notifications in Active Directory Domain Services in .NET as described in this blog. This will return all events that happen on an selected object (or in the subtree of that object). I now want to filter the list of events for creation and deletion (and maybe undeletion) events.
I would like to tell the ChangeNotifier class to only observe create-/delete-/undelete-events. The other solution is to receive all events and filter them on my side. I know that in case of the deletion of an object, the atribute list that is returned will contain the attribute isDeleted with the value True. But is there a way to see if the event represents the creation of an object? In my tests the value for usnchanged is always usncreated+1 in case of userobjects and both are equal for OUs, but can this be assured in high-frequency ADs? It is also possible to compare the changed and modified timestamp. And how can I tell if an object has been undeleted?
Just for the record, here is the main part of the code from the blog:
public class ChangeNotifier : IDisposable
{
static void Main(string[] args)
{
using (LdapConnection connect = CreateConnection("localhost"))
{
using (ChangeNotifier notifier = new ChangeNotifier(connect))
{
//register some objects for notifications (limit 5)
notifier.Register("dc=dunnry,dc=net", SearchScope.OneLevel);
notifier.Register("cn=testuser1,ou=users,dc=dunnry,dc=net", SearchScope.Base);
notifier.ObjectChanged += new EventHandler<ObjectChangedEventArgs>(notifier_ObjectChanged);
Console.WriteLine("Waiting for changes...");
Console.WriteLine();
Console.ReadLine();
}
}
}
static void notifier_ObjectChanged(object sender, ObjectChangedEventArgs e)
{
Console.WriteLine(e.Result.DistinguishedName);
foreach (string attrib in e.Result.Attributes.AttributeNames)
{
foreach (var item in e.Result.Attributes[attrib].GetValues(typeof(string)))
{
Console.WriteLine("\t{0}: {1}", attrib, item);
}
}
Console.WriteLine();
Console.WriteLine("====================");
Console.WriteLine();
}
LdapConnection _connection;
HashSet<IAsyncResult> _results = new HashSet<IAsyncResult>();
public ChangeNotifier(LdapConnection connection)
{
_connection = connection;
_connection.AutoBind = true;
}
public void Register(string dn, SearchScope scope)
{
SearchRequest request = new SearchRequest(
dn, //root the search here
"(objectClass=*)", //very inclusive
scope, //any scope works
null //we are interested in all attributes
);
//register our search
request.Controls.Add(new DirectoryNotificationControl());
//we will send this async and register our callback
//note how we would like to have partial results
IAsyncResult result = _connection.BeginSendRequest(
request,
TimeSpan.FromDays(1), //set timeout to a day...
PartialResultProcessing.ReturnPartialResultsAndNotifyCallback,
Notify,
request
);
//store the hash for disposal later
_results.Add(result);
}
private void Notify(IAsyncResult result)
{
//since our search is long running, we don't want to use EndSendRequest
PartialResultsCollection prc = _connection.GetPartialResults(result);
foreach (SearchResultEntry entry in prc)
{
OnObjectChanged(new ObjectChangedEventArgs(entry));
}
}
private void OnObjectChanged(ObjectChangedEventArgs args)
{
if (ObjectChanged != null)
{
ObjectChanged(this, args);
}
}
public event EventHandler<ObjectChangedEventArgs> ObjectChanged;
#region IDisposable Members
public void Dispose()
{
foreach (var result in _results)
{
//end each async search
_connection.Abort(result);
}
}
#endregion
}
public class ObjectChangedEventArgs : EventArgs
{
public ObjectChangedEventArgs(SearchResultEntry entry)
{
Result = entry;
}
public SearchResultEntry Result { get; set; }
}
I participated in a design review about five years back on a project that started out using AD change notification. Very similar questions to yours were asked. I can share what I remember, and don't think things have change much since then. We ended up switching to DirSync.
It didn't seem possible to get just creates & deletes from AD change notifications. We found change notification resulted enough events monitoring a large directory that notification processing could bottleneck and fall behind. This API is not designed for scale, but as I recall the performance/latency were not the primary reason we switched.
Yes, the usn relationship for new objects generally holds, although I think there are multi-dc scenarios where you can get usncreated == usnchanged for a new user, but we didn't test that extensively, because...
The important thing for us was that change notification only gives you reliable object creation detection under the unrealistic assumption that your machine is up 100% of the time! In production systems there are always some case where you need to reboot and catch up or re-synchronize, and we switched to DirSync because it has a robust way to handle those scenarios.
In our case it could block email to a new user for an indeterminate time if an object create were missed. That obviously wouldn't be good, we needed to be sure. For AD change notifications, getting that resync right that would have some more work and hard to test. But for DirSync, its more natural, and there's a fast-path resume mechanism that usually avoids resync. For safety I think we triggered a full re-synchronize every day.
DirSync is not as real-time as change notification, but its possible to get ~30-second average latency by issuing the DirSync query once a minute.
I've got some code as shown below with multiple methods which call a web service to get some data from a database. This will produce a set of fields which are then added to another database from the web app. This all works great but I have no idea how to unit test any of it due to it mostly outputting voids, and the data coming from a database which changes every time the button is clicked. Is there a way to unit test to just if the methods work or not? Sorry I am very new to unit testing but I know how important it is so any help would be appreciated.
//Get webservice service
private Service1 GetService()
{
return new TestProjectService.Service1();
}
//Choose which webservice we want to use based on radio button selection
private TestProjectService.CommandMessages GetCommand(Service1 service)
{
var command = new TestProjectService.CommandMessages();
switch (WebServiceRadio.SelectedIndex)
{
case 0:
command = service.GetData();
break;
case 1:
command = service.GetDataLINQ();
break;
}
return command;
}
//Display the results in a label on screen
private void DisplayResult(string text)
{
LatestCommandLabel.Text = text;
}
//Get the current username of the user logged in
public string GetUsername()
{
return System.Security.Principal.WindowsIdentity.GetCurrent().Name;
}
//Submit the data to the database using Linq
private void SubmitData(string username, TestProjectService.CommandMessages command)
{
var dc = new TestProjectLinqSQLDataContext();
var msg = new TestProjectCommandMessage
{
Command_Type = command.CommandType,
Command = command.Command,
DateTimeSent = command.DateTimeSent,
DateTimeCreated = command.DateTimeCreated,
Created_User = username,
Created_Dttm = DateTime.Now
};
dc.TestProjectCommandMessages.InsertOnSubmit(msg);
dc.SubmitChanges();
}
//Return the value and submit data to database
private void ReturnValue()
{
var service = GetService();
var command = GetCommand(service);
var username = GetUsername();
if (command != null)
{
DisplayResult(String.Format("Last Command Called (Using {0}) : {1}", WebServiceRadio.SelectedItem.ToString(), command.Command));
string userName = GetUsername();
SubmitData(username, command);
}
else
{
DisplayResult("No Commands Available");
}
}
//Onlick return value
protected void GetCommandButton_Click(object sender, EventArgs e)
{
ReturnValue();
}
Behavior verification is the approach used to test methods that don't return any values.
In a nutshell, since the method doesn't return any results, the only thing a test can do is make sure the method causes the appropriate actions to take place. This is typically accomplished by using a mock object, which keeps track of whether or not its methods have been called.
In order for your tests to use test doubles, you need to include seams in the design of your system.
I strongly recommend reading Dependency Injection in .Net, by Mark Seeman. Since you're new to unit testing, you undoubtedly have many questions on the mechanics involved in unit testing (and this answer probably sparked even more questions) - this book goes into great detail answering those questions.
So I have a Win Phone app that is finding a list of taxi companies and pulling their name and address from Bing successfully and populating a listbox that is being displayed to users. Now what I want to do is, to search for each of these terms on Bing, find the number of hits each search term returns and rank them accordingly (a loose sort of popularity ranking)
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("&","&");
}
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;
}
}
So that bit of code finds the taxi company and launches an asynchronous task to find the number of search results ( gResults ) to create each teaxicompany object.
//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();
}
}
}
}
}
The above snipped finds the number of search results on bing, but the problem is since it launches async there is no way to correlate the gResults obtained in the 2nd method with the right company in method 1. Is there any way to either:
1.) Pass the name and phone variables into the 2nd method to create the taxi object there
2.) Pass back the gResults variable and only then create the corresponding taxicompany object?
Right well there is a lot do here.
Getting some small helper code
First off I want to point you to a couple of blog posts called Simple Asynchronous Operation Runner Part 1 and Part 2. I'm not suggesting you actually read them (although you're welcome too but I've been told they're not easy reading). What you actually need is a couple of code blocks from them to put in your application.
First from Part 1 copy the code from the "AsyncOperationService" box, place it in new class file in your project called "AsyncOperationService.cs".
Second you'll need the "DownloadString" function from Part 2. You could put that anywhere but I recommend you create a static public class called "WebClientUtils" and put it in there.
Outline of solution
We're going to create a class (TaxiCompanyFinder) that has a single method which fires off the asynchronous job to get the results you are after and then has an event that is raised when the job is done.
So lets get started. You have a TaxiCompany class, I'll invent my own here so that the example is as complete as possible:-
public class TaxiCompany
{
public string Name { get; set; }
public string Phone { get; set; }
public int Total { get; set; }
}
We also need an EventArgs for the completed event that carries the completed List<TaxiCompany> and also an Error property that will return any exception that may have occured. That looks like this:-
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;
}
}
Now we can make a start with some bare bones for the TaxiCompanyFinder class:-
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
}
}
This is pretty straight forward so far. You'll note the use of BeginInvoke on the dispatcher, since there are going to be a series of async actions involved we want to make sure that when the event is actually raised it runs on the UI thread making it easier to consume this class.
Separating XML parsing
One of the problems your original code has is that it mixes enumerating XML with trying to do other functions as well, its all a bit spagetti. First function that I indentified is the parsing of the XML to get the name and phone number. Add this function to the class:-
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();
}
}
}
}
Note that this function yields a set of TaxiCompany instances from the xml without trying to do anything else. Also the use of ReadElementContentAsString which makes for tidier reading. In addition the consuming of the xml string is much smoother.
For similar reasons add this function to the class:-
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;
}
The core function
Add the following function to the class, this is the function that does all the real async work:-
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));
}
It actually looks pretty straight forward, almost like synchonous code which is the point. It fetchs the initial xml containing the set you need, creates the set of TaxiCompany objects. It the foreaches through the set adding the Total value of each. Finally the completed event is fired with the full set of companies.
We just need to fill in the FindCompaniesAsync method:-
public void FindCompaniesAsync()
{
Uri initialUri = new Uri("ConstructUriHere", UriKind.Absolute);
FindCompanies(initialUri).Run((e) =>
{
if (e != null)
OnFindCompaniesCompleted(new FindCompaniesCompletedEventArgs(e));
});
}
I don't know what the initial Uri is or whether you need to paramatise in some way but you would just need to tweak this function. The real magic happens in the Run extension method, this jogs through all the async operations, if any return an exception then the completed event fires with Error property set.
Using the class
Now in you can consume this class like this:
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();
You might also consider using
TaxiCompanyDisplayList.ItemsSource = args.Results.OrderByDescending(tc => tc.Total);
if you want to get the company with the highest total at the top of the list.
You can pass any object as "UserState" as part of making your asynchronous call, which will then become available in the async callback. So in your first block of code, change:
c.DownloadStringAsync(new Uri(baseURL));
c.DownloadStringCompleted += new DownloadStringCompletedEventHandler(findTotalResults);
to:
TaxiCompany t = new TaxiCompany(rName, phone);
c.DownloadStringAsync(new Uri(baseURL), t);
c.DownloadStringCompleted += new DownloadStringCompletedEventHandler(findTotalResults);
Which should then allow you to do this:
void findTotalResults(object sender, DownloadStringCompletedEventArgs e)
{
lock (this)
{
TaxiCompany t = e.UserState;
string s = e.Result;
...
}
}
I haven't tested this code per-se, but the general idea of passing objects to async callbacks using the eventarg's UserState should work regardless.
Have a look at the AsyncCompletedEventArgs.UserState definition on MSDN for further information.