microsoft / BingMapsRESTToolkit

This is a portable class library which makes it easy to access the Bing Maps REST services from .NET.

Home Page:https://github.com/Microsoft/BingMapsRESTToolkit/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Include ASP.NET example in Samples

rsbarro opened this issue · comments

Would it be possible to include an ASP.NET example in with the sample projects? I'm asking because the code sample in the Console app doesn't seem to work under ASP.NET. I used code similar to the following in a Button Click event in an ASP.NET page:

var r = ServiceManager.GetResponseAsync(new GeocodeRequest()
{
    BingMapsKey = System.Configuration.ConfigurationManager.AppSettings.Get("BingMapsKey"),
    Query = "Seattle"
}).GetAwaiter().GetResult();

Using the above approach, the requests would time out. When I switched to using Task.Run, the calls completed successfully. I ended up writing a method to make the call for a Geocode request and to also support a timeout in case the service is not responding. Here is the method below:

private string GetGeocode(string address)
{
    const int timeoutMilliseconds = 5000;
    
    //Define Geocode task
    var taskGeocode = Task.Run(() => ServiceManager.GetResponseAsync(new GeocodeRequest
    {
        BingMapsKey = "[your_api_key]",
        Query = address
    }).Result);

    //Define timeout task
    CancellationTokenSource source = null;
    var taskTimeout = Task.Run(async () =>
    {
        source = new CancellationTokenSource();
        await Task.Delay(timeoutMilliseconds, source.Token);
        source.Dispose();
    });

    //Execute tasks
    if (Task.WaitAny(taskGeocode, taskTimeout) == 1)
        return "Timeout occurred";

    //Task completed, get result and cancel timeout task
    var result = taskGeocode.Result;
    source?.Cancel(true);

    //Process result
    if (!taskGeocode.IsCompleted) throw new ApplicationException("Geocoding task did not complete.");
    var errorDetails = result?.ErrorDetails != null ? string.Join(",", result.ErrorDetails) : "(none)";
    if (errorDetails != "(none)") throw new ApplicationException($"Bing Maps API indicates errors occurred: {errorDetails}");
    if (result == null) throw new ApplicationException("Bing Maps API result is null.");
    if (result.StatusCode != 200) throw new ApplicationException($"Bing Maps API returned status of {result.StatusCode}. Error details: {errorDetails}");
    var resourceSet = result.ResourceSets.FirstOrDefault();
    if (resourceSet == null) throw new ApplicationException($"Bing Maps API returned a null ResourceSet. Error details: {errorDetails}");
    if (resourceSet.Resources == null || resourceSet.Resources.Length == 0) throw new ApplicationException($"Bing Maps API returned null or empty Resources. Error details: {errorDetails}");
    var resource = resourceSet.Resources[0] as Location;
    if (resource == null) throw new ApplicationException($"Bing Maps API returned a resource but it is not of type Location. Error details: {errorDetails}");
    var point = resource.Point;
    if (point == null) throw new ApplicationException($"Bing Maps API returned a Location but the Point value is null. Error details: {errorDetails}");
    if (point.Coordinates == null || point.Coordinates.Length != 2) throw new ApplicationException($"Bing Maps API returned a Location but the Point value has the wrong number of elements. Error details: {errorDetails}");
    return string.Format("{0:0.0000} x {1:0.0000}", (decimal)point.Coordinates[0], (decimal)point.Coordinates[1]);
}

So, a few questions.

  1. I'm assuming that some sort of context issue keeps the Console sample from working under ASP.NET. Does that make sense?
  2. Do you think it would be useful to include this code in a sample project? I'd be happy to set one up.
  3. Is there a better way to handle this issue in ASP.NET?

Thanks,
Rich

You really shouldn't use this in web apps. Instead use the Bing Maps V8 web control. If you use these services with a map, the service request gets marked as non-billable as it becomes part of the map session which would mean lower licensing costs for you. Take a look at the search module in Bing Maps V8, or even better, if the user is typing in a query, use the Autosuggest module. Here are some resources:

http://bingmapsv8samples.azurewebsites.net/#Autosuggest%20with%20Map

https://msdn.microsoft.com/en-us/library/mt712650.aspx

http://bingmapsv8samples.azurewebsites.net/#Geocode

https://msdn.microsoft.com/en-us/library/mt712846.aspx

Thank you for the reply. If it is ok I still have a few questions, but first let me explain a little more about my use case.

My use case does not require a map, I just need the latitude and longitude for an address. I have a process that runs through a console app that does geocode lookups for a list of addresses. Those addresses can sometimes be updated through a non-public facing web application, so when one is updated I do another geocode lookup for the updated address from the web application.

Since there is a console app example, I assumed it was ok to use this library to do those geocode lookups. I ran into problems using the code I wrote for the console app in the web application, ostensibly due to some sort of context issue when trying to use HttpWebRequest in an async fashion.

My primary reason for creating this issue was to try to determine if I was correct in my diagnosis of the blocking/timeout issue. Since I was able to come up with a workaround, I thought I would try to get some feedback on my solution. I don't think our use case is necessarily an uncommon one, and I thought this information might be helpful for others.

So, with all that being said:

  1. Is there another Bing service I could use to do geocode lookups without a map in a non billable fashion? I'm not sure the Bing Maps V8 web control is a good solution for us, as most of the lookups are done through a backend process. To rewrite that code to use a web control and embed it in my web application seems not ideal, even if it would save on billable calls.
  2. All of the calls through my console app are billable calls, so is there any reason that it is ok to have billable calls from a console app but not a web application, especially one that is not accessible publicly?
  1. The Bing Maps REST Location API would be your best option in this case. There is no non-billable option.
  2. Don't understand the question, but don't think it matters. the only time any calls are non-billable is when they are pat of a map session which doesn't occur in server side or console apps.

