Asynchronous insert in Azure Table - c#

How to asynchronously save an entity to Windows Azure Table Service?
The code below works synchronously but raises an exception when trying to save asynchronously.
This statement:
context.BeginSaveChangesWithRetries(SaveChangesOptions.Batch,
(asyncResult => context.EndSaveChanges(asyncResult)), null);
Results in System.ArgumentException: "The current object did not originate the async result. Parameter name: asyncResult".
Additionally, what's the correct pattern for creating the service context when saving asynchronously? Should I create a separate context for each write operation? Is it too expensive (e.g. requiring a call over the network)?
TableStorageWriter.cs:
using System;
using System.Data.Services.Client;
using System.Diagnostics;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;
namespace WorkerRole1
{
public class TableStorageWriter
{
private const string _tableName = "StorageTest";
private readonly CloudStorageAccount _storageAccount;
private CloudTableClient _tableClient;
public TableStorageWriter()
{
_storageAccount = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("StorageConnectionString"));
_tableClient = _storageAccount.CreateCloudTableClient();
_tableClient.CreateTableIfNotExist(_tableName);
}
public void Write(string message)
{
try
{
DateTime now = DateTime.UtcNow;
var entity = new StorageTestEntity
{
Message = message,
PartitionKey = string.Format("{0:yyyy-MM-dd}", now),
RowKey = string.Format("{0:HH:mm:ss.fff}-{1}", now, Guid.NewGuid())
};
// Should I get this context before each write? It is efficient?
TableServiceContext context = _tableClient.GetDataServiceContext();
context.AddObject(_tableName, entity);
// This statement works but it's synchronous
context.SaveChangesWithRetries();
// This attempt at saving asynchronously results in System.ArgumentException:
// The current object did not originate the async result. Parameter name: asyncResult
// context.BeginSaveChangesWithRetries(SaveChangesOptions.Batch,
// (asyncResult => context.EndSaveChanges(asyncResult)), null);
}
catch (StorageClientException e)
{
Debug.WriteLine("Error: {0}", e.Message);
Debug.WriteLine("Extended error info: {0} : {1}",
e.ExtendedErrorInformation.ErrorCode,
e.ExtendedErrorInformation.ErrorMessage);
}
}
}
internal class StorageTestEntity : TableServiceEntity
{
public string Message { get; set; }
}
}
Called from WorkerRole.cs:
using System.Net;
using System.Threading;
using Microsoft.WindowsAzure.ServiceRuntime;
using log4net;
namespace WorkerRole1
{
public class WorkerRole : RoleEntryPoint
{
public override void Run()
{
var storageWriter = new TableStorageWriter();
while (true)
{
Thread.Sleep(10000);
storageWriter.Write("Working...");
}
}
public override bool OnStart()
{
ServicePointManager.DefaultConnectionLimit = 12;
return base.OnStart();
}
}
}
Examples using Windows Azure SDK for .NET 1.8.

You should call EndSaveChangesWithRetries instead of EndSaveChanges, as otherwise the IAsyncResult object returned by BeginSaveChangesWithRetries cannot be used by EndSaveChanges. So, could you please try changing your End method call as below?
context.BeginSaveChangesWithRetries(SaveChangesOptions.Batch,
(asyncResult => context.EndSaveChangesWithRetries(asyncResult)),
null);
And for your other question, I would recommend creating a new TableServiceContext for each call, as DataServiceContext is not stateless (MSDN) and the way you implemented TableStorageWriter.Write with the asynchronous call might allow concurrent operations. Actually, in Storage Client Library 2.0, we explicitly prevented concurrent operations that uses a single TableServiceContext object. Moreover, creating a TableServiceContext does not result in a request to Azure Storage.

Related

Easymodbus gives connection error in Quartz.net class

Friends, I am pulling data from a device with a library called EasyModbus. I want to use the Quartz library to automatically pull this data every hour. The problem is that while I can pull data normally, when I use the same code inside the class I get a connection error.
I don't have any problem pulling data in main form. I only get a connection error when using it within the Quartz class.
public class Gorev : IJob // Quartz.Net
{
string address = "10.100.135.20";
public ModbusClient modbus = new ModbusClient(); // EasyModbus
public bool ModbusConnect() // Modbus Connection
{
if (modbus.Connected == false)
{
modbus.Connect(address, 502);
return modbus.Connected; // Return True
}
else
{
modbus.Disconnect();
return modbus.Connected; // Retunn False
}
}
public Task Execute(IJobExecutionContext context)
{
int[] frekans = modbus.ReadHoldingRegisters(0x009E, 1); //Connected Error
return Task.CompletedTask;
}
}
You did not specify a parameter in the ModbusClient constructor:
public ModbusClient modbus = new ModbusClient(address, 502); // then modbus.Connect() without parameter

