Upload is stripping image metadata MVC5 - c#

I have a simple file upload that is for adding images to a record in our business app. The issue is that after the file is uploaded and I am in my C# controller I do not have the EXIF data for the image. I am not doing anything special just:
HTML
<form action="/Image/Upload" method="post" enctype="multipart/form-data">
<input type="file" class="form-control" style="width:80%" name="photo" accept="image/*" capture="camera" />
<input type="hidden" name="clientid" id="hvClientid" />
<input type="submit" class="btn btn-primary btn-xs" value="Upload"/>
</form>
Controller
[HttpPost]
public ActionResult Upload(HttpPostedFileBase photo, FormCollection items)
{
string clientId = items[0].ToString();
var tempimg = Image.FromStream(photo.InputStream);
if (ImageTools.DetermineIfImageSizeAboveMax(tempimg))
tempimg = ImageTools.Resize(tempimg, ImageTools.ImageSize.Large);
var exif = new EXIF();
var img = exif.FixOrientation(tempimg);
var azure = new Azure.Blob.Consumer.StorageConsumer(clientId, Azure.StorageBase.MyApp);
var tempUri = azure.CacheImage(img, photo.FileName);
return Json(new { path = tempUri });
}
The tempimg variable does not contain all the EXIF metadata that is present on the original file. The EXIF class is just a lib i wrote to do things like fix orientation etc.

Fetching Attributes from the azure storage before setting the properties might help you retain the exif data while uploading the image.
CloudBlockBlob blob = sampleContainer.GetBlockBlobReference(photo.FileName);
blob.FetchAttributes();
blob.Properties.ContentType = "image/jpg";
blob.SetProperties();
Also make sure that the image contain's necessary Exif data before its uploading.
(In my case I was sending the image through a chat app which actually removed the Exif data from the image.)
please refer this blog for more info

Related

embedding a pdf file on razor page

I am trying to embed a pdf file on a razor page. Below is the code in the controller:
[HttpPost]
public ActionResult ViewPDF()
{
string embed = "<object data=\"{0}\" type=\"application/pdf\" width=\"500px\" height=\"300px\">";
embed += "If you are unable to view file, you can download from here";
embed += " or download <a target = \"_blank\" href = \"http://get.adobe.com/reader/\">Adobe PDF Reader</a> to view the file.";
embed += "</object>";
TempData["Embed"] = `string.Format(embed,Url.Content("~/Documents/2022Packet.pdf"));`
return RedirectToAction("Index");
}
public IActionResult Index()
{
return View();
}
Documents is in a folder in my application where I have pdf file called 2022Packet.pdf. below is the screen shot:
This is what I have in my view:
#using (Html.BeginForm("ViewPDF", "PDF", FormMethod.Post))
{
View PDF
<hr />
#Html.Raw(TempData["Embed"])
}
when I run my code, i see this screen shot instead of my pdf file
Is Url.Content("~/Documents/2022Packet.pdf")); that I am using to display the pdf file wrong syntax to display the pdf file? Do I need to use absolutre URL or resolveURL to display the pdf file. This is the resulting HTML on browser:
<div class="container-bg">
<div class="container">
<main role="main" class="pb-3">
<form action="/PDF/ViewPDF" method="post"> View PDF
<hr />
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8CeXypSjYClBh-AvfVG-nwcQ_BEfVjwZg8BdU__NxjoQeke-Tc1mAO5wcURZ8FLNSZPXevyRML7o_nAHh2jwpec04_5ouAXFbTmUdt8YoxCSBy8WtvIMwF7badc7Yd4_blsULWgvxSRfS92lpJ4o7LE" /></form>
</main>
</div>
</div>
Any help will be greatly appreciated.
Is Url.Content("~/Documents/2022Packet.pdf")); that I am using to
display the pdf file wrong syntax to display the pdf file? Do I need
to use absolutre URL or resolveURL to display the pdf file. This is
the resulting HTML on browser:
Well, to begin with your first question, yes its incorrect. In fact, it would not display your file as expected because in your embed html string at data=\"{0}\" you ought to pass file steream instead of file path. Finally, in your string.Format you have to pass your file path from where it would be read. Thus, your pdf has not been displayed.
Solution:
public class PDFController : Controller
{
private readonly IWebHostEnvironment _environment;
public PDFController(IWebHostEnvironment environment)
{
_environment = environment;
}
public IActionResult Index()
{
string path = Path.Combine(_environment.ContentRootPath, "Documents");
// string path = Path.Combine(_environment.WebRootPath, "Documents");
using (MemoryStream stream = new MemoryStream())
{
System.IO.File.WriteAllBytes(path + "/YourFileName.pdf", stream.ToArray());
string embed = "<object data=\"{0}\" type=\"application/pdf\" width=\"500px\" height=\"300px\">";
embed += "If you are unable to view file, you can download from here";
embed += " or download <a target = \"_blank\" href = \"http://get.adobe.com/reader/\">Adobe PDF Reader</a> to view the file.";
embed += "</object>";
TempData["Embed"] = string.Format(embed, "/Documents/YourFileName.pdf");
return View();
}
}
}
Note: If you wanted to read file from ourside of wwwroot use _environment.ContentRootPath but from inside wwwroot use _environment.WebRootPath. As you can see in my example. Then, in string.formeter pass your file path like this string.Format(embed, "/Documents/YourFileName.pdf"); tild ~ is not required.
Output:
Browser Seettings:
If your browser restrict you, in that scenari, you should configure your browsere setting. For Eadge you can configure as following.
Update:
Razor View:
<div class="container-bg">
<div class="container">
<main role="main" class="pb-3">
<a target="_blank" class="btn btn-info" asp-controller="PDF" asp-action="ViewPDF">Download PDF</a>
</main>
</div>
</div>
#Html.Raw(TempData["Embed"])
Download PDF Controller:
While Download PDF would be clicked following controller would called.
public ActionResult ViewPDF()
{
string physicalPath = "wwwroot/Documents/YourPDFFileName.pdf";
byte[] pdfBytes = System.IO.File.ReadAllBytes(physicalPath);
MemoryStream stream = new MemoryStream(pdfBytes);
string mimeType = "application/pdf";
return new FileStreamResult(stream, mimeType)
{
FileDownloadName = "AnyNameYouWantToSet.pdf"
};
}