Going back to your original question, can you not use async/await. The console sample is different from the standard way the API is used. Does this work:

private async string GetGeocode(string address)
{
    const int timeoutMilliseconds = 5000;
    
    //Define Geocode task
    var taskGeocode = await ServiceManager.GetResponseAsync(new GeocodeRequest
    {
        BingMapsKey = "[your_api_key]",
        Query = address
    });

@rbrundritt Your example using await above also times out when used in asp.net. What's the best way to use this library for a ReverseGeoCodeRequest?

Managed to convert @rsbarro 's code to vb.net if any other lurker is here as we all run from Google's 1500% price increase.

Public Shared Function GetReverseGeoLookupRequest(ByVal Latitude As Decimal, ByVal Longitude As Decimal) As List(Of Location)
        Dim Req As New ReverseGeocodeRequest()
        Dim RetVal As New List(Of Location)
        Req.BingMapsKey = "Your Bing Maps Key"
        Req.Point = New Coordinate(Latitude, Longitude)
        Req.IncludeNeighborhood = True
        Const TIMEOUT_MILLISECONDS As Integer = 5000
        Dim TaskGeocode = Task.Run(Function() ServiceManager.GetResponseAsync(Req).Result)
        Dim Source As CancellationTokenSource = Nothing
        Dim TaskTimeout = Task.Run(Async Function()
                                       Source = New CancellationTokenSource()
                                       Await Task.Delay(TIMEOUT_MILLISECONDS, Source.Token)
                                       Source.Dispose()
                                   End Function)

        If Task.WaitAny(TaskGeocode, TaskTimeout) = 1 Then
            Throw New ApplicationException("Request Timed Out")
        End If
        Dim Result = TaskGeocode.Result

        If Result IsNot Nothing AndAlso Result.ResourceSets IsNot Nothing AndAlso Result.ResourceSets.Length > 0 Then

            For Each Item In Result.ResourceSets
                For Each LocItem In Item.Resources
                    RetVal.Add(TryCast(LocItem, Location))
                Next

            Next
        End If

        Return RetVal
    End Function

This method can be used to iterate all values of async response Latitude and Longitude.
private void QueryMapAsync()
{
var taskGeocode = Task.Run(() => ServiceManager.GetResponseAsync(new GeocodeRequest
{
Domain = "http://dev.virtualearth.net/REST/v1/",
BingMapsKey = Key,
Query = Query
}).Result) ;

        //Define timeout task
        CancellationTokenSource source = null;
        const int timeoutMilliseconds = 5000;

        var taskTimeout = Task.Run(async () =>
        {
            source = new CancellationTokenSource();
            await Task.Delay(timeoutMilliseconds, source.Token);
            source.Dispose();
        });

        //Execute tasks
        if (Task.WaitAny(taskGeocode, taskTimeout) == 10)
        {
            return;
        }
        //Task completed, get result and cancel timeout task
        var result = taskGeocode.Result;
        //source?.Cancel(true);

        //Process result
        if (!taskGeocode.IsCompleted) throw new ApplicationException("Geocoding task did not complete.");
        var errorDetails = result?.ErrorDetails != null ? string.Join(",", result.ErrorDetails) : "(none)";

        if (errorDetails != "(none)") throw new ApplicationException($"Bing Maps API indicates errors occurred: {errorDetails}");

        if (result == null) throw new ApplicationException("Bing Maps API result is null.");

        if (result.StatusCode != 200) throw new ApplicationException($"Bing Maps API returned status of {result.StatusCode}. Error details: {errorDetails}");
        var resourceSet = result.ResourceSets.FirstOrDefault();

        if (resourceSet == null) throw new ApplicationException($"Bing Maps API returned a null ResourceSet. Error details: {errorDetails}");

        if (resourceSet.Resources == null || resourceSet.Resources.Length == 0) throw new ApplicationException($"Bing Maps API returned null or empty Resources. Error details: {errorDetails}");

        var resource = resourceSet.Resources[0] as Location;

        if (resource == null) throw new ApplicationException($"Bing Maps API returned a resource but it is not of type Location. Error details: {errorDetails}");

        var point = resource.Point;

        if (point == null) throw new ApplicationException($"Bing Maps API returned a Location but the Point value is null. Error details: {errorDetails}");

        if (point.Coordinates == null || point.Coordinates.Length != 2) throw new ApplicationException($"Bing Maps API returned a Location but the Point value has the wrong number of elements. Error details: {errorDetails}");

        //MessageBox.Show(string.Format("{0:0.0000000000} x {1:0.0000000000}", (decimal)point.Coordinates[0], (decimal)point.Coordinates[1]));

        foreach (var eachValue in result.ResourceSets[0].Resources)
        {
            foreach (var latlong in (eachValue as Location).Point.Coordinates)
            {
                Console.WriteLine(latlong);
            }
            var location = eachValue as Location;
            if (location.ConfidenceLevelType == ConfidenceLevelType.High || location.ConfidenceLevelType == ConfidenceLevelType.Medium)
            {
                MessageBox.Show(string.Format("Latitude: {0:0.0000000000000} and Longitude: {1:0.0000000000000}", (decimal)location.Point.Coordinates[0], (decimal)location.Point.Coordinates[1]));
            }
        }

    }