Blazor Server: Creating email attachments from files uploaded via InputFile - blazor-server-side

I am trying to send emails with attachments attached to the email. I have a InputFile with a progress bar that I am able to upload files. I have attempted to use the memory stream to make attachments to the MailMessage class. The issue is that when the email is received, I am able to see the attachments but I can't read or view the contents of the attachments. I've posted my code below so you can replicate the issue that I am having (Make sure to install the Meziantou.Framework.ByteSize nuget package)
#using System.Net.Mail
#using System.Globalization
#using Meziantou.Framework
<InputFile OnChange="e => LoadFiles(e)" multiple></InputFile>
#foreach (var file in uploadedFiles)
{
<div>
#file.FileName
<progress value="#file.UploadedBytes" max="#file.Size"></progress>
#file.UploadedPercentage.ToString("F1")%
(#FormatBytes(file.UploadedBytes) / #FormatBytes(file.Size))
</div>
}
<button type="button" #onclick="#HandleNotifSubmit" class="btn btn-primary submit">Send Email</button>
#code {
private MemoryStream fileContents { get; set; }
List<FileUploadProgress> uploadedFiles = new();
MailMessage message = new MailMessage();
StreamWriter writer { get; set; }
private async ValueTask LoadFiles(InputFileChangeEventArgs e)
{
var files = e.GetMultipleFiles(maximumFileCount: 100);
var startIndex = uploadedFiles.Count;
// Add all files to the UI
foreach (var file in files)
{
var progress = new FileUploadProgress(file.Name, file.Size);
uploadedFiles.Add(progress);
}
// We don't want to refresh the UI too frequently,
// So, we use a timer to update the UI every few hundred milliseconds
await using var timer = new Timer(_ => InvokeAsync(() => StateHasChanged()));
timer.Change(TimeSpan.FromMilliseconds(500), TimeSpan.FromMilliseconds(500));
// Upload files
byte[] buffer = System.Buffers.ArrayPool<byte>.Shared.Rent(4096);
try
{
foreach (var file in files)
{
using var stream = file.OpenReadStream(maxAllowedSize: 10 * 1024 * 1024);
while (await stream.ReadAsync(buffer) is int read && read > 0)
{
uploadedFiles[startIndex].UploadedBytes += read;
var readData = buffer.AsMemory().Slice(0, read);
}
fileContents = new MemoryStream(buffer);
writer = new StreamWriter(fileContents);
fileContents.Position = 0;
message.Attachments.Add(new Attachment(fileContents, file.Name));
startIndex++;
}
}
finally
{
System.Buffers.ArrayPool<byte>.Shared.Return(buffer);
StateHasChanged();
}
}
string FormatBytes(long value)
=> ByteSize.FromBytes(value).ToString("fi2", CultureInfo.CurrentCulture);
record FileUploadProgress(string FileName, long Size)
{
public long UploadedBytes { get; set; }
public double UploadedPercentage => (double)UploadedBytes / (double)Size * 100d;
}
private async void HandleNotifSubmit()
{
try
{
var sClient = new SmtpClient("FAKECOMPANYCLIENT");
sClient.Port = 25;
sClient.UseDefaultCredentials = false;
message.Subject = "Hello World";
message.From = new MailAddress("test#gmail.com");
message.IsBodyHtml = true;
message.To.Add(new MailAddress("Fake#gmail.com"));
message.Body = "Please view attachments below.";
sClient.Send(message);
message.Dispose();
}
catch
{
Console.WriteLine("error");
}
}
}
I have also tried to use a stream writer with no success. I have also tried various ways to do a file.CopyToAsync(memoryStreamname). I am not sure what else I am missing or doing wrong here.
Thank you in advance.

Related

Download the file as a zip in ASP.NET Core

