Better way to download files directly using Amazon S3 API - SDK on ASP.MVC - asp.net-mvc

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.

Related

Cannot open file after downloading from Server

After downloaded a file from Server (pdf, jpg,..) successfully, I couldn't open that file in my computer.
It said "It looks like we don't support this file format". Files are stored and readable on Server.
Wonder if there is something missing in my Download Function:
[HttpGet]
public ActionResult Download(Guid? attachmentId)
{
var visitAttachment = _visitAttachmentService.FindOne(x => x.Id == attachmentId);
try
{
var serverPath = Server.MapPath(visitAttachment.Path);
byte[] fileBytes = System.IO.File.ReadAllBytes(serverPath);
return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, visitAttachment.AttachmentName);
}
catch
{
return File(Encoding.UTF8.GetBytes(""), System.Net.Mime.MediaTypeNames.Application.Octet, visitAttachment.AttachmentName);
}
}
It seems like file not downloaded properly, try this, may it help, good luck
FileDownload(yourfilepath ,yourfilenamewithFormat)
{
string filename = yourfilenamewithFormat;
byte[] file_Bytes = System.IO.File.ReadAllBytes(yourfilepath);
return File(file_Bytes, System.Net.Mime.MediaTypeNames.Application.Octet, filename);
}
My opinion, is that you can be missing the file extension. If that is the case you can get it by the using Path.GetExtension(serverPath)
Edited
Try to use FileResult instead of ActionResult
[HttpGet]
public FileResult Download(Guid? attachmentId)
{
var visitAttachment = _visitAttachmentService.FindOne(x => x.Id == attachmentId);
try
{
var serverPath = Server.MapPath(visitAttachment.Path);
byte[] fileBytes = System.IO.File.ReadAllBytes(serverPath);
return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, visitAttachment.AttachmentName);
}
catch
{
return File(Encoding.UTF8.GetBytes(""), System.Net.Mime.MediaTypeNames.Application.Octet, visitAttachment.AttachmentName);
}
}

How to store recordings for each channel

I am following following blog to click to call.
https://www.twilio.com/docs/tutorials/click-to-call-java-spring
I am able to place call and record too. But how can we store recording for each channel ?
Can someone please help.?
callCreator.setRecordingChannels("5"); or
callCreator.setRecordingChannels("dual");
does not help
Here is the complete class
public class TwilioLine {
private String twilioNumber;
private TwilioRestClient restClient;
public TwilioLine(TwilioRestClient restClient, String twilioNumber) {
this.restClient = restClient;
this.twilioNumber = twilioNumber;
}
public void call(final String phoneNumber, final String responseUrl) {
try {
CallCreator callCreator = new CallCreator(new PhoneNumber(phoneNumber), new PhoneNumber(twilioNumber), new URI(responseUrl));
callCreator.setRecord(true);
callCreator.setSendDigits("8245719056");
callCreator.setRecordingChannels("5");
callCreator.create(restClient);
} catch (URISyntaxException e) {
throw new CallException(e);
}
}
}

Continuously output from StandardOutput to text box in Visual C# [duplicate]

