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();
}
Related
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 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>>();
}
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.
i have a view where i put the id of the event then i can download all the images for that event.....
here's my code
[HttpPost]
public ActionResult Index(FormCollection All)
{
try
{
var context = new MyEntities();
var Im = (from p in context.Event_Photos
where p.Event_Id == 1332
select p.Event_Photo);
Response.Clear();
var downloadFileName = string.Format("YourDownload-{0}.zip", DateTime.Now.ToString("yyyy-MM-dd-HH_mm_ss"));
Response.ContentType = "application/zip";
Response.AddHeader("content-disposition", "filename=" + downloadFileName);
using (ZipFile zipFile = new ZipFile())
{
zipFile.AddDirectoryByName("Files");
foreach (var userPicture in Im)
{
zipFile.AddFile(Server.MapPath(#"\") + userPicture.Remove(0, 1), "Files");
}
zipFile.Save(Response.OutputStream);
//Response.Close();
}
return View();
}
catch (Exception ex)
{
return View();
}
}
The problem is that each time i get html page to download so instead of downloading "Album.zip" i get "Album.html" any ideas???
In MVC, rather than returning a view, if you want to return a file, you can return this as an ActionResult by doing:
return File(zipFile.GetBytes(), "application/zip", downloadFileName);
// OR
return File(zipFile.GetStream(), "application/zip", downloadFileName);
Don't mess about with manually writing to the output stream if you're using MVC.
I'm not sure if you can get the bytes or the stream from the ZipFile class though. Alternatively, you might want it to write it's output to a MemoryStream and then return that:
var cd = new System.Net.Mime.ContentDisposition {
FileName = downloadFileName,
Inline = false,
};
Response.AppendHeader("Content-Disposition", cd.ToString());
var memStream = new MemoryStream();
zipFile.Save(memStream);
memStream.Position = 0; // Else it will try to read starting at the end
return File(memStream, "application/zip");
And by using this, you can remove all lines in which you are doing anything with the Response. No need to Clear or AddHeader.
I have my controller
[HttpPost]
public ActionResult ChangeAvatar(HttpPostedFileBase file)
{
AvatarHelper.AvatarUpdate(file, User.Identity.Name);
return RedirectToAction("Index", "Profile");
}
And I already check if file is in jpeg/png format:
private static bool IsImage(string contentType)
{
return AllowedFormats.Any(format => contentType.EndsWith(format,
StringComparison.OrdinalIgnoreCase));
}
public static List<string> AllowedFormats
{
get { return new List<string>() {".jpg", ".png", ".jpeg"}; }
}
What I need - it ensure that uploaded file is real image file and not txt file with image extension.
I convert my uploaded file like this:
using (var image = System.Drawing.Image.FromStream(postedFile.InputStream))
{
///image stuff
}
I am thinking about try/catch block on creating image from input stream but I wonder if there is good way to do it?
Thanks)
P.S.
I wonder if there is another (more efficient way that try/catch block) way to check whether file is real image?
You could use the RawFormat property:
private static ImageFormat[] ValidFormats = new[] { ImageFormat.Jpeg, ImageFormat.Png };
public bool IsValid(Stream image)
{
try
{
using (var img = Image.FromStream(file.InputStream))
{
return ValidFormats.Contains(img.RawFormat);
}
}
catch
{
return false;
}
}
Also you could put this validation logic into a reusable validation attribute as I have shown in this post.
My solution as an extension, actually checking if a base64 string is an image or not:
public static bool IsImage(this string base64String)
{
byte[] imageBytes = Convert.FromBase64String(base64String);
var stream = new MemoryStream(imageBytes, 0, imageBytes.Length);
try
{
stream.Write(imageBytes, 0, imageBytes.Length);
System.Drawing.Image image = System.Drawing.Image.FromStream(stream, true);
return true;
}
catch (Exception)
{
return false;
}
}
Usage:
if(!"base64string".IsImage())
throw new Exception("Not an image");