I am trying to call a web service which is supposed to run an async task. This async task should iterate through an array of records and process them. Each record to be processed generates a result. What I want is to concatenate in a list these results. When the web service is called, I want to retrieve such list from the HttpResponse of the caller but I do not have an idea how to do it.
The code of the caller function is:
private void ProcessRecords(List<Record> recordList)
{
//The WS is called and a response is awaited
HttpResponseMessage response = client.PostAsJsonAsync("/api/mycontroller/myws/", recordList).Result;
//TODO: Read the result list from the http response
}
The code of my Web Service is as follows:
[HttpPost]
[Route("myws")]
public async Task<IHttpActionResult> WebServiceMethod()
{
var jsonString = await Request.Content.ReadAsStringAsync();
List<Record> recordList = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Record>>(jsonString);
Task<Result> resultTask = CreateTaskToProcessRecords(recordList);
//TODO I Do not now what to return here in order
//for it to contain the resultTask variable and later await it in the user function
}
private Task<List<Result>> CreateTaskToProcessRecords(List<Record> recordList)
{
var newTask = Task.Run<List<Result>>(() =>
{
List<Result> resultList = new List<Result>();
try
{
foreach(Record record in recordList)
{
var result = DoSomething(record);
resultList.Add(result);
}
return resultList;
}
catch (Exception ex)
{
return resultList;
}
});
return newTask;
}
What I am trying to do is to somehow return a Task> to the function that calls the web service so that the whole processing done in the web service "newTask" remains asynchronous.
Do you have any ideas?
Thanks
Luis.
All your work is synchronous. There's no need for async or await here, so don't use them. (And as a general rule, avoid Task.Run on ASP.NET).
[HttpPost]
[Route("myws")]
public IHttpActionResult WebServiceMethod()
{
var jsonString = await Request.Content.ReadAsStringAsync();
List<Record> recordList = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Record>>(jsonString);
var results = ProcessRecords(recordList);
return Json(results);
}
private List<Result> ProcessRecords(List<Record> recordList)
{
List<Result> resultList = new List<Result>();
try
{
foreach(Record record in recordList)
{
var result = DoSomething(record);
resultList.Add(result);
}
return resultList;
}
catch (Exception ex)
{
return resultList;
}
}
Note that you can still consume it asynchronously on the client:
private async Task ProcessRecordsAsync(List<Record> recordList)
{
// The WS is called and a response is awaited
HttpResponseMessage response = await client.PostAsJsonAsync("/api/mycontroller/myws/", recordList);
var result = await response.Content.ReadAsAsync<List<Result>>();
}
Related
Making a network request is easy to Python and what makes it easy is that sync request.
In Dart, I can make a request like this:
HttpClient client = new HttpClient();
client.getUrl(Uri.parse("http://www.example.com/"))
.then((HttpClientRequest request) {
// Optionally set up headers...
// Optionally write to the request object...
// Then call close.
...
return request.close();
})
.then((HttpClientResponse response) {
// Process the response.
...
});
Obviously it's async request. In my opinion, above code can be reused many times. So I want to make a request and return a JSON object.
getResponse(String url) async {
HttpClient httpClient = new HttpClient();
HttpClientRequest request = await httpClient.getUrl(Uri.parse(url));
HttpClientResponse response = await request.close();
String responseBody = await response.transform(utf8.decoder).join();
Map jsonResponse = jsonDecode(responseBody) as Map;
httpClient.close();
return jsonResponse;
}
As you see, the above method getResponse returns Future<dynamic>. So how can I call it and get the json value?
To get the dynamic from inside the Future you do one of the following:
// option 1 async method
MyAsyncMethod() async {
dynamic result = await getResponse("http://google.com");
if (result is Map) {
// process the data
}
}
// option 2 callback with .then()
MyNonAsyncMethod() {
getResponse("http://google.com").then ( (dynamic result) {
if (result is Map) {
// process the data
}
});
}
Note that your own async method can also return a Future<something> and be treated in the same two ways when called.
Where map is a nested Map<String, Dynamic> and result is an object of type of your creation that conforms to Json serialization interface (See this link).
To access Data in the map:
//given example json structure
var map = {
"myPropertyName":50,
"myArrayProperty":[
"anArrayEntry"
],
"mySubobject": {
"subObjectProperty":"someValue"
}
};
var myProperty = map["myPropertyName"]; // get a property value from the object
var myArrayEntry = map["myArrayProperty"][0]; // get first element of an array property
var mySubobjectPropertyValue = map["mySubobject"]["subObjectProperty"]; // get a property value from a subobject
I have an async method that calls an API, then if the response returned is a PDF, it returns the file
If the response is NOT a pdf (ie: a JSON object with an error value), how can I stay on the current page and simply display the error message?
The application is underpinned by Umbraco and it currently sets the ViewBag and then returns RedirectToCurrentUmbracoPage which means the page displays only the JSON object
public async Task<ActionResult> QuotationPdfAsync(DbCalculationInput calculation)
{
var content = await _apiClient.QuotationAsync(calculation);
if (content.HasPdf)
{
var fileName = $"{calculation.CalculationName}_{DateTime.Now:yyyyMMdd}_{DateTime.Now:HHmmss}.pdf";
return this.File(content.Pdf, "application/pdf", fileName);
}
this.ViewBag.FormError = content.ErrorResponse.FailureDescription;
return this.RedirectToCurrentUmbracoPage();
}
public async Task<ActionResult> QuotationPdfAsync(DbCalculationInput calculation)
{
var content = await _apiClient.QuotationAsync(calculation);
if (content.HasPdf)
{
var fileName = $"{calculation.CalculationName}_{DateTime.Now:yyyyMMdd}_{DateTime.Now:HHmmss}.pdf";
return this.File(content.Pdf, "application/pdf", fileName);
}
else
{
this.ViewBag.FormError = content.ErrorResponse.FailureDescription;
return Json("So and so error..");
}
return this.RedirectToCurrentUmbracoPage();
}
Amazon provides a vast documentation, but there are so many docs that I'm lost, so here is my current service for upload/download files. Upload works as expected but on the download its where I have to download the files to a physical path and later serve the download to the user, I don't have much experience working with streams. Here is the FileManagerService class that connects to Amazon API.
using Amazon.S3;
using Amazon.S3.Model;
public class FileManagerService
{
public FileManagerService()
{
string serverPath = HttpContext.Current.Server.MapPath("~/");
string uploadPath = Path.Combine(serverPath, "FileUploads");
Directory.CreateDirectory(uploadPath);
UploadDirectory = uploadPath;
}
private string UploadDirectory { get; set; }
private docucloudEntities db = new docucloudEntities();
private IAmazonS3 S3Client = new AmazonS3Client();
private string S3Bucket = "bucketname";
public async Task<string> DownloadFile(string AmazonFileKey, string FileName)
{
var fileRequest = new GetObjectRequest
{
BucketName = S3Bucket,
Key = AmazonFileKey
};
var localRoute = Path.Combine(UploadDirectory, FileName);
using (var fileObject = await S3Client.GetObjectAsync(fileRequest))
{
if (fileObject.HttpStatusCode == HttpStatusCode.OK)
{
fileObject.WriteResponseStreamToFile(localRoute);
}
}
return localRoute;
}
}
This method returns the string, it's not complete yet with try catch blocks, but it currently works. Here is my controller method that download the file to the client:
public class FileManagerController : Controller
{
private FileManagerService FileService = new FileManagerService();
public async Task<ActionResult> DownloadFileAmazon(long FileId)
{
if (db.Archivos.Any(i => i.ArchivoID == FileId))
{
var archivo = db.Archivos.Single(i => i.ArchivoID == FileId);
var rutaarchivo = await FileService.DownloadFile(archivo.Ruta, archivo.Nombre);
if (System.IO.File.Exists(rutaarchivo))
{
var fileBytes = System.IO.File.ReadAllBytes(rutaarchivo);
var response = new FileContentResult(fileBytes, "application/octet-stream");
response.FileDownloadName = archivo.Nombre;
System.IO.File.Delete(rutaarchivo);
return response;
}else
{
return HttpNotFound();
}
}else
{
return HttpNotFound();
}
}
}
So here on the controller I read the file bytes and serve the download, after deleting the file, but this could lead to a slower perfomance, its there a way of achieving direct download.
As far as I can tell there is no reason to dispose GetObjectResponse (return type of GetObjectAsync) even if the docs says so. GetObjectResponse is not implementing IDisposable but is inheriting StreamResponse that is. However, as far as I can tell it's only disposing the ResponseStream. So you could return the stream from GetObjectResponse (fileObject.ResponseStream) together with the ContentTypefrom the headers (fileObject.Headers.ContentType) that you then can return as a file in your controller:
[HttpGet]
[Route("blob/{filename}")]
public async Task<IActionResult> GetFile(string filename)
{
try
{
var file = await _fileStorageService.GetBlobAsync(filename);
return File(file.Stream, file.ContentType);
}
catch (Exception ex)
{
// Handle exceptions
}
}
FileResult will dispose the stream after it has written the file so there the stream will finally get disposed.
I want to return a String from an async function but I get a Future
What am I doing wrong;
Example
main() {
String s;
s = dummy("http://www.google.com");
}
String dummy(String s) {
String response;
response = readURL(s);
return response;
}
Future<String> readURL(String requestString) async {
String response = await http.read(requestString);
print(response);
return response;
}
Error:
type '_Future' is not a subtype of type 'String' of 'response'.
A function that's annotated with async will always return a Future.
so when you call readUrl(s) you can await its result.
To use await, the caller (here your main function) has to be marked as async. So the end result could look like this:
main() async {
String s = await dummy("http://www.google.com");
}
Future<String> dummy(String s) async {
String response = await readURL(s);
return (response);
}
Future<String> readURL(String requestString) async {
String response = await http.read(requestString);
print(response);
return(response);
}
The thing to notice here: If you use await in a function, it is now considered as function that returns a Future. So every function you convert to be async will now return a Future.
Here is the Simple Two way to get value from Function with return type Future<Type>
1- First way (best way, as you call this code from any file)
FutureFunctionName.then((val) {
val contains data
});
For example- (I am posting one from real example)
Future<String> getUserAgents() async {
String userAgent;
await FlutterUserAgent.init();
userAgent = FlutterUserAgent.webViewUserAgent;
return userAgent;
}
String userAgent;
getUserAgents().then((val) {
userAgent = val;
});
print(userAgent); // you will get output
2- Second way (use a global variable to get data)
String userAgent;
Future<void> getUserAgents() async {
await FlutterUserAgent.init();
userAgent = FlutterUserAgent.webViewUserAgent;
}
print(userAgent);
I have next controller
public async Task<ActionResult> ImageAsync(int id)
{
var img = await _repository.GetImageAsync(id);
if (img != null)
{
return File(img, "image/jpg"); //View(img);
}
byte[] res = new byte[0];
return File(res, "image/jpg");
}
and method in repository
public async Task<byte[]> GetImage(int imageId)
{
try
{
var dbCtx = new smartbags_storeEntities();
var res = await dbCtx.GoodImages.SingleAsync(d => d.ImageId == imageId);
return res != null ? res.ImageData : null;
}
catch (Exception ex)
{
throw ex;
}
}
public async Task<byte[]> GetImageAsync(int imageId)
{
byte[] img = await Task.Run(() =>
{
var res = GetImage(imageId).Result;
if (res != null)
{
var wi = new System.Web.Helpers.WebImage(res);
wi.AddTextWatermark("info");
return wi.GetBytes();
}
return null;
});
return img;
}
but execution of image reading is freezing on line
var res = await dbCtx.GoodImages.SingleAsync(d => d.ImageId == imageId);
What I am doing in wrong way when try to read data from data base in async style ?
The call to the property Result of a Task is a blocking call and the continuation of the await won't be able to be posted to run.
Once you already have a Task returning method, why didn't you just use await?
public async Task<byte[]> GetImageAsync(int imageId)
{
var res = await GetImage(imageId);
if (res != null)
{
var wi = new System.Web.Helpers.WebImage(res);
wi.AddTextWatermark("info");
return wi.GetBytes();
}
return null;
}
The funny thing about that line is that it calls SingleAsync, which is a TAP extension method for observables.
I have never used a data repository that exposed its collections as observables, though I suppose it is possible. My first guess is that [the task returned by] SingleAsync isn't completing because the GoodImages observable isn't completing. Note that SingleAsync must continue scanning after it sees a match to ensure that it is the only match; FirstAsync is more forgiving and will complete as soon as it sees the first match.
On a side note, I do recommend using await instead of Result and not using Task.Run on the server. So Paulo's answer is good in that regard, though in this case Result is not causing a deadlock.