Debugger skipping methods after organizing code into new solutions

I had a perfectly working API that was making http calls & a UI that was using that API.
Everything was working and it was built really ugly ( 2 solutions for everything), so I wanted to separate everything so it would be more organized.
UI
DataManagerService
DataManager
Contracts
After a lot of copy paste & dependencies references it all looked like its working with 0 errors.
BUT now something weird happens, A method is being skipped and I have NO IDEA WHY.
I am not on release mode.
If anyone has any ideas I would appreciate it!
The method that is being skipped :
private static List<Actor> ReadActorsFromJson(string json)
{
List<Actor> celebListReadFromFile;
try
{
var celebJson = File.ReadAllText(json);
celebListReadFromFile = JsonConvert.DeserializeObject<List<Actor>>(celebJson);
}
catch (Exception ex)
{
celebListReadFromFile = new List<Actor>();
// Empty list/whatever it got in it
}
return celebListReadFromFile;
}
Which is being invoked by :
public static async Task SaveOriginal()
{
foreach (var currceleb in ReadActorsFromJson(filePath))
{
var curr = currceleb;
originalList.TryAdd(currceleb.name, currceleb);
}
}
and this method is being invoked by the classes static constructor:
static Logic()
{
originalList = new ConcurrentDictionary<string, Actor>();
filePath = ConfigurationManager.AppSettings["tempList"];
File.Copy(filePath, BACKUP, true);
// invoking the method
SaveOriginal();
}
The API:
using Contracts;
using System.Threading.Tasks;
using System.Web.Mvc;
namespace WebApplication12.Controllers
{
public class ValuesController : Controller
{
public ILogic _Ilogic;
public ValuesController(ILogic logic)
{
_Ilogic = logic;
}
// GET api/values
public async Task<ActionResult> GetActors()
{
return Json(await _Ilogic.GetAllActorsAsync(), JsonRequestBehavior.AllowGet);
}
public async Task<ActionResult> RemoveActorAsync(Actor actor) {
await _Ilogic.RemoveActorAsync(actor.name);
return Json(await _Ilogic.GetAllActorsAsync());
}
public async Task<ActionResult> ResetAsync()
{
await _Ilogic.ResetAsync();
return Json(await _Ilogic.GetAllActorsAsync());
}
}
}
The business logic :
using System;
using System.Collections.Generic;
using System.IO;
using System.Configuration;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using System.Linq;
using Newtonsoft.Json;
using Contracts;
namespace DataManager
{
public class Logic : ILogic
{
static string filePath;
private static ConcurrentDictionary<string, Actor> originalList;
const string BACKUP = #"C:\tempList\backup.txt";
static Logic()
{
originalList = new ConcurrentDictionary<string, Actor>();
filePath = ConfigurationManager.AppSettings["tempList"];
File.Copy(filePath, BACKUP, true);
SaveOriginal();
}
public async static Task<List<Actor>> GetCelebritiesInner()
{
return originalList.Values.ToList();
}
public async Task<List<Actor>> GetAllActorsAsync()
{
return await GetCelebritiesInner();
}
// Try to read the data from the Json and initialize it. if failed , initialize with whatever it got. return
private static List<Actor> ReadActorsFromJson(string json)
{
List<Actor> celebListReadFromFile;
try
{
var celebJson = File.ReadAllText(json);
celebListReadFromFile = JsonConvert.DeserializeObject<List<Actor>>(celebJson);
}
catch (Exception ex)
{
celebListReadFromFile = new List<Actor>();
// Empty list/whatever it got in it
}
return celebListReadFromFile;
}
public async Task RemoveActorAsync(string name)
{
if (originalList.TryRemove(name, out Actor removedActor))
{
var jsonToWrite = JsonConvert.SerializeObject(await GetCelebritiesInner());
try
{
File.WriteAllText(filePath, jsonToWrite);
}
catch (Exception ex)
{
//Unable to remove due to an error.
}
}
}
public async Task ResetAsync()
{
originalList.Clear();
await UpdateFile();
await SaveOriginal();
}
//Saving the actor, adding the name as key & object as value.
public static async Task SaveOriginal()
{
foreach (var currceleb in ReadActorsFromJson(filePath))
{
var curr = currceleb;
originalList.TryAdd(currceleb.name, currceleb);
}
}
public static async Task UpdateFile()
{
File.Copy(BACKUP, filePath, true);
}
}
}
When running the program, the static ctor is being invoked and should invoke the SaveOriginal method. which it doesn't.
Static constructor calls when the first access is made. You are using dependency injection which is lazy loading. It doesn't create object until first access is made.
So, Try to get/set any property or method, static constructor will be called first.
Ok so I cleaned and re built every solution one by one ( I don't know if it helped) & then I ran the API, sent a request from POSTMAN instead of opening the UI, in postman I got the following error:
Could not load file or assembly 'Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed' or one of its dependencies
I looked at the versions in the Nuget packages and there was no 12.0.0.0 in there, so I removed Newtonsoft.Json from all solutions, re-installed Newtonsoft.Json in every solution and it worked.
turns out somehow when opening the new solutions, I installed different Newtonsoft.Json versions. and this caused the program to skip the method without giving the stack trace like postman did, weird.

Azure Service Bus Topics and Subscriptions with Worker Role

So I've recently gotten the need to use Service Bus Topic and Subscriptions and I've followed many articles and tutorials. I've been able to successfully implement Microsoft's Get started with Service Bus topics and also successfully used Visual Studio 2017's Worker Role template to access a database.
However, I'm confused as to how to properly "combine" the two. While the Get started with Service Bus topics article shows how to create 2 apps, one to send and one to receive and then quits, the Worker Role template seems to loops endlessly with await Task.Delay(10000);.
I'm not sure how to "mesh" the two properly. Essentially, I want my Worker Role to stay alive and listen for entries into it's subscription forever (or until it quits obviously).
Any guidance would be great!
P.S.: I've asked a related question concerning proper technology I should use for my case scenario at StackExchange - Software Engineering if you are interested.
Update #1 (2018/08/09)
Based on Arunprabhu's answer, here is some code of how I'm sending a Message based on articles I've read and receiving using Visual Studio 2017's Worker Role with Service Bus Queue template.
Sending (based on Get started with Service Bus topics)
using System;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.ServiceBus;
namespace TopicsSender {
internal static class Program {
private const string ServiceBusConnectionString = "<your_connection_string>";
private const string TopicName = "test-topic";
private static ITopicClient _topicClient;
private static void Main(string[] args) {
MainAsync().GetAwaiter().GetResult();
}
private static async Task MainAsync() {
const int numberOfMessages = 10;
_topicClient = new TopicClient(ServiceBusConnectionString, TopicName);
Console.WriteLine("======================================================");
Console.WriteLine("Press ENTER key to exit after sending all the messages.");
Console.WriteLine("======================================================");
// Send messages.
await SendMessagesAsync(numberOfMessages);
Console.ReadKey();
await _topicClient.CloseAsync();
}
private static async Task SendMessagesAsync(int numberOfMessagesToSend) {
try {
for (var i = 0; i < numberOfMessagesToSend; i++) {
// Create a new message to send to the topic
var messageBody = $"Message {i}";
var message = new Message(Encoding.UTF8.GetBytes(messageBody));
// Write the body of the message to the console
Console.WriteLine($"Sending message: {messageBody}");
// Send the message to the topic
await _topicClient.SendAsync(message);
}
} catch (Exception exception) {
Console.WriteLine($"{DateTime.Now} :: Exception: {exception.Message}");
}
}
}
}
Receiving (based on Worker Role with Service Bus Queue template)
using System;
using System.Diagnostics;
using System.Net;
using System.Threading;
using Microsoft.ServiceBus.Messaging;
using Microsoft.WindowsAzure.ServiceRuntime;
namespace WorkerRoleWithSBQueue1 {
public class WorkerRole : RoleEntryPoint {
// The name of your queue
private const string ServiceBusConnectionString = "<your_connection_string>";
private const string TopicName = "test-topic";
private const string SubscriptionName = "test-sub1";
// QueueClient is thread-safe. Recommended that you cache
// rather than recreating it on every request
private SubscriptionClient _client;
private readonly ManualResetEvent _completedEvent = new ManualResetEvent(false);
public override void Run() {
Trace.WriteLine("Starting processing of messages");
// Initiates the message pump and callback is invoked for each message that is received, calling close on the client will stop the pump.
_client.OnMessage((receivedMessage) => {
try {
// Process the message
Trace.WriteLine("Processing Service Bus message: " + receivedMessage.SequenceNumber.ToString());
var message = receivedMessage.GetBody<byte[]>();
Trace.WriteLine($"Received message: SequenceNumber:{receivedMessage.SequenceNumber} Body:{message.ToString()}");
} catch (Exception e) {
// Handle any message processing specific exceptions here
Trace.Write(e.ToString());
}
});
_completedEvent.WaitOne();
}
public override bool OnStart() {
// Set the maximum number of concurrent connections
ServicePointManager.DefaultConnectionLimit = 12;
// Initialize the connection to Service Bus Queue
_client = SubscriptionClient.CreateFromConnectionString(ServiceBusConnectionString, TopicName, SubscriptionName);
return base.OnStart();
}
public override void OnStop() {
// Close the connection to Service Bus Queue
_client.Close();
_completedEvent.Set();
base.OnStop();
}
}
}
Update #2 (2018/08/10)
After a few suggestions from Arunprabhu and knowing I was using different libraries, below is my current solution with pieces taken from several sources. Is there anything I'm overlooking, adding that shouldering be there, etc? Currently getting an error that may be for another question or already answered so don't want to post it yet before further research.
using System;
using System.Diagnostics;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.ServiceBus;
using Microsoft.WindowsAzure.ServiceRuntime;
namespace WorkerRoleWithSBQueue1 {
public class WorkerRole : RoleEntryPoint {
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private readonly ManualResetEvent _runCompleteEvent = new ManualResetEvent(false);
// The name of your queue
private const string ServiceBusConnectionString = "<your_connection_string>";
private const string TopicName = "test-topic";
private const string SubscriptionName = "test-sub1";
// _client is thread-safe. Recommended that you cache
// rather than recreating it on every request
private SubscriptionClient _client;
public override void Run() {
Trace.WriteLine("Starting processing of messages");
try {
this.RunAsync(this._cancellationTokenSource.Token).Wait();
} catch (Exception e) {
Trace.WriteLine("Exception");
Trace.WriteLine(e.ToString());
} finally {
Trace.WriteLine("Finally...");
this._runCompleteEvent.Set();
}
}
public override bool OnStart() {
// Set the maximum number of concurrent connections
ServicePointManager.DefaultConnectionLimit = 12;
var result = base.OnStart();
Trace.WriteLine("WorkerRole has been started");
return result;
}
public override void OnStop() {
// Close the connection to Service Bus Queue
this._cancellationTokenSource.Cancel();
this._runCompleteEvent.WaitOne();
base.OnStop();
}
private async Task RunAsync(CancellationToken cancellationToken) {
// Configure the client
RegisterOnMessageHandlerAndReceiveMessages(ServiceBusConnectionString, TopicName, SubscriptionName);
_runCompleteEvent.WaitOne();
Trace.WriteLine("Closing");
await _client.CloseAsync();
}
private void RegisterOnMessageHandlerAndReceiveMessages(string connectionString, string topicName, string subscriptionName) {
_client = new SubscriptionClient(connectionString, topicName, subscriptionName);
var messageHandlerOptions = new MessageHandlerOptions(ExceptionReceivedHandler) {
// Maximum number of concurrent calls to the callback ProcessMessagesAsync(), set to 1 for simplicity.
// Set it according to how many messages the application wants to process in parallel.
MaxConcurrentCalls = 1,
// Indicates whether MessagePump should automatically complete the messages after returning from User Callback.
// False below indicates the Complete will be handled by the User Callback as in `ProcessMessagesAsync` below.
AutoComplete = false,
};
_client.RegisterMessageHandler(ProcessMessageAsync, messageHandlerOptions);
}
private async Task ProcessMessageAsync(Message message, CancellationToken token) {
try {
// Process the message
Trace.WriteLine($"Received message: SequenceNumber:{message.SystemProperties.SequenceNumber} Body:{Encoding.UTF8.GetString(message.Body)}");
await _client.CompleteAsync(message.SystemProperties.LockToken);
} catch (Exception e) {
// Handle any message processing specific exceptions here
Trace.Write(e.ToString());
await _client.AbandonAsync(message.SystemProperties.LockToken);
}
}
private static Task ExceptionReceivedHandler(ExceptionReceivedEventArgs exceptionReceivedEventArgs) {
Console.WriteLine($"Message handler encountered an exception {exceptionReceivedEventArgs.Exception}.");
var context = exceptionReceivedEventArgs.ExceptionReceivedContext;
Console.WriteLine("Exception context for troubleshooting:");
Console.WriteLine($"- Endpoint: {context.Endpoint}");
Console.WriteLine($"- Entity Path: {context.EntityPath}");
Console.WriteLine($"- Executing Action: {context.Action}");
return Task.CompletedTask;
}
}
}
Considering the complexity of the updated question Update #1 (2018/08/09), I am providing a separate answer.
The sender and receiver are using different libraries.
Sender - Microsoft.Azure.ServiceBus
Receiver - WindowsAzure.ServiceBus
Microsoft.Azure.ServiceBus has the message object as Message, where WindowsAzure.ServiceBus has BrokeredMessage.
There is a method RegisterMessageHandler available in Microsoft.Azure.ServiceBus, this is the alternative for client.OnMessage() in WindowsAzure.ServiceBus. By using this, the listener receives the message as Message object. This library supports asynchronous programming as you expect.
Refer here for samples from both the libraries.
If you are using Visual Studio, there is a default template available for creating Azure Cloud Service and Worker Role with Service Bus Queue. There you need to change the QueueClient with SubscriptionClient in WorkerRole.cs.
Then, the worker role will stay active, listening for the messages from Topic Subscription.
You can find the samples here. You should create Worker role with Service Bus Queue inside the Cloud Service

Azure mobile service - accessing a table controller from another custom controller

I am trying to access a table using its controller from another controller method.
But when the method tries to call the table controller method I get an exception:
Exception=System.NullReferenceException: Object reference not set to an instance of an object. at Microsoft.WindowsAzure.Mobile.Service.TableController.....
I manage to access the table controller method from the web API and execute it successfully.
I tried the same thing with TodoItem given as an example by the initial mobile service.
After several publishes to the server trying to fix the issue the web API stopped working and I get this exception : An exception of type 'Microsoft.WindowsAzure.MobileServices.MobileServiceInvalidOperationException' occurred in mscorlib.dll but was not handled in user code
Additional information: The request could not be completed. (Internal Server Error) I managed to solve it when I reopened a mobile service and database with the exact same code that didn't work.
Any tips ?
Here is my table controller created by the controller wizard:
using System.Linq;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.OData;
using Microsoft.WindowsAzure.Mobile.Service;
using FringProjectMobileService.DataObjects;
using FringProjectMobileService.Models;
namespace FringProjectMobileService.Controllers
{
public class StorageItemController : TableController<StorageItem>
{
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
FringProjectMobileServiceContext context = new FringProjectMobileServiceContext();
DomainManager = new EntityDomainManager<StorageItem>(context, Request, Services);
}
// GET tables/StorageItem
public IQueryable<StorageItem> GetAllStorageItem()
{
return Query();
}
// GET tables/StorageItem/xxxxxxxxxx
public SingleResult<StorageItem> GetStorageItem(string id)
{
return Lookup(id);
}
// PATCH tables/StorageItem/xxxxxxxx
public Task<StorageItem> PatchStorageItem(string id, Delta<StorageItem> patch)
{
return UpdateAsync(id, patch);
}
// POST tables/StorageItem
public async Task<IHttpActionResult> PostStorageItem(StorageItem item)
{
StorageItem current = await InsertAsync(item);
return CreatedAtRoute("Tables", new { id = current.Id }, current);
}
// DELETE tables/StorageItem/xxxxxxxxxx
public Task DeleteStorageItem(string id)
{
return DeleteAsync(id);
}
}
}
Below the other controller code trying to access the method:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using Microsoft.WindowsAzure.Mobile.Service;
namespace FringProjectMobileService.Controllers
{
public class ArduinoController : ApiController
{
public ApiServices Services { get; set; }
// GET api/Arduino
public string Get()
{
Services.Log.Info("Hello from custom controller!");
return "Hello";
}
public async void PostProcessTag(String id)
{
Microsoft.WindowsAzure.MobileServices.MobileServiceClient client = new Microsoft.WindowsAzure.MobileServices.MobileServiceClient("http://some-service.azure-mobile.net", "XXXXXXXXXXXXXXX");
Microsoft.WindowsAzure.MobileServices.IMobileServiceTable<DataObjects.StorageItem> storage_item_table = client.GetTable<DataObjects.StorageItem>();
await storage_item_table.ToEnumerableAsync();
}
}
}
I also tried a different implementation for the method :
public void PostProcessTag(String id)
{
StorageItemController table_controller = new StorageItemController();
IQueryable<DataObjects.StorageItem> item = table_controller.GetAllStorageItem();
}
The service context:
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
using System.Linq;
using Microsoft.WindowsAzure.Mobile.Service;
using Microsoft.WindowsAzure.Mobile.Service.Tables;
namespace FringProjectMobileService.Models
{
public class FringProjectMobileServiceContext : DbContext
{
// You can add custom code to this file. Changes will not be overwritten.
//
// If you want Entity Framework to alter your database
// automatically whenever you change your model schema, please use data migrations.
// For more information refer to the documentation:
// http://msdn.microsoft.com/en-us/data/jj591621.aspx
//
// To enable Entity Framework migrations in the cloud, please ensure that the
// service name, set by the 'MS_MobileServiceName' AppSettings in the local
// Web.config, is the same as the service name when hosted in Azure.
private const string connectionStringName = "Name=MS_TableConnectionString";
public FringProjectMobileServiceContext() : base(connectionStringName)
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
string schema = ServiceSettingsDictionary.GetSchemaName();
if (!string.IsNullOrEmpty(schema))
{
modelBuilder.HasDefaultSchema(schema);
}
modelBuilder.Conventions.Add(
new AttributeToColumnAnnotationConvention<TableColumnAttribute, string>(
"ServiceTableColumn", (property, attributes) => attributes.Single().ColumnType.ToString()));
}
public System.Data.Entity.DbSet<FringProjectMobileService.DataObjects.StorageItem> StorageItems { get; set; }
}
}