I am designing an educational site. When the user downloads a training course, I want this download (training course) to be done in the form of compression (zipper), please give a solution
My code:
public Tuple<byte[],string,string> DownloadFile(long episodeId)
{
var episode=_context.CourseEpisodes.Find(episodeId);
string filepath = Path.Combine(Directory.GetCurrentDirectory(),
"wwwroot/courseFiles",
episode.FileName);
string fileName = episode.FileName;
if(episode.IsFree)
{
byte[] file = System.IO.File.ReadAllBytes(filepath);
return Tuple.Create(file, "application/force-download",fileName);
}
if(_httpContextAccessor.HttpContext.User.Identity.IsAuthenticated)
{
if(IsuserIncorse(_httpContextAccessor.HttpContext.User.Identity.Name,
episode.CourseId))
{
byte[] file = System.IO.File.ReadAllBytes(filepath);
return Tuple.Create(file, "application/force-download", fileName);
}
}
return null;
}
I write a demo to show how to download zip file from .net core:
First , Add NuGet package SharpZipLib , create an Image Folder in wwwroot and put some picture in it.
controller
public class HomeController : Controller
{
private IHostingEnvironment _IHosting;
public HomeController(IHostingEnvironment IHosting)
{
_IHosting = IHosting;
}
public IActionResult Index()
{
return View();
}
public FileResult DownLoadZip()
{
var webRoot = _IHosting.WebRootPath;
var fileName = "MyZip.zip";
var tempOutput = webRoot + "/Images/" + fileName;
using (ZipOutputStream IzipOutputStream = new ZipOutputStream(System.IO.File.Create(tempOutput)))
{
IzipOutputStream.SetLevel(9);
byte[] buffer = new byte[4096];
var imageList = new List<string>();
imageList.Add(webRoot + "/Images/1202.png");
imageList.Add(webRoot + "/Images/1data.png");
imageList.Add(webRoot + "/Images/aaa.png");
for (int i = 0; i < imageList.Count; i++)
{
ZipEntry entry = new ZipEntry(Path.GetFileName(imageList[i]));
entry.DateTime= DateTime.Now;
entry.IsUnicodeText = true;
IzipOutputStream.PutNextEntry(entry);
using (FileStream oFileStream = System.IO.File.OpenRead(imageList[i]))
{
int sourceBytes;
do
{
sourceBytes = oFileStream.Read(buffer, 0, buffer.Length);
IzipOutputStream.Write(buffer, 0, sourceBytes);
}while (sourceBytes > 0);
}
}
IzipOutputStream.Finish();
IzipOutputStream.Flush();
IzipOutputStream.Close();
}
byte[] finalResult = System.IO.File.ReadAllBytes(tempOutput);
if (System.IO.File.Exists(tempOutput)) {
System.IO.File.Delete(tempOutput);
}
if (finalResult == null || !finalResult.Any()) {
throw new Exception(String.Format("Nothing found"));
}
return File(finalResult, "application/zip", fileName);
}
}
when I click the downloadZip ,it will download a .zip file
The simple example that follows illustrates the use of the static ZipFile.CreateFromDirectory method which, despite the fact that it is in the System.IO.Compression namespace , actually resides in the System.IO.Compression.FileSystem assembly, so you need to add a reference to that in your controller.
[HttpPost]
public FileResult Download()
{
List<string> files = new List<string> { "filepath1", "filepath2" };
var archive = Server.MapPath("~/archive.zip");
var temp = Server.MapPath("~/temp");
// clear any existing archive
if (System.IO.File.Exists(archive))
{
System.IO.File.Delete(archive);
}
// empty the temp folder
Directory.EnumerateFiles(temp).ToList().ForEach(f => System.IO.File.Delete(f));
// copy the selected files to the temp folder
files.ForEach(f => System.IO.File.Copy(f, Path.Combine(temp, Path.GetFileName(f))));
// create a new archive
ZipFile.CreateFromDirectory(temp, archive);
return File(archive, "application/zip", "archive.zip");
}
Answer from Source - MikesDotNetting

How to download multiple files at once from S3 using C# AWS SDK