I have an external dll written in C# and I studied from the assemblies documentation that it writes its debug messages to the Console using Console.WriteLine.
this DLL writes to console during my interaction with the UI of the Application, so i don't make DLL calls directly, but i would capture all console output , so i think i got to intialize in form load , then get that captured text later.
I would like to redirect all the output to a string variable.
I tried Console.SetOut, but its use to redirect to string is not easy.
As it seems like you want to catch the Console output in realtime, I figured out that you might create your own TextWriter implementation that fires an event whenever a Write or WriteLine happens on the Console.
The writer looks like this:
public class ConsoleWriterEventArgs : EventArgs
{
public string Value { get; private set; }
public ConsoleWriterEventArgs(string value)
{
Value = value;
}
}
public class ConsoleWriter : TextWriter
{
public override Encoding Encoding { get { return Encoding.UTF8; } }
public override void Write(string value)
{
if (WriteEvent != null) WriteEvent(this, new ConsoleWriterEventArgs(value));
base.Write(value);
}
public override void WriteLine(string value)
{
if (WriteLineEvent != null) WriteLineEvent(this, new ConsoleWriterEventArgs(value));
base.WriteLine(value);
}
public event EventHandler<ConsoleWriterEventArgs> WriteEvent;
public event EventHandler<ConsoleWriterEventArgs> WriteLineEvent;
}
If it's a WinForm app, you can setup the writer and consume its events in the Program.cs like this:
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
using (var consoleWriter = new ConsoleWriter())
{
consoleWriter.WriteEvent += consoleWriter_WriteEvent;
consoleWriter.WriteLineEvent += consoleWriter_WriteLineEvent;
Console.SetOut(consoleWriter);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
static void consoleWriter_WriteLineEvent(object sender, Program.ConsoleWriterEventArgs e)
{
MessageBox.Show(e.Value, "WriteLine");
}
static void consoleWriter_WriteEvent(object sender, Program.ConsoleWriterEventArgs e)
{
MessageBox.Show(e.Value, "Write");
}
It basically amounts to the following:
var originalConsoleOut = Console.Out; // preserve the original stream
using(var writer = new StringWriter())
{
Console.SetOut(writer);
Console.WriteLine("some stuff"); // or make your DLL calls :)
writer.Flush(); // when you're done, make sure everything is written out
var myString = writer.GetStringBuilder().ToString();
}
Console.SetOut(originalConsoleOut); // restore Console.Out
So in your case you'd set this up before making calls to your third-party DLL.
You can also call SetOut with Console.OpenStandardOutput, this will restore the original output stream:
Console.SetOut(new StreamWriter(Console.OpenStandardOutput()));
Or you can wrap it up in a helper method that takes some code as an argument run it and returns the string that was printed. Notice how we gracefully handle exceptions.
public string RunCodeReturnConsoleOut(Action code)
{
string result;
var originalConsoleOut = Console.Out;
try
{
using (var writer = new StringWriter())
{
Console.SetOut(writer);
code();
writer.Flush();
result = writer.GetStringBuilder().ToString();
}
return result;
}
finally
{
Console.SetOut(originalConsoleOut);
}
}
Using solutions proposed by #Adam Lear and #Carlo V. Dango I created a helper class:
public sealed class RedirectConsole : IDisposable
{
private readonly Action<string> logFunction;
private readonly TextWriter oldOut = Console.Out;
private readonly StringWriter sw = new StringWriter();
public RedirectConsole(Action<string> logFunction)
{
this.logFunction = logFunction;
Console.SetOut(sw);
}
public void Dispose()
{
Console.SetOut(oldOut);
sw.Flush();
logFunction(sw.ToString());
sw.Dispose();
}
}
which can be used in the following way:
public static void MyWrite(string str)
{
// print console output to Log/Socket/File
}
public static void Main()
{
using(var r = new RedirectConsole(MyWrite)) {
Console.WriteLine("Message 1");
Console.WriteLine("Message 2");
}
// After the using section is finished,
// MyWrite will be called once with a string containing all messages,
// which has been written during the using section,
// separated by new line characters
}

Check if uploaded file is an image in C# ASP.NET MVC

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");

Session Variables Lost Between Controllers & Action Methods

I have almost exactly the same scenario described by Nathon Taylor in ASP.NET MVC - Sharing Session State Between Controllers. The problem is that if I save the path to the images inside a Session variable List<string> it is not being defined back in the ItemController so all the paths are being lost... Here's my setup:
Inside ImageController I have the Upload() action method:
public ActionResult Upload()
{
var newFile = System.Web.HttpContext.Current.Request.Files["Filedata"];
string guid = Guid.NewGuid() + newFile.FileName;
string itemImagesFolder = Server.MapPath(Url.Content("~/Content/ItemImages/"));
string fileName = itemImagesFolder + "originals/" + guid;
newFile.SaveAs(fileName);
var resizePath = itemImagesFolder + "temp/";
string finalPath;
foreach (var dim in _dimensions)
{
var resizedPath = _imageService.ResizeImage(fileName, resizePath, dim.Width + (dim.Width * 10/100), guid);
var bytes = _imageService.CropImage(resizedPath, dim.Width, dim.Height, 0, 0);
finalPath = itemImagesFolder + dim.Title + "/" + guid;
_imageService.SaveImage(bytes, finalPath);
}
AddToSession(guid);
var returnPath = Url.Content("~/Content/ItemImages/150x150/" + guid);
return Content(returnPath);
}
private void AddToSession(string fileName)
{
if(Session[SessionKeys.Images] == null)
{
var imageList = new List<string>();
Session[SessionKeys.Images] = imageList;
}
((List<string>)Session[SessionKeys.Images]).Add(fileName);
}
Then inside my ItemController I have the New() action method which has the following code:
List<string> imageNames;
var images = new List<Image>();
if (Session[SessionKeys.Images] != null) //always returns false
{
imageNames = Session[SessionKeys.Images] as List<string>;
int rank = 1;
foreach (var name in imageNames)
{
var img = new Image {Name = name, Rank = rank};
images.Add(img);
rank++;
}
}
Ok so why is this happening and how do I solve it?
Also, I was thinking of whether I could move the ActionMethod that takes care of the upload of the images into the ItemController and store the image paths inside a List property on the ItemController itself, would that actually work? Note though, that images are being uploaded and taken care of via an AJAX request. Then when the user submits the item entry form, all the data about the Item along with the images should be saved to the database...
Update:
I've updated the code. Also I think I should add that I'm using StructureMap as my controller factorory. Could it be a scoping issue? What is the default scope that is usually used by StructureMap?
public class StructureMapDependencyResolver : IDependencyResolver
{
public StructureMapDependencyResolver(IContainer container)
{
_container = container;
}
public object GetService(Type serviceType)
{
if (serviceType.IsAbstract || serviceType.IsInterface)
{
return _container.TryGetInstance(serviceType);
}
else
{
return _container.GetInstance(serviceType);
}
}
public IEnumerable<object> GetServices(Type serviceType)
{
return _container.GetAllInstances<object>()
.Where(s => s.GetType() == serviceType);
}
private readonly IContainer _container;
}
And inside my Global.asax file:
private static IContainer ConfigureStructureMap()
{
ObjectFactory.Configure(x =>
{
x.For<IDatabaseFactory>().Use<EfDatabaseFactory>();
x.For<IUnitOfWork>().Use<UnitOfWork>();
x.For<IGenericMethodsRepository>().Use<GenericMethodsRepository>();
x.For<IUserService>().Use<UsersManager>();
x.For<IBiddingService>().Use<BiddingService>();
x.For<ISearchService>().Use<SearchService>();
x.For<IFaqService>().Use<FaqService>();
x.For<IItemsService>().Use<ItemsService>();
x.For<IMessagingService>().Use<MessagingService>();
x.For<IStaticQueriesService>().Use<StaticQueriesService>();
x.For < IImagesService<Image>>().Use<ImagesService>();
x.For<ICommentingService>().Use<CommentingService>();
x.For<ICategoryService>().Use<CategoryService>();
x.For<IHelper>().Use<Helper>();
x.For<HttpContext>().HttpContextScoped().Use(HttpContext.Current);
x.For(typeof(Validator<>)).Use(typeof(NullValidator<>));
x.For<Validator<Rating>>().Use<RatingValidator>();
x.For<Validator<TopLevelCategory>>().Use<TopLevelCategoryValidator>();
});
Func<Type, IValidator> validatorFactory = type =>
{
var valType = typeof(Validator<>).MakeGenericType(type);
return (IValidator)ObjectFactory.GetInstance(valType);
};
ObjectFactory.Configure(x => x.For<IValidationProvider>().Use(() => new ValidationProvider(validatorFactory)));
return ObjectFactory.Container;
}
Any thoughts?
I just added this to Global.asax.cs
protected void Session_Start()
{
}
It seems that this fixed the issue. I set a breakpoint that gets hit only once per session (as expected).
One possible reason for this is that the application domain restarts between the first and the second actions and because session is stored in memory it will be lost. This could happen if you recompile the application between the two. Try putting a breakpoints in the Application_Start and Session_Start callbacks in Global.asax and see if they are called twice.
Are you ever using it other than accessing HttpContext.Current directly in your code? In other words, are there any places where you're injecting the HttpContext for the sake of mocking in unit tests?
If you're only accessing it directly in your methods, then there's no reason to have the entry x.For<HttpContext>().HttpContextScoped().Use(HttpContext.Current); in you application startup. I wonder if it would start working if you removed it.

Resources