How to make a WCF REST method entirely asynchronous with the Task Parallel Library?

I am trying to make a WCF REST method entirely asynchronous (I don't want to block anywhere). Essentially I have a simple service with 3 layers: Service, Business Logic and Data Access Layer. The Data Access Layer is accessing a database and it can take several second to get a response back from that method.
I don't understand very well how to chaining of all those method work. Can someone please help me to complete the sample I am trying to write below? I don't understand well the pattern used by WCF and I didn't find much documentation on the subject.
Can someone help me to complete the following example? In addition, how can I measure that the service will be able to handle more load than a typical synchronous implementation?
using System;
using System.Collections.Generic;
using System.Runtime.Remoting.Messaging;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using System.Threading.Tasks;
namespace WcfRestService1
{
[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode =
AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class Service1
{
private BusinessLogic bll = new BusinessLogic();
// Synchronous version
[WebGet(UriTemplate = "/sync")]
public string GetSamples()
{
return bll.ComputeData();
}
// Asynchronous version - Begin
[WebGet(UriTemplate = "/async")]
[OperationContract(AsyncPattern = true)]
public IAsyncResult BeginGetSampleAsync(AsyncCallback callback,
object state)
{
Task<string> t = bll.ComputeDataAsync();
// What am I suppose to return here
// return t.AsyncState; ???
}
// Asynchronous version - End
public List<SampleItem> EndGetSampleAsync(IAsyncResult result)
{
// How do I handle the callback here?
}
}
public class BusinessLogic
{
public Task<string> ComputeDataAsync()
{
DataAccessLayer dal = new DataAccessLayer();
return dal.GetData();
}
public string ComputeData()
{
Task<string> t = this.ComputeDataAsync();
// I am blocking... Waiting for the data
t.Wait();
return t.Result;
}
}
public class DataAccessLayer
{
public Task<string> GetData()
{
// Read data from disk or network or db
}
}
}
Here's an example. I got it working with help from the following posts:
Edit: Added an example of an async client
Implement Classic Async Pattern using TPL
http://pfelix.wordpress.com/2008/06/27/wcf-and-the-asyncpattern-property-part-1/
http://pfelix.wordpress.com/2008/06/28/wcf-and-the-asyncpattern-part-2/
Here's a little do-nothing service:
namespace WcfAsyncTest
{
[ServiceContract]
public interface IAsyncTest
{
[OperationContract(AsyncPattern=true)]
IAsyncResult BeginOperation(AsyncCallback callback, object state);
string EndOperation(IAsyncResult ar);
}
// NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service1" in code, svc and config file together.
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class Service1 : IAsyncTest
{
public IAsyncResult BeginOperation(AsyncCallback callback, object state)
{
Task result = Task.Factory.StartNew((x) =>
{
// spin to simulate some work
var stop = DateTime.Now.AddSeconds(10);
while (DateTime.Now < stop)
Thread.Sleep(100);
}, state);
if (callback != null)
result.ContinueWith(t => callback(t));
return result;
}
public string EndOperation(IAsyncResult ar)
{
ar.AsyncWaitHandle.WaitOne();
return "Hello!!";
}
}
}
And here's the client (command line):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TestClient
{
class Program
{
static void Main(string[] args)
{
var client = new ServiceReference1.AsyncTestClient();
var result = client.Operation();
Console.WriteLine(result);
Console.ReadLine();
}
}
}
if you put trace points on the service, you can see that WCF really is calling EndOperation for you.
Async Client Example
First, you will need to generate an async proxy. You can do that by right-clicking on the Service Reference (in the References Folder of your project), and choosing "Configure Service Reference". Check the "Generate Asynchronous Operations" checkbox.
Now your client proxy will have some new members that weren't there before. Here's how to use them:
// this is in the command-line test client
// no changes to your service required.
static void AsyncTest()
{
var client = new ServiceReference1.AsyncTestClient();
client.OperationCompleted += new EventHandler(client_OperationCompleted);
client.OperationAsync();
Console.WriteLine("Operation Running");
}
static void client_OperationCompleted(object sender, ServiceReference1.OperationCompletedEventArgs e)
{
if (e.Error == null)
Console.WriteLine("Operation Complete. Result: " + e.Result);
else
Console.WriteLine(e.Error.ToString());
}
here is an implementation of a service that implements Async. In this the callback of wcf is passed all the way to the ado.net's sql command. When the command returns, it would invoke the service's EndXXX method, which would invoke Business layer, which would finally invoke EndXXX of SqlCommand. Let me know if you face any issues
public class Service
{
private BusinessLogic businessLayer = new BusinessLogic();
public IAsyncResult BeginAnyOperation(AsyncCallback callback, object userState)
{
return businessLayer.BeginComputeData(callback, userState);
}
public string EndAnyOperation(IAsyncResult result)
{
return businessLayer.EndComputeDate(result);
}
}
public class MyState<T> : IAsyncResult
{
public MyState() { }
public object AsyncState { get; set; }
public WaitHandle AsyncWaitHandle { get; set; }
public bool CompletedSynchronously
{
get { return true; }
}
public bool IsCompleted { get; set; }
public AsyncCallback AsyncCallback { get; set; }
public T Result { get; set; }
public IAsyncResult InnerResult { get; set; }
}
public class BusinessLogic
{
private DataAccessLayer dal = new DataAccessLayer();
public IAsyncResult BeginComputeData(AsyncCallback callback, object state)
{
return dal.BeginGetData(callback, state);
}
public string EndComputeDate(IAsyncResult asyncResult)
{
return dal.EndGetData(asyncResult);
}
}
public class DataAccessLayer
{
public IAsyncResult BeginGetData(AsyncCallback callback, object state)
{
var conn = new SqlConnection("");
conn.Open();
SqlCommand cmd = new SqlCommand("myProc", conn);
var commandResult = cmd.BeginExecuteReader(callback, state, System.Data.CommandBehavior.CloseConnection);
return new MyState<string> { AsyncState = cmd, InnerResult = commandResult };
}
public string EndGetData(IAsyncResult result)
{
var state = (MyState<string>)result;
var command = (SqlCommand)state.AsyncState;
var reader = command.EndExecuteReader(state.InnerResult);
if (reader.Read())
return reader.GetString(0);
return string.Empty;
}
}

Categories