How to download multiple files from s3 buckets. I could not find any better option on SO.
Here is my code for single file download. Given list of Urls, I am looping to download multiple files.
public async Task Download(string url, Stream output)
{
var s3Uri = new AmazonS3Uri(url);
GetObjectRequest getObjectRequest = new GetObjectRequest
{
BucketName = s3Uri.Bucket,
Key = System.Net.WebUtility.UrlDecode(s3Uri.Key)
};
using (var s3Client = new AmazonS3Client(s3Uri.Region))
{
// dispose the underline stream when writing to stream is done
using (var getObjectResponse = await s3Client.GetObjectAsync(getObjectRequest).ConfigureAwait(false))
{
using (var responseStream = getObjectResponse.ResponseStream)
{
await responseStream.CopyToAsync(output);
}
}
}
output.Seek(0L, SeekOrigin.Begin);
}
Download files given s3 urls
var list = new List<Stream>();
foreach(var url in urls)
{
var stream = new MemoryStream();
await Download(url,ms);
list.Add(stream);
}
Is there any better option to download multiple files at once from S3?
I finally decided to implement my own version
public class StreamWrapper
{
public string Url { get; set; }
public Stream Content { get; set; }
public string FileName { get; set; }
}
public async Task Download(IList<StreamWrapper> inout, int maxConcurrentDownloads)
{
if (maxConcurrentDownloads <= 0)
{
maxConcurrentDownloads = 20;
}
if (!inout.HasAny())
return;
var tasks = new List<Task>();
for (int i = 0; i < inout.Count; i++)
{
StreamWrapper wrapper = inout[i];
AmazonS3Uri s3Uri = null;
if (AmazonS3Uri.TryParseAmazonS3Uri(wrapper.Url, out s3Uri))
{
tasks.Add(GetObject(s3Uri, wrapper.Content));
}
if (tasks.Count == maxConcurrentDownloads || i == inout.Count - 1)
{
await Task.WhenAll(tasks);
tasks.Clear();
}
}
}
private async Task GetObject(AmazonS3Uri s3Uri, Stream output)
{
GetObjectRequest getObjectRequest = new GetObjectRequest
{
BucketName = s3Uri.Bucket,
Key = System.Net.WebUtility.UrlDecode(s3Uri.Key)
};
using (var s3Client = new AmazonS3Client(s3Uri.Region))
{
// dispose the underline stream when writing to local file system is done
using (var getObjectResponse = await s3Client.GetObjectAsync(getObjectRequest).ConfigureAwait(false))
{
using (var responseStream = getObjectResponse.ResponseStream)
{
await responseStream.CopyToAsync(output);
}
}
}
output.Seek(0L, SeekOrigin.Begin);
}

blazor-webassembly upload file can't show progress?

i want realize a big file upload progress demo,but below code can't working normal.
if remove the code "await Task.Delay(1)" in ProgressableStreamContent class,and id "mybar" element not refresh UI.
if add "await Task.Delay(1)" ,it's work normal.can refresh UI,show progress. why?
Has anyone encountered this problem? Can you help me with this? Thank you.
<p>
<InputFile OnChange="#OnInputFileChange" />
</p>
<div>
<p>File Sizeļ¼š#totalSize #progressPercent % </p>
#{
var progressWidthStyle = progressPercent + "%";
}
<div class="progress">
<div id="mybar" class="progress-bar" role="progressbar" style="width:#progressWidthStyle" area-valuenow="#progressPercent" aria-minvalue="0" aria-maxvalue="100"></div>
</div>
</div>
private CancellationTokenSource cancelation;
public long totalSize = 0;
public int progressPercent = 0;
private string _fileName = "";
private async Task OnInputFileChange(InputFileChangeEventArgs e)
{
IBrowserFile imageFile = e.File;
totalSize = imageFile.Size;
var buffer = new byte[totalSize];
await imageFile.OpenReadStream(512000*1000).ReadAsync(buffer);
var content = new MultipartFormDataContent { { new ByteArrayContent(buffer), "\"upload\"", e.File.Name } };
var progressContent = new ProgressableStreamContent(content, 10240,
(sent, total) =>
{
progressPercent = (int)(sent * 100 / total);
Console.WriteLine("Uploading {0}%", progressPercent);
StateHasChanged();
});
var repsone = await client.PostAsync("http://localhost:5000/Home/Upload", progressContent);
var taskStr = await repsone.Content.ReadAsStringAsync();
Console.WriteLine("taskStr=" + taskStr);
}
public class ProgressableStreamContent : HttpContent
{
/// <summary>
/// Lets keep buffer of 20kb
/// </summary>
private HttpContent content;
private int bufferSize;
//private bool contentConsumed;
private Action<long, long> progress;
public ProgressableStreamContent(HttpContent content, int bufferSize, Action<long, long> progress)
{
if (content == null)
{
throw new ArgumentNullException("content");
}
if (bufferSize <= 0)
{
throw new ArgumentOutOfRangeException("bufferSize");
}
this.content = content;
this.bufferSize = bufferSize;
this.progress = progress;
foreach (var h in content.Headers)
{
this.Headers.Add(h.Key, h.Value);
}
}
protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
var buffer = new Byte[this.bufferSize];
long size;
TryComputeLength(out size);
var uploaded = 0;
using (var sinput = await content.ReadAsStreamAsync())
{
while (true)
{
var length = sinput.Read(buffer, 0, buffer.Length);
if (length <= 0) break;
//downloader.Uploaded = uploaded += length;
uploaded += length;
progress?.Invoke(uploaded, size);
await Task.Delay(1);
//System.Diagnostics.Debug.WriteLine($"Bytes sent {uploaded} of {size}");
await stream.WriteAsync(buffer, 0, length);
}
}
stream.Flush();
}
protected override bool TryComputeLength(out long length)
{
length = content.Headers.ContentLength.GetValueOrDefault();
return true;
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
content.Dispose();
}
base.Dispose(disposing);
}
}
It's not possible using the Blazor's HttpClient, because it uses the browser's fetch-API behind the scene and this API doesn't support streaming at the moment.