How to deal with input=file / IFormFile two-way binding in Razor Pages

I have an entity that has byte[] to store logos in the database as varbinary. But to use this model on a Razor Page, I have extended it and added a IFormFile property to receive the uploaded file.
public class Company
{
public string Name { get; set; }
public byte[] Logo { get; set; }
}
public class CompanyModel : Company
{
[DataType(DataType.Upload)]
[FromForm(Name = "UploadedLogo")]
public IFormFile UploadedLogo { get; set; }
}
And in a method I fetch this company from the database and set IFormFile accordingly:
var response = await _companyService.GetByIdAsync(id);
if (response != null)
{
if (response.Logo != null)
{
using (var stream = new MemoryStream(response.Logo))
{
var formFile = new FormFile(stream, 0, stream.Length, response.Name, response.Name);
formFile.Headers = new HeaderDictionary()
{
new KeyValuePair<string, StringValues>("Content-Disposition", $"form-data; name=\"Company.UploadedLogo\"; filename=\"{response.Name}.png\""),
new KeyValuePair<string, StringValues>("Content-Type", "image/png"),
};
response.UploadedLogo = formFile;
}
}
return response;
}
And the UploadedLogo is populated and I bind that on Razor Page
<form method="post"
enctype="multipart/form-data"
data-ajax="true"
data-ajax-method="post"
data-ajax-begin="begin"
data-ajax-complete="completed"
data-ajax-failure="failed">
...
<div class="form-group row">
<div class="col-sm-2 text-right">
<label asp-for="#Model.Company.Logo" class="col-form-label"></label>
</div>
<div class="col-sm-9">
<input type="file" class="dropify" data-height="200"
asp-for="#Model.Company.UploadedLogo"
data-max-file-size="100K" data-allowed-file-extensions="png jpg jpeg" />
</div>
</div>
...
<div class="form-group modal-actions">
<input type="submit" class="btn btn-primary btn-icon-text btn-md btn-save-editing" value="Save" />
</div>
</form>
By the way, I am using Dropify as file upload plugin and jquery-ajax-unobtrusive library to handle post requests. Here is the post method:
public async Task<CompanyModel> OnPostAsync(CompanyModel company)
{
CompanyModel result = new CompanyModel();
try
{
if (company.UploadedLogo != null)
company.Logo = await company.UploadedLogo.GetBytes();
var response = await _companyService.SaveAsync(company);
if (response != null)
result = response;
}
catch (Exception ex)
{
_Logger.LogException(ex);
}
return result;
}
Now here is the scenario:
When I am adding a new company, I enter company name and browse a file from my computer, and save the data. I can see Uploaded logo in company model received in post request, which is then converted to byte[] and saved in database. Everything is fine. Below is the fiddler capture:
Fiddler capture for INSERT
Problem starts when I try to edit the company. I open the company, service fetches the data, convert byte[] to IFormFile and the data (name + logo) is shown on the form. I just edit the name, do not touch the logo and let it be as it is and hit save. At this point, the Uploaded logo is null in company model received in post request. Below is the fiddler capture:
Fiddler capture for UPDATE
I can see the difference in the posted requests captures clearly. The file is not there in the case of edit. But I don't know how to fix this. It has been a day I am hurting my brain on this, can anyone assist me on this please?
UPDATE: Added fiddler captures as well.
I know this is not the best solution, but I managed to work it.
What I did is, I converted the image bytes into base64 data-uri of the image and save the uri in an input[hidden]. I also added another input[hidden] to save the flag that whether the user has changed the logo or not. And when the user changes the logo, I update these hidden fields with the new base64 data-uri of uploaded image and flag=true. And if the user does not change the image, the uri is the same and flag=false. Now on save, I get the uri string and the flag. If the flag is true, I convert the uri into the image and persist it in the database.

