.NET async webservice call with a callback - c#

We have a legacy VB6 application that uses an ASMX webservice written in C# (.NET 4.5), which in turn uses a library (C#/.NET 4.5) to execute some business logic. One of the library methods triggers a long-running database stored procedure at the end of which we need to kick off another process that consumes the data generated by the stored procedure. Because one of the requirements is that control must immediately return to the VB6 client after calling the webservice, the library method is async, takes an Action callback as a parameter, the webservice defines the callback as an anonymous method and doesn't await the results of the library method call.
At a high level it looks like this:
using System;
using System.Data.SqlClient;
using System.Threading.Tasks;
using System.Web.Services;
namespace Sample
{
[WebService(Namespace = "urn:Services")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class MyWebService
{
[WebMethod]
public string Request(string request)
{
// Step 1: Call the library method to generate data
var lib = new MyLibrary();
lib.GenerateDataAsync(() =>
{
// Step 2: Kick off a process that consumes the data created in Step 1
});
return "some kind of response";
}
}
public class MyLibrary
{
public async Task GenerateDataAsync(Action onDoneCallback)
{
try
{
using (var cmd = new SqlCommand("MyStoredProc", new SqlConnection("my DB connection string")))
{
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.CommandTimeout = 0;
cmd.Connection.Open();
// Asynchronously call the stored procedure.
await cmd.ExecuteNonQueryAsync().ConfigureAwait(false);
// Invoke the callback if it's provided.
if (onDoneCallback != null)
onDoneCallback.Invoke();
}
}
catch (Exception ex)
{
// Handle errors...
}
}
}
}
The above works in local tests, but when the code is deployed as a webservice Step 2 is never executed even though the Step 1 stored procedure completes and generates the data.
Any idea what we are doing wrong?

it is dangerous to leave tasks running on IIS, the app domain may be shut down before the method completes, that is likely what is happening to you. If you use HostingEnvironment.QueueBackgroundWorkItem you can tell IIS that there is work happening that needs to be kept running. This will keep the app domain alive for a extra 90 seconds (by default)
using System;
using System.Data.SqlClient;
using System.Threading.Tasks;
using System.Web.Services;
namespace Sample
{
[WebService(Namespace = "urn:Services")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class MyWebService
{
[WebMethod]
public string Request(string request)
{
// Step 1: Call the library method to generate data
var lib = new MyLibrary();
HostingEnvironment.QueueBackgroundWorkItem((token) =>
lib.GenerateDataAsync(() =>
{
// Step 2: Kick off a process that consumes the data created in Step 1
}));
return "some kind of response";
}
}
public class MyLibrary
{
public async Task GenerateDataAsync(Action onDoneCallback)
{
try
{
using (var cmd = new SqlCommand("MyStoredProc", new SqlConnection("my DB connection string")))
{
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.CommandTimeout = 0;
cmd.Connection.Open();
// Asynchronously call the stored procedure.
await cmd.ExecuteNonQueryAsync().ConfigureAwait(false);
// Invoke the callback if it's provided.
if (onDoneCallback != null)
onDoneCallback();
}
}
catch (Exception ex)
{
// Handle errors...
}
}
}
}
If you want something more reliable than 90 extra seconds see the article "Fire and Forget on ASP.NET" by Stephen Cleary for some other options.

I have found a solution to my problem that involves the old-style (Begin/End) approach to asynchronous execution of code:
public void GenerateData(Action onDoneCallback)
{
try
{
var cmd = new SqlCommand("MyStoredProc", new SqlConnection("my DB connection string"));
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.CommandTimeout = 0;
cmd.Connection.Open();
cmd.BeginExecuteNonQuery(
(IAsyncResult result) =>
{
cmd.EndExecuteNonQuery(result);
cmd.Dispose();
// Invoke the callback if it's provided, ignoring any errors it may throw.
var callback = result.AsyncState as Action;
if (callback != null)
callback.Invoke();
},
onUpdateCompleted);
}
catch (Exception ex)
{
// Handle errors...
}
}
The onUpdateCompleted callback action is passed to the BeginExecuteNonQuery method as the second argument and is then consumed in the AsyncCallback (the first argument). This works like a charm both when debugging inside VS and when deployed to IIS.

Related

How to execute a stored procedure and forget about it using C# and Entity Framework?

I am using Entity Framework for an app that I wrote using ASP.NET MVC.
I need to be able to call a stored procedure and forget about it. I don't care about the result I just want to trigger it.
I tried to accomplish that by using a separate connection for calling the stored procedure using the ExecuteSqlCommandAsync method. But the stored procedure does not seems to be getting fired.
Here is my async method
protected async Task ProcessRecords(DateTime loadedAt, string email)
{
try
{
using (var conn = new AppContext())
{
var result = conn.Database.ExecuteSqlCommandAsync("EXEC [DedecatedProcesses] #p0, #p1;", loadedAt, email);
await Task.WhenAll(result);
}
}
catch (Exception e)
{
// Do something with e.Message
}
}
Then from an action method with in a controller I call it like so
var loaded = ProcessRecords(DateTime.UtcNow, model.Email);
I do not get any error when I run my code but the procedure does not run.
When I execute the stored procedure using SSMS it runs with no problems.
How can I correctly call the stored procedure and forget about it?
Before answering your question, I want to point this out :
You create an async Task, if "correctly" used you would already have an async call. Something wrong beforehand?
Answer :
await is a call that will not end your curent context by default. In this case you will wait ExecuteSqlCommandAsync at the end of the try.
You can explicitly remove this behaviour as follow :
protected async Task ProcessRecords(DateTime loadedAt, string email)
{
try
{
using (var conn = new AppContext())
{
await conn.Database.ExecuteSqlCommandAsync("DedecatedProcesses #p0, #p1;", loadedAt, email)
.ConfigureAwait(false);
}
}
catch (Exception e)
{
// Do something with e.Message
}
}
NB: There is no point to store your result here.

SignalR SqlDependency Update work but when changing page it stops working

Hello and thanks for reading this.
I used this guide as my base, and I have only added Owin Login/Registration(cookie)
When I login it shows the Notification from my NotificationHub and Im able to update the database and it runs the Update right away. So everything works until you try to switch page, then the notification stop. Even if i just update the current page im on.
What can be the problem?
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Web;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using System.Security.Claims;
using System.Collections.Concurrent;
namespace LifeChange
{
[HubName("notificationHub")]
public class NotificationHub : Hub
{
private static readonly ConcurrentDictionary<string, User> Users = new ConcurrentDictionary<string, User>(StringComparer.InvariantCultureIgnoreCase);
private static List<User> UserList = new List<User>();
Int16 totalNewMessages = 0;
string UserID;
[HubMethodName("check")]
public Task Check(string id)
{
if (!Users.ToList().Exists(i => i.Value.ProfileId == id))
{
string profileId = id; //Context.QueryString["id"];
string connectionId = Context.ConnectionId;
var user = Users.GetOrAdd(profileId, _ => new User
{
ProfileId = profileId,
ConnectionIds = connectionId
});
lock (user.ConnectionIds)
{
Groups.Add(connectionId, user.ProfileId);
}
return base.OnConnected();
}
return null;
}
[HubMethodName("sendNotifications")]
public Task SendNotifications(string id)
{
UserID = id;
using (var connection = new SqlConnection(ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString))
{
string query = "SELECT NotificationNumber FROM [dbo].[NotificationStatus] WHERE UserID=" + UserID;
connection.Open();
using (SqlCommand command = new SqlCommand(query, connection))
{
command.Notification = null;
DataTable dt = new DataTable();
SqlDependency dependency = new SqlDependency(command);
dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
if (connection.State == ConnectionState.Closed)
connection.Open();
var reader = command.ExecuteReader();
dt.Load(reader);
if (dt.Rows.Count > 0)
{
totalNewMessages = Int16.Parse(dt.Rows[0]["NotificationNumber"].ToString());
}
}
}
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
//return context.Clients.All.RecieveNotification(totalNewMessages);
return context.Clients.Client(Users.Values.FirstOrDefault(i => i.ProfileId == UserID).ConnectionIds).RecieveNotification(totalNewMessages);
}
private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
if (e.Type == SqlNotificationType.Change)
{
NotificationHub nHub = new NotificationHub();
nHub.SendNotifications(UserID);
}
}
}
}
There are a lot of things wrong here, you should read some good basic docs about SignalR concepts first. The guide you are referring to is very poor. The main things you should check:
hubs are created by SignalR from scratch each time a hub method is called from a client, and then destroyed, therefore for something as basic as this you should not interfere with that, which instead you do with your event handler
because of the previous point, your UserID member is simply not in the right place, and it sort of works for you just because of wrong side effects you are generating with more mistakes (described below): do not store any state supposed to live across calls inside hubs instance members, it is not meant to survive
you explicitly do a new NotificationHub() for something that has nothing to do with SignalR broadcasting, you should move the sql dependency stuff outside of the hub and do it there, while leaving hubs do their own stuff only
when you change page your connection is lost, buy your event handler is (probably) artificially keeping a "dead" hub instance alive, and it uses it for notification, but that's simply wrong
You should review your design, starting by managing the sql dependency outside of hubs. You already know about IHubContext, so use that from outside hubs, and you should use hubs just for what they are for, without keeping them alive artificially when attaching them to dependency event or, even worse, creating them explicitly. You're on the road to entangled spaghetti code which is hard to reason about and understand, as you are clearly experiencing.

ExecuteReaderAsync Call - Web App Hangs

I'm trying to modify an existing database call so that it makes asynchronous database calls. This is my first foray into asynchronous database calls, so I've looked at this post, this post, and the first two sections of this MSDN article. This is the code that I've come up with, which is similar to what's found in the second answer of the second post:
public async Task<IEnumerable<Item>> GetDataAsync(int id)
{
using (SqlConnection conn = new SqlConnection(oCSB.ConnectionString))
{
using (SqlCommand cmd = new SqlCommand("stored_procedure", conn))
{
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("param", "value");
await conn.OpenAsync();
SqlDataReader reader = await cmd.ExecuteReaderAsync();
return ReadItems(reader, id).ToList();
}
}
}
private IEnumerable<Item> ReadItems(SqlDataReader reader, long id)
{
while (reader.Read())
{
var item = new Item(id);
yield return item;
}
}
The application is a Web Forms application, and the call is initiated by a jQuery ajax request to a static WebMethod in an aspx page, which then calls the GetDataAsync method. Unfortunately, the application hangs on the cmd.ExecuteReaderAsync call with no exception thrown, and I haven't been able to figure out why. I've run it both on the VS dev server and on my local IIS 8, but I get the same result. I've also tried modifying it so that it makes a very simple select on a table. I've also tried changing the code based on other posts I've come across either on MSDN or SO. Anybody know what could possibly be causing it to hang on the ExecuteReaderAsync call?

Calling a method asynchronously using Task.Run

Can someone help me how to make the call to the method SendSms(textMessageItems) asynchronous? What is the best method/practice? I am presuming that Task.Run Async-Await can be used here since I am using .Net4.5 using MVC4 WebApi. But I would like to hear from the experts as I am new to this. I am using this code in my web Server which is in IIS7 and this method could take sometime to process and so would like to process it asynchronously so that the response can return right away to the caller.
Also since I am calling the SendSms inside a for loop would it cause any issue? Do you think I should pass it as a collection List and then process? Please advise.
using Twilio.Mvc;
using Twilio.TwiML.Mvc;
using Twilio.TwiML;
public class SmsController : ApiController
{
[HttpPost]
public HttpResponseMessage Post([FromBody]SmsRequest smsReq)
{
var response = new Twilio.TwiML.TwilioResponse();
//validation checks..
try
{
if ((txtMessageResponse != null) && (txtMessageResponse.SmsMessageInfo.Count > 0))
{
_smsStagingList = txtMessageResponse.SmsMessageInfo;
foreach (TextMessageStaging prepareTextMessageResponse in _smsStagingList)
{
smsDTO textMessageItems = new smsDTO();
textMessageItems.PhoneNumber = prepareTextMessageResponse.PhoneNumber;
textMessageItems.SmsMessage = prepareTextMessageResponse.SmsMessageBody;
isTxtMessageSent = SendSms(textMessageItems);
//If the messages were sent then no need to set the flag to be updated
if (isTxtMessageSent)
{
txtMessageStatusToBeUpdated = false;
}
}
return Request.CreateResponse(HttpStatusCode.OK, twilioResponse.Element);
}
else
{
//send error response
}
catch (Exception msgProcessingError)
{
//send error response again as processing error
}
finally
{
//set the outbound flag in the table
}
}
private bool SendSms(smsDTO textMessageItems)
{
bool isTxtMessageSent = false;
PushMessageRequest txtMessageRequest = new PushMessageRequest();
PushMessageResponse txtMessageResponse = null;
txtMessageRequest.SmsMessageInfo = new SendTextMessage(); //instantiate the dto
txtMessageRequest.SmsMessageInfo.ToPhone = textMessageItems.PhoneNumber;
txtMessageRequest.SmsMessageInfo.TextMessage = textMessageItems.SmsMessage;
try
{
using (ITextService textService = ObjectFactory.SendSmsMessage())
{
txtMessageResponse = textService.SendSmsMessage(txtMessageRequest);
}
isTxtMessageSent = txtMessageResponse.IsSuccessful;
}
catch (Exception ex)
{
isTxtMessageSent = false;
}
return isTxtMessageSent;
}
I recommend that you not use Task.Run. AFAIK, Twilio does not have an async API, so you should ask them about that. You could of course write your own, e.g., based on HttpClient.
would like to process it asynchronously so that the response can return right away to the caller
Please note that async does not change the HTTP protocol, as described on my blog. It is possible, but extremely dangerous, to return a response from ASP.NET while the request has not finished processing (also a link to my blog).

Cannot implicitly convert type DbDataReader to MySqlDataReader when using ExecuteReaderAsync

I've the following function that allows me to pass in a object and populate that object with the returning data, if any.
I've modified the function so that it can be called asynchronously.
public static async Task<MySqlDataReader> MySQLReturnReader(string sName, List<MySqlParameter> oParameters, Action<MySqlDataReader> fn)
{
using (MySqlConnection oConn = new MySqlConnection(MasterConn))
{
await oConn.OpenAsync();
using (MySqlCommand oMySqlCommand = new MySqlCommand(sName, oConn))
{
oMySqlCommand.CommandType = CommandType.StoredProcedure;
if (oParameters != null)
{
foreach (MySqlParameter oPar in oParameters)
{
oMySqlCommand.Parameters.Add(oPar);
}
}
oMySqlCommand.Connection.Open();
using (MySqlDataReader oReader = oMySqlCommand.ExecuteReaderAsync())
{
fn(oReader);
}
}
}
return;
}
My class object is something like;
public class MyClass
{
public int Id {get;set;}
public string Name {get;set;}
...
}
The function can be called like
List<MyClass> oMyClassList = new List<MyClass>();
List<MySqlParameter> oParams = new List<MySqlParameter>();
List<int> Ids = new List<int>(500);
Ids.Add(1);
Ids.Add(2);
...
Ids.Add(499);
foreach(int Id in Ids)
{
MySQLReturnReader("SPCheck", oParams, oRes =>
{
while (oRes.Read())
{
MyClass oMyClass = new MyClass();
oMyClass.Id = Convert.ToInt32(oRes["Id"]);
oMyClass.Name = oRes["Name"].ToString();
}
oMyClassList.Add(oMyClass);
}
);
}
The problem is I'm getting the compilation error of 'Cannot implicitly convert type 'System.Threading.Tasks.Task' to 'MySql.Data.MySqlClient.MySqlDataReader'. Where am I going wrong ?
I'm wanting to use ExecuteReaderAsync in this way, as the Stored procedure called is very complex and would prefer to run the requests in parallel.
In your code, you have:
using (MySqlDataReader oReader = oMySqlCommand.ExecuteReaderAsync())
The compiler error Cannot implicitly convert type 'System.Threading.Tasks.Task' to 'MySql.Data.MySqlClient.MySqlDataReader' means that you need to use the await keyword to "unwrap" the task returned by ExecuteReaderAsync:
using (MySqlDataReader oReader = await oMySqlCommand.ExecuteReaderAsync())
Note however that if you're using the official MySql.Data package, this call won't actually execute asynchronously. The Async methods in the MySql.Data connector aren't asynchronous; they block on network I/O and only return when the DB operation is complete. (For a much more detailed discussion, see this question and its top answer.) MySQL bug #70111 reports this problem in the MySQL connector.
To get truly asynchronous DB operations, you'll have to wait until that bug is fixed, or switch to a different connector. I've been developing a new, fully async connector that should be a drop-in replacement for MySql.Data; to try it out, install MySqlConnector from NuGet; its source is at GitHub.
This most likely indicates that the library you're using doesn't support ExecuteReaderAsync(), so you're just calling the default implementation inherited from DbCommand. This is why it returns the general DbDataReader (instead of the MySQL-specific one). And this also means that your code won't actually be asynchronous, the default version of ExecuteReaderAsync() is just a synchronous wrapper around ExecuteReader().
So, I think you should directly use the old ExecuteReader(), until your library adds support for ExecuteReaderAsync().

Categories