Is there an optimization for writing images or PDFs faster to the database?

I'm facing an issue that my upload time for an image or PDF of 40+MB is more than 2.5 minutes (20+ seconds of which are just routing the request from the frontend to the backend but I'm worried more about the sql query slowlyness). I pasted some code snippets below. I also don't get an upload percentage indicator and the fetched bytes don't open as an image either in html using <img src="data:image/jpeg;base64,#Convert.ToBase64String(Model.Image)" /> or using Win10 Photos app.
I'm looking first of all for an optimization on how can I write an image or pdf file faster into the database?
Index.cshtml
using (Html.BeginForm("Index", "File", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
#Html.TextBoxFor(m => m.File, new { #type = "file", #accept = "image/jpeg,image/gif,image/png,application/pdf" })
<input type="submit" value="Upload" />
#Html.ValidationSummary()
}
Controller
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Index(Model model)
{
var file = model.File;
if (file != null)
{
if (file.ContentLength == 0)
{
ModelState.AddModelError(String.Empty, "File cannot be empty.");
}
const int maxFileSizeMB = 50;
if (file.ContentLength > maxFileSizeMB * 1024 * 1024)
{
ModelState.AddModelError(String.Empty, $"File cannot be bigger than {maxFileSizeMB} megabytes.");
}
//Check for content-type, file extension, and first bytes if it's indeed a valid image.
if (!file.IsImageOrPdfFile())
{
ModelState.AddModelError(String.Empty, "File is not a valid image.");
}
if (ModelState.IsValid)
{
var fileName = file.FileName;
var fileBytes = new byte[file.ContentLength];
file.InputStream.Read(fileBytes, 0, file.ContentLength);
await _api.SaveFile(fileName, fileBytes);
//success!
return Redirect(Url.Action("Index"));
}
}
//View with errors
return View(model);
}
API method
public async Task SaveFile(string fileName, byte[] fileBytes)
{
var client = CreateRestClient();
client.Timeout = 300000;
var request = CreateJsonPostRequest("File", new SaveFileRequest
{
FileName = fileName,
FileBytes = fileBytes
});
var response = await client.ExecuteAsync(request);
if (response.StatusCode != HttpStatusCode.NoContent && response.StatusCode != HttpStatusCode.OK)
{
throw new Exception(response.Content);
}
}
Backend API
[RoutePrefix("api/File")]
public class FileController : ApiController
{
private readonly IFileActionsRepository _fileActions;
public FileController(IFileActionsRepository fileActions)
{
_fileActions = fileActions;
}
[Authorize]
[HttpPost, Route("")]
public async Task Post([FromBody] SaveFileRequest request)
{
if (request == null) throw new ArgumentNullException(nameof(request));
if (request.FileBytes.Length == 0) throw new ArgumentException("request.FileBytes");
_fileActions.SaveFile(request.FileName, request.FileBytes);
}
}
FileActionsRepository.cs
public class FileActionsRepository : IFileActionsRepository
{
private readonly IDataContext _dataContext;
public FileActionsRepository(IDataContext dataContext)
{
_dataContext = dataContext;
//Get the ObjectContext related to this DbContext
var objectContext = (_dataContext.Context as IObjectContextAdapter).ObjectContext;
//Sets the command timeout for all the commands (since it's too slow)
objectContext.CommandTimeout = 300;
}
public void SaveFile(string fileName, byte[] fileBytes)
{
//Using sql query (with update .write() block) since it's faster than entity framework linq-2-entities.
_dataContext.Context.Database.ExecuteSqlCommand(
"if exists (select FileName from Files with (updlock,serializable) where FileName = #FileName)"
+ " update Files set FileBytes .write(#FileBytes, 0, null), SysModified = GETDATE()"
+ " where FileName = #FileName"
+ " else"
+ " insert into Files (FileName, FileBytes, SysCreated)"
+ " values (#FileName, CONVERT(varbinary, '0x00'), GETDATE())"
+ " update Files set FileBytes .write(#FileBytes, 0, null)"
+ " where FileName = #FileName",
new SqlParameter("#FileName", fileName),
new SqlParameter("#FileBytes", fileBytes));
}
}

How to convert MailAttachment to MimeKit Attachment

We have to rework how we're sending emails since we are using Amazon SES. In the past, we were using smtp but can't do that in this case. The class that needs updated has taken in a MailMessage object and used smtp to send it. So I'm trying to rework the method to be able to continue to accept the MailMessage object and convert it to a MimeKit MimeMessage. For the most part it's working fine except when it comes to attachments. In the code I have, the attachment gets added and sent, however, when trying to open it appears it's corrupted or something. In my test case I attached a csv file. I could not open it in excel after receiving the email.
public class EmailAbstraction
{
public virtual void Send(MailMessage mailMessage)
{
sendMessage(mailMessage);
}
private static void sendMessage(MailMessage mailMessage)
{
using (var client = new AmazonSimpleEmailServiceClient(AwsConstants.SESAWSKey, AwsConstants.SESAWSSecret, AwsConstants.RegionEndpoint))
{
foreach (var to in mailMessage.To)
{
using (var messageStream = new MemoryStream())
{
var newMessage = new MimeMessage();
var builder = new BodyBuilder
{
HtmlBody = mailMessage.Body
};
newMessage.From.Add(mailMessage.From == null
? new MailboxAddress(EmailConstants.DefaultFromEmailDisplayName, EmailConstants.DefaultFromEmailAddress)
: new MailboxAddress(mailMessage.From.Address));
newMessage.To.Add(new MailboxAddress(to.DisplayName, to.Address));
newMessage.Subject = mailMessage.Subject;
foreach (var attachment in mailMessage.Attachments)
{
builder.Attachments.Add(attachment.Name, attachment.ContentStream);
}
newMessage.Body = builder.ToMessageBody();
newMessage.WriteTo(messageStream);
var request = new SendRawEmailRequest
{
RawMessage = new RawMessage { Data = messageStream }
};
client.SendRawEmail(request);
}
}
}
}
}
And in my test app, I have this.
internal class Program
{
private static void Main(string[] args)
{
var s = GetFileStream();
var m = new MailMessage();
var sender = new MailAddress("info#ourwebsite.com", "info");
m.From = sender;
m.Sender = sender;
m.Body = "test email";
m.Subject = "test subject";
m.To.Add(myemail);
m.Attachments.Add(new Attachment(s, "test-file.csv"));
new EmailAbstraction().Send(m);
}
private static MemoryStream GetFileStream()
{
var stream = new MemoryStream();
var fileStream = File.Open(#"C:\Users\dev\Desktop\test-file.csv", FileMode.Open);
fileStream.CopyTo(stream);
fileStream.Close();
return stream;
}
}
This is just copied from the MimeKit source code:
static MimePart GetMimePart (System.Net.Mail.AttachmentBase item)
{
var mimeType = item.ContentType.ToString ();
var contentType = ContentType.Parse (mimeType);
var attachment = item as System.Net.Mail.Attachment;
MimePart part;
if (contentType.MediaType.Equals ("text", StringComparison.OrdinalIgnoreCase))
part = new TextPart (contentType);
else
part = new MimePart (contentType);
if (attachment != null) {
var disposition = attachment.ContentDisposition.ToString ();
part.ContentDisposition = ContentDisposition.Parse (disposition);
}
switch (item.TransferEncoding) {
case System.Net.Mime.TransferEncoding.QuotedPrintable:
part.ContentTransferEncoding = ContentEncoding.QuotedPrintable;
break;
case System.Net.Mime.TransferEncoding.Base64:
part.ContentTransferEncoding = ContentEncoding.Base64;
break;
case System.Net.Mime.TransferEncoding.SevenBit:
part.ContentTransferEncoding = ContentEncoding.SevenBit;
break;
//case System.Net.Mime.TransferEncoding.EightBit:
// part.ContentTransferEncoding = ContentEncoding.EightBit;
// break;
}
if (item.ContentId != null)
part.ContentId = item.ContentId;
var stream = new MemoryStream ();
item.ContentStream.CopyTo (stream);
stream.Position = 0;
part.Content = new MimeContent (stream);
return part;
}