IFormFile IEnumerable is of size 1 even though multiple file upload is specified

Model
public List<ZertifikatFiles> Files { get; set; }
[NotMapped]
public IEnumerable<IFormFile> Certificates { get; set; }
View
<form asp-action="AddCertificate" method="post" enctype="multipart/form-data" data-file-dragndrop>
<div class="row">
<div class="col-md-3"></div>
<div class="form-group col-md-9">
<input type="file" asp-for="IFormFiles" multiple />
<span asp-validation-for="IFormFiles" class="text-danger"></span>
</div>
</div> </form>
Controller
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AddCertificate(Certificates certificates )
{
if (ModelState.IsValid)
{
if (certificates.IFormFiles != null && !certificates.IFormFiles.IsEmpty())
{
certificates.Files = new List<CertificateFiles>();
foreach (IFormFile formFile in certificates.IFormFiles)
{
byte[] bytes = new byte[formFile.Length];
using (var reader = formFile.OpenReadStream())
{
await reader.ReadAsync(bytes, 0, (int)formFile.Length);
}......
Whenever I try to upload more than one file, the IEnumerable only takes the first file and leaves the rest behind.
Translated: Choose Files, 3 Files
Even though I specified the multiple file upload in the input field, the certificates.IFormFiles delivers me a size of 1.
What am I doing wrong?
If you used .Net core 2.0 or 2.1, try to update your SDK to 2.2.203 then it will work without problem.
the issue not in your code, it was a bug in .NET core
I invite you to read more about this bug here : https://github.com/aspnet/Mvc/issues/8527
After a few discussions with the team, I found out that the custom property attribute data-file-dragndrop only allows 1 file to be sent via AJAX. You were still able to upload more than one file but the AJAX-Request only accepted one file. In the case where you uploaded more than one file, the AJAX-Request took the first file and left the rest behind.
We all did not know this until the one who created this attribute explained it to us. I'm sorry for the inconvenience!

Upload a picture using ASP.NET MVC4 RAZOR

I am using kendo mobile to build a mobile application in which the user will be able to click and upload a photo. When they first enter the page, it will show their current photo, I want to be able to click and open file explorer on their device and have the ability to show a preview of their photo in place of the old one. Then when the click done it will send it to my MVC controller where I can then send it to where I want. I cant figure out how to send my file to the controller.
HTML
<div id="NewAccountUploadContainer">
<img id="NewAccountUpload" src="~/Images/btnCamera.png" data-bind="click: uploadPhoto" />
#using (Html.BeginForm("SendNewPhoto", "MobilePlatform", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<input id="ImageUploadBtn" style="display: none" type="file" accept="image/*" />
<input type="submit" value="OK" style="display: none" />
}
<div id="ImgUploadTxt" data-bind="click: uploadPhoto">
Upload a<br />
different photo.
</div>
The #ImageUploadBtn will be triggered by the #NewAccountUpload or #ImgUploadTxt clicks in jquery which works, but I cant get it to display a file or send to my controller when I trigger the submit.
C# Controller
[HttpPost]
public ActionResult SendNewPhoto(HttpPostedFileBase file)
{
// Verify that the user selected a file
if (file != null && file.ContentLength > 0)
{
// extract only the fielname
var fileName = Path.GetFileName(file.FileName);
// store the file inside ~/App_Data/uploads folder
var path = Path.Combine(Server.MapPath("~/App_Data/uploads"), fileName);
file.SaveAs(path);
}
// redirect back to the index action to show the form once again
return RedirectToAction("Index");
}
The file is always null at this point.
I'm using the Kendo for mvc4, and a mobile implementation, and I'm using the follow code, works for me:
View:
#(Html.Kendo().Upload()
.Name("files")
)
Controller
public ActionResult Submit(IEnumerable<HttpPostedFileBase> files)
{
if (files != null)
{
TempData["UploadedFiles"] = GetFileInfo(files);
}
return RedirectToAction("Result");
}
public ActionResult Result()
{
return View();
}
private IEnumerable<string> GetFileInfo(IEnumerable<HttpPostedFileBase> files)
{
return
from a in files
where a != null
select string.Format("{0} ({1} bytes)", Path.GetFileName(a.FileName), a.ContentLength);
}

set src property in view to a url outside of the MVC3 project

I am trying to create an application that will display images that are stored locally on the webserver. Here is what I have in my view, note that "entry" are absolute addresses like "C:\Images\Image1.jpg". However, when I run it, I get "Not allowed to load local resource: file:///C:/Images/ImageName.jpg" in the console log. So maybe it tries to access the image on the client. How do I tell my view to access the local webserver path and not look for the image source on the client? Please note that moving the images into project directory is not an option, because the images are stored on a different drive on the webserver.
<!-- language: c# -->
#model List<String>
<div style="height: 500px; overflow:scroll;">
<h2>
ScreenShots for testMachine</h2>
#foreach (var entry in Model)
{
<div class="nailthumb-container square-thumb">
<img alt="screenshot" src="#Url.Content(entry)" />
</div>
}
</div>
You cannot directly serve images outside of your ASP.NET MVC 3 application to the client. That would be a huge security vulnerability if the client could access arbitrary files on your server.
You will need to write a controller action that will return them and then point your src property of your <img> tags to this controller action.
public class ImagesController: Controller
{
public ActionResult SomeImage()
{
return File(#"C:\Images\foo.jpg", "image/jpeg");
}
}
and inside your view:
<img src="#Url.Action("SomeImage", "Images")" alt="" />
You could also pass the image name as parameter to the controller action:
public class ImagesController: Controller
{
public ActionResult SomeImage(string imageName)
{
var root = #"C:\Images\";
var path = Path.Combine(root, imageName);
path = Path.GetFullPath(path);
if (!path.StartsWith(root))
{
// Ensure that we are serving file only inside the root folder
// and block requests outside like "../web.config"
throw new HttpException(403, "Forbidden");
}
return File(path, "image/jpeg");
}
}
and in your view:
<img src="#Url.Action("SomeImage", "Images", new { image = "foo.jpg" })" alt="" />
The above code was useful for me, with a change like this
System.Web.UI.Page page = new System.Web.UI.Page();
string filePath = page.Server.MapPath("~/Log/" + fileName);
if (!filePath.StartsWith(filePath))
{
throw new HttpException(403, "Forbidden");
}
return File(filePath, "Content-Disposition", "attachment;filename=TableImportLog.csv");
}
the file thrown to the user is with file name like this "attachment;filename=TableImportLog.csv", but i want the file name as "TableErrorLog.csv"
need help for the same!

Categories

Resources