skip to Main Content

While making some old web services (ASMX – ASp.Net) asynchronous, I ran into a problem with an error message in the browser after an ajax call to the service from javascript:

    Error (RetrieveDD): {
  "readyState": 4,
  "responseText": "System.InvalidOperationException: An asynchronous operation cannot be started at this time. Asynchronous operations may only be started within an asynchronous handler or module or during certain events in the Page lifecycle. If this exception occurred while executing a Page, ensure that the Page is marked <%@ Page Async="true" %>. This exception may also indicate an attempt to call an "async void" method, which is generally unsupported within ASP.NET request processing. Instead, the asynchronous method should return a Task, and the caller should await it.rn   at System.Web.AspNetSynchronizationContext.OperationStarted()rn   at System.Runtime.CompilerServices.AsyncVoidMethodBuilder.Create()rn   at SearchWebsite.DDService.LoadCountries(Int32 ID)rn",
  "status": 500,
  "statusText": "error"
}

The javascript side:

        if (servicename != "") {
        $.ajax({
            url: '../../DDService.asmx/' + servicename,
            dataType: 'json',
            method: 'post',
            data: { ID: id },
            success: function success(data) {
                ddl.empty();
                if (hid != "") {
                    $.each(data, function () {
                        if (this['V'] == hid) {
                            var newOption = new Option(this['T'], this['V'], true, true);
                            ddl.append(newOption);
                        } else {
                            var newOption = new Option(this['T'], this['V'], false, false);
                            ddl.append(newOption);
                        }
                    });
                } else {
                    $.each(data, function (index) {
                        if (index == 0) {
                            var newOption = new Option(this['T'], this['V'], true, true);
                            ddl.append(newOption);
                        } else {
                            var newOption = new Option(this['T'], this['V'], false, false);
                            ddl.append(newOption);
                        }
                    });
                };
                if (typeof callback === 'function') {
                    callback(data);
                };
            },
            error: function error(err) {
                console.log('Error (RetrieveDD): ' + JSON.stringify(err, null, 2));
                if (typeof callback === 'function') {
                    callback(err);
                }
            }
        });
    };

And the method in the webservice (older asmx type):

    [WebMethod(Description = "Return list of Countries")]
    public async void LoadCountries(int ID)
    {          
        var retval = new List<DDListItemLight>();
        retval = await DropDownData.GetCountriesListAsync();
        string responseStr = JsonConvert.SerializeObject(retval);
        Context.Response.Write(responseStr);
    }

Anyway, before I made the webmethod async, everything works just fine.
I’ve tried changing the signature to
public async Task
and
public async Task
as well with no luck.
I thought ending the response would work, but it has no effect.

While looking around here and on the internet, the suggestion is to put Async="true" on the page header – but this is a webmethod not an ASPX page, so I was hoping there was some way to make this work (with old asmx).
The MS documentation for this is a decade old and predates async/await (with no examples I could find)

Any ideas?

2

