I have an asp.net MVC application which allows a user to upload a PDF/image onto the system. I then want to send this PDF onto the Azure Read API and store the returned text in a .text file on the system (so I can later enter some of the data into a database).
I have it working perfectly while testing it in a Console Application, although I can not get it to work when I try implement it into my MVC web app; when I upload a PDF, the file gets uploaded although nothing else happens i.e. no text file is created with the returned data. When I try this with the same Azure API methods in a Console App it works fine (file is created with the returned text)
My Controller:
public ActionResult Upload()
{
return View();
}
[HttpPost]
public ActionResult Upload(HttpPostedFileBase file)
{
string filename = Guid.NewGuid() + Path.GetExtension(file.FileName);
string filepath = @"C:\Users\35385\source\repos\BookingSystem\BookingSystem\Surveys\" + filename;
file.SaveAs(Path.Combine(Server.MapPath("/Surveys"), filename));
AzureVisionAPI.ExtractToTextFile(filepath);
return View();
}
My Azure API call helper methods:
namespace BookingSystem.Helpers
{
static class AzureVisionAPI
{
static string subscriptionKey = ("SUBSCRIPTON_KEY");
static string endpoint = ("END_POINT");
public static ComputerVisionClient Authenticate(string endpoint, string key)
{
ComputerVisionClient client =
new ComputerVisionClient(new ApiKeyServiceClientCredentials(key))
{ Endpoint = endpoint };
return client;
}
public static void ExtractToTextFile(string filename)
{
ComputerVisionClient client = Authenticate(endpoint, subscriptionKey);
ExtractTextLocal(client, filename).Wait();
}
public static async Task ExtractTextLocal(ComputerVisionClient client, string localImage)
{
// Helps calucalte starting index to retrieve operation ID
const int numberOfCharsInOperationId = 36;
using (Stream imageStream = File.OpenRead(localImage))
{
// Read the text from the local image
BatchReadFileInStreamHeaders localFileTextHeaders = await client.BatchReadFileInStreamAsync(imageStream);
// Get the operation location (operation ID)
string operationLocation = localFileTextHeaders.OperationLocation;
// Retrieve the URI where the recognized text will be stored from the Operation-Location header.
string operationId = operationLocation.Substring(operationLocation.Length - numberOfCharsInOperationId);
// Extract text, wait for it to complete.
int i = 0;
int maxRetries = 10;
ReadOperationResult results;
do
{
results = await client.GetReadOperationResultAsync(operationId);
await Task.Delay(1000);
if (maxRetries == 9)
{
throw new Exception("Azure API timed out");
}
}
while ((results.Status == TextOperationStatusCodes.Running ||
results.Status == TextOperationStatusCodes.NotStarted) && i++ < maxRetries);
// Display the found text.
var textRecognitionLocalFileResults = results.RecognitionResults;
foreach (TextRecognitionResult recResult in textRecognitionLocalFileResults)
{
using (StreamWriter sw = new StreamWriter(@"C:\Users\35385\source\repos\BookingSystem\BookingSystem\surveytest.txt"))
{
foreach (Line line in recResult.Lines)
{
sw.WriteLine(line.Text);
}
}
}
}
}
}
}
You are likely seeing an async deadlock here. Instead of using .Wait()
you should await the Task
returned from your async method.
What happens is at your first await
the method returns, to the method that then calls .Wait()
, this blocks the thread and has exclusive access to your current SynchronizationContext
, when the inner awaited thing completes, the following code is queued up on the same SynchronizationContext
, which is blocked. The .Wait()
will never stop blocking because the Task
can never complete. This is know as an async deadlock.
The best way to deal with it here is make your controller async and return Task<ActionResult>
, then use async/await top-to-bottom. Another way to handle this is to scatter .ConfigureAwait(false)
to all of your Task
you await
, however it's best to just not block when dealing with asynchronous work.
Your code might look like:
Controller:
public ActionResult Upload()
{
return View();
}
[HttpPost]
public async Task<ActionResult> Upload(HttpPostedFileBase file)
{
string filename = Guid.NewGuid() + Path.GetExtension(file.FileName);
string filepath = @"C:\Users\35385\source\repos\BookingSystem\BookingSystem\Surveys\" + filename;
file.SaveAs(Path.Combine(Server.MapPath("/Surveys"), filename));
await AzureVisionAPI.ExtractToTextFile(filepath);
return View();
}
AzureVisionAPI:
namespace BookingSystem.Helpers
{
static class AzureVisionAPI
{
static string subscriptionKey = ("SUBSCRIPTON_KEY");
static string endpoint = ("END_POINT");
public static ComputerVisionClient Authenticate(string endpoint, string key)
{
ComputerVisionClient client =
new ComputerVisionClient(new ApiKeyServiceClientCredentials(key))
{ Endpoint = endpoint };
return client;
}
public static async Task ExtractToTextFile(string filename)
{
ComputerVisionClient client = Authenticate(endpoint, subscriptionKey);
await ExtractTextLocal(client, filename);
}
public static async Task ExtractTextLocal(ComputerVisionClient client, string localImage)
{
// Helps calucalte starting index to retrieve operation ID
const int numberOfCharsInOperationId = 36;
using (Stream imageStream = File.OpenRead(localImage))
{
// Read the text from the local image
BatchReadFileInStreamHeaders localFileTextHeaders = await client.BatchReadFileInStreamAsync(imageStream);
// Get the operation location (operation ID)
string operationLocation = localFileTextHeaders.OperationLocation;
// Retrieve the URI where the recognized text will be stored from the Operation-Location header.
string operationId = operationLocation.Substring(operationLocation.Length - numberOfCharsInOperationId);
// Extract text, wait for it to complete.
int i = 0;
int maxRetries = 10;
ReadOperationResult results;
do
{
results = await client.GetReadOperationResultAsync(operationId);
await Task.Delay(1000);
if (maxRetries == 9)
{
throw new Exception("Azure API timed out");
}
}
while ((results.Status == TextOperationStatusCodes.Running ||
results.Status == TextOperationStatusCodes.NotStarted) && i++ < maxRetries);
// Display the found text.
var textRecognitionLocalFileResults = results.RecognitionResults;
foreach (TextRecognitionResult recResult in textRecognitionLocalFileResults)
{
using (StreamWriter sw = new StreamWriter(@"C:\Users\35385\source\repos\BookingSystem\BookingSystem\surveytest.txt"))
{
foreach (Line line in recResult.Lines)
{
sw.WriteLine(line.Text);
}
}
}
}
}
}
}