Answers


  1. Chosen as BEST ANSWER

    While the accepted answer worked, I decided to try something different and use controllers like with MVC. (ultimately will be converting over to Blazor WASM, so writing these controllers isn't a waste of time)

    Here's the controller class:

    public sealed class DDServiceController : ApiController
    {
        private static string DbConn;
        DropDownData dropDownData = new DropDownData();
    
        public DDServiceController()
        {
            DbConn = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;
            dropDownData.SetDBConnStr(DbConn);
        }
    
        [Route("api/LoadCountries")]
        [HttpGet]
        public async Task<IHttpActionResult> LoadCountries(int id)
        {
            var retval = new List<DDListItemLight>();
            retval = await dropDownData.GetCountriesListAsync();
            return Ok(retval);
        }
    
        [Route("api/LoadStateProvinces")]
        [HttpGet]
        public async Task<IHttpActionResult> LoadStateProvinces(int id)
        {
            var retval = new List<DDListItemLight>();
            retval = await dropDownData.GetStateProvincesByCountryListAsync(id);
            return Ok(retval);
        }
    
        [Route("api/LoadCityLocalities")]
        [HttpGet]
        public async Task<IHttpActionResult> LoadCityLocalities(int id)
        {
            var retval = new List<DDListItemLight>();
            retval = await dropDownData.GetCityLocalitiesByState_ProvinceListAsync(id);
            return Ok(retval);
        }
    
        [Route("api/loadsalutations")]
        [HttpGet]
        public async Task<IHttpActionResult> LoadSalutations(int id)
        {
            var retval = new List<DDListItemLight>();
            retval = await dropDownData.GetSalutationsListAsync();
            return Ok(retval);
        }
    
    }
    

    Then called it from js like so:

        function RetrieveDD(ddl, ddtype, id) {
        var callback = arguments.length <= 3 || arguments[3] === undefined ? false : arguments[3];
    
        var servicename;
        var hid;
        switch (ddtype) {
            case 'salutation':
                servicename = "LoadSalutations";
                hid = $("#hidSelectedSalutation").val();
                break;
            case 'country':
                servicename = "LoadCountries";
                hid = $("#hidSelectedCountry").val();
                break;
            case 'state':
                servicename = "LoadStateProvinces";
                hid = $("#hidSelectedState").val();
                break;
            case 'city':
                servicename = "LoadCityLocalities";
                hid = $("#hidSelectedCity").val();
                break;
            case 'salutation':
                servicename = "LoadSalutations";
                hid = $("#hidSelectedSalutation").val();
                break;
            default:
                hid = "";
                servicename = "";
        }
        if (servicename != "") {
            $.ajax({
                url: '../../api/' + servicename,
                dataType: 'json',
                method: 'get',
                data: { ID: id },
                success: function success(data) {
                    ddl.empty();
                    if (hid != "") {
                        $.each(data, function () {
                            if (this['V'] == hid) {
                                var newOption = new Option(this['T'], this['V'], true, true);
                                ddl.append(newOption);
                            } else {
                                var newOption = new Option(this['T'], this['V'], false, false);
                                ddl.append(newOption);
                            }
                        });
                    } else {
                        $.each(data, function (index) {
                            if (index == 0) {
                                var newOption = new Option(this['T'], this['V'], true, true);
                                ddl.append(newOption);
                            } else {
                                var newOption = new Option(this['T'], this['V'], false, false);
                                ddl.append(newOption);
                            }
                        });
                    };
                    if (typeof callback === 'function') {
                        callback(data);
                    };
                },
                error: function error(err) {
                    console.log('Error (RetrieveDD): ' + JSON.stringify(err, null, 2));
                    if (typeof callback === 'function') {
                        callback(err);
                    }
                }
            });
        };
    };
    

  2. ASMX does not support async. It was several generations old by the time async was introduced, so it was never updated to support async.

    It does support APM-style asynchrony, so you can get this to work, but it will be awkward.

    First, you need to ensure you’re targeting .NET 4.5 or higher and have httpRuntime.targetFramework set to 4.5 or higher.

    Then, you can define your API using APM methods and wrap the more natural TAP methods. The wrapping can be a bit painful, so I recommend using the ApmTaskFactory from my AsyncEx library, as such:

    [WebMethod(Description = "Return list of Countries")]
    public IAsyncResult BeginLoadCountries(int ID, AsyncCallback callback, object state)
    {
      var task = LoadCountriesAsync(ID);
      return ApmAsyncFactory.ToBegin(task, callback, state);
    }
    
    private static async Task<string> LoadCountriesAsync(int ID)
    {
      var retval = new List<DDListItemLight>();
      retval = await DropDownData.GetCountriesListAsync();
      return JsonConvert.SerializeObject(retval);
    }
    
    public void EndLoadCountries(IAsyncResult asyncResult)
    {
      var responseStr = ApmAsyncFactory.ToEnd<string>(asyncResult);
      Context.Response.Write(responseStr);
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search