Blazor WebAssembly PWA - IFormFile FromForm is always null - c#

I have setup a Blazor WebAssembly ASP.Net Core hosted PWA project and am using Azure Cognitive Services in it. Therefore I have in one of my client-views a form where the user can upload an image and this will be referred to Azure.
In the razor-view I have this:
#inject HttpClient client;
#inject IFileReaderService FileReader;
#inject NavigationManager navi;
<div class="text-center">
<input class="btn btn-secondary " name="file" #ref="InpReference" type="file" id="file-selector" placeholder="Brows" accept="image/*" capture="camera" #onclick="InputFile">
#if (_fileSelected != false)
{
<input class="btn btn-primary" type="button" role="button" id="startbutton" #onclick="Upload" value="upload" />
}
</div>
#code {
private async Task Upload()
{
// content base structure
MultipartFormDataContent content = new MultipartFormDataContent();
content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("form-data");
foreach (/*IFileReference*/var fileReference in fileReferences)
{
Console.WriteLine("test1");
// getting the file size
IFileInfo fileinfo = await fileReference.ReadFileInfoAsync();
Stream fileStream;
var fileReferencesArray = fileReferences.ToArray();
using (var fs = await fileReference.CreateMemoryStreamAsync((int)fileinfo.Size))
{
Console.WriteLine("test2");
fileStream=new MemoryStream(fs.ToArray());
}
Console.WriteLine("test4" + fileinfo.Size);
StreamContent sc = new StreamContent(fileStream, (int)fileStream.Length);
content.Add(sc, "file", fileinfo.Name);
Console.WriteLine("test5");
}
Console.WriteLine("test6");
var response = await client.PostJsonAsync<List<Prediction>>("api/Azure/Prediction", content);
Console.WriteLine(response.Count + " : " + response.GetType().ToString());
foreach (var prediction in response)
{
Console.WriteLine(prediction.Id + ":" + prediction.Name + "," + prediction.Probability.ToString());
}
navi.NavigateTo("detailView/");
}
}
My WebApi Controller for the handling:
...
[HttpPost]
public List<Prediction> getPrediction([FromForm]IFormFile file)
{
if (file == null)
{
return new List<Prediction>();
}
List<Prediction> predicitions = azure_Client.GetPrediction(file.OpenReadStream());
return predicitions;
}
...
The problem is that the [FromForm]IFormFile file in the controller is always null. This is only null in the PWA project. I have set the same project up without PWA and it works, it is not null and it is getting the selected image from the view! What is the difference there and why isn't the HttpClient doing the same as in the Blazor WebAssembly ASP.Net Core hosted?

According to my test, if you want to upload file in Blazor WebAssembly ASP.Net Core hosted PWA, please refer to the following steps
Client(I use the sdk Tewr.Blazor.FileReader)
a. update Program.cs
builder.Services.AddFileReaderService(options => {
options.UseWasmSharedBuffer = true;
});
builder.Services.AddTransient(sp =>
new HttpClient
{
BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)
});
b. upload file razor-view
#using System.IO
#using Blazor.FileReader
#inject HttpClient client;
#inject IFileReaderService fileReader
<h1>File uplaod Blzaor WebAssembly!</h1>
<div class="row">
<div class="col-4">
<div class="form-group">
<input type="file" name="image" #ref="inputReference" #onchange="async() =>await OpenFile()" />
<ul>
<li>File Name: #fileName</li>
<li>Size: #size</li>
<li>Type: #type</li>
</ul>
</div>
<button class="btn btn-block btn-success" #onclick="async() =>await UploadFile()"> Upload File</button>
#if (!string.IsNullOrWhiteSpace(message))
{
<div class="alert alert-success">
File has been uplaoded
</div>
}
</div>
</div>
#code{
ElementReference inputReference;
string message = string.Empty;
string fileName = string.Empty;
string type = string.Empty;
string size = string.Empty;
Stream fileStream=null;
async Task UploadFile()
{
var content = new MultipartFormDataContent();
content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("form-data");
var sc = new StreamContent(fileStream, (int)fileStream.Length);
content.Add(sc, "image", fileName);
var response = await client.PostAsync("/upload", content);
if (response.IsSuccessStatusCode) {
message = "OK";
}
}
async Task OpenFile()
{
var file = (await fileReader.CreateReference(inputReference).EnumerateFilesAsync()).FirstOrDefault();
if (file == null) {
return;
}
var fileInfo=await file.ReadFileInfoAsync();
fileName = fileInfo.Name;
type = fileInfo.Type;
size = $"{fileInfo.Size} Bytes";
using (var ms = await file.CreateMemoryStreamAsync((int)fileInfo.Size)) {
fileStream = new MemoryStream(ms.ToArray());
}
}
}
Server
API COntroller
[HttpPost]
public async Task<IActionResult> Post([FromForm(Name ="image")]IFormFile file) {
if (file == null || file.Length == 0) {
return BadRequest("do not receive file");
}
var fileName = file.FileName;
var extension = Path.GetExtension(fileName);
var newFileName = $"{Guid.NewGuid()}{extension}";
var filePath = Path.Combine(_env.ContentRootPath, "Images", newFileName);
if (!Directory.Exists(Path.Combine(_env.ContentRootPath, "Images"))) {
Directory.CreateDirectory(Path.Combine(_env.ContentRootPath, "Images"));
}
using (var stream = new FileStream(filePath, FileMode.Create, FileAccess.Write)) {
await file.CopyToAsync(stream);
}
return Ok(filePath);
}
Result

Related

file control doesn't initialized in edit page - razor pages

I use asp.net core razor pages to create my application, in my create page, I have two file controls, one for upload icon image and the other for uploading detail images. But when I click edit button, all of the fields were initialized, except the two file controls. Please check my code. Anyone can help?
In my razor page:
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label asp-for="Product.Icon" class="control-label"></label>
<input asp-for="#Model.Icon" type="file" />
<span asp-validation-for="Product.Icon" class="text-danger"></span>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label class="control-label”>Detail Images(support multi-uploading):</label>
<input type="file" id="fUpload" name="files" multiple />
</div>
</div>
</div>
In my page model:
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Product = await _context.Products
.Include(p => p.Shop).SingleOrDefaultAsync(m => m.ID == id);
if (Product == null)
{
return NotFound();
}
ViewData["Shop"] = new SelectList(_context.Shops, "ID", "Name");
return Page();
}
public async Task<IActionResult> OnPostAsync(List<IFormFile> files)
{
if (!ModelState.IsValid)
{
return Page();
}
var uploads = Path.Combine(hostingEnvironment.WebRootPath, "uploads");
if (!Directory.Exists(uploads))
{
Directory.CreateDirectory(uploads);
}
if (this.Icon != null)
{
var fileName = GetUniqueName(this.Icon.FileName);
var filePath = Path.Combine(uploads, fileName);
this.Icon.CopyTo(new FileStream(filePath, FileMode.Create));
this.Product.Icon = fileName;
}
if (files != null && files.Count > 0)
{
foreach (IFormFile item in files)
{
if (item.Length > 0)
{
var fn = GetUniqueName(item.FileName);
var fp = Path.Combine(uploads, fn);
item.CopyTo(new FileStream(fp, FileMode.Create));
this.Product.ProductImages = this.Product.ProductImages + fn + "^";
}
}
}
_context.Attach(Product).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ProductExists(Product.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index");
}
The most important part of your page is missing from the code you provided - the <form> tag. For file uploading to work, you must specify that the method is post and you must also provide an enctype attribute with its value set to multipart/form-data:
<form method="post" enctype="multipart/form-data">
...
Ref: https://www.learnrazorpages.com/razor-pages/forms/file-upload

Could not locate Razor Host Factory type (ASP.NET WEB MVC )

I have been trying to solve this razor form error for a week now and I can't seem to fix it.
Packages:
Microsoft.AspNet.MVC (version 5.2.4)
M.AspNet.Razor (version 3.2.4)
M.AspNet.WebPages (version 3.2.4)
M.Web.Infrastructure (version 1.0.0.0)
Newtonesoft.Json (version 11.0.2)
System.Net.Http (version 4.3.3)
Packages Screenshot
HomeController.cs (Code)
public async Task<ActionResult> Index()
{
PrimeCommand PCommand = new PrimeCommand();
var responseString = await PCommand.Login("user", "pass", "api");
ViewBag.SessionId = responseString;
return View();
}
Login() function:
public async Task<string> Login(string username, string password, string apikey)
{
try
{
using (var httpClientHandler = new HttpClientHandler())
{
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; // PJDS code
using (var client = new HttpClient(httpClientHandler))
{
var newLogin = new LoginObject();
newLogin.ApplicationKey = apikey;
newLogin.UserName = username;
newLogin.Password = password;
var contentObject = JsonConvert.SerializeObject(newLogin);
var content = new StringContent(contentObject);
var response = await client.PostAsync(Api.base_url + "/Login", content);
var responseString = await response.Content.ReadAsStringAsync();
dynamic responseObject = JsonConvert.DeserializeObject(responseString);
var sessionID = responseObject.result.SessionID;
var searchObj = new SimpleSearchObject
{
SessionID = sessionID
};
return sessionID;
}
}
}
catch (Exception e)
{
Debug.WriteLine("error: login - " + e.Message);
return null;
}
}
Index.cshtml
<form style="height: auto;width:300px;" action="action_page.php">
<div class="container">
#using (Html.BeginForm())
{
<b>#Html.LabelFor(x => x.UserName)</b>
#Html.TextBoxFor(x => x.UserName)
<br />
<b>#Html.LabelFor(x => x.Password)</b>
#Html.TextBoxFor(x => x.Password)
<button type="submit" value="Index" name="action:Index">Login</button>
<label>
<input type="checkbox" checked="checked" name="remember"> Remember me
</label>
}
<span class="psw">Forgot password?</span>
</div>
The Error
System.InvalidOperationException
Could not locate Razor Host Factory type:
System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.2.0.0, Culture=neutral, PublicKeyToken=*******************
Description: HTTP 500.Error processing request.
Details: Non-web exception. Exception origin (name of application or object): System.Web.WebPages.Razor.
I will just answer my question, the problem was in my Web.config file instead of <....System.Web.Mvc, Version=5.2.0.0... my version was 5.2.3.0 , I just change the zero to three.

Expected end of MIME multipart stream. MIME multipart message is not complete

I have an Angular application, written in Typescript, with an ASP.Net Web Api backend. I am trying to use the ng-file-upload (see this link for details) directive to upload an image file.
I receive an exception in my Web API Post method:
"Unexpected end of MIME multipart stream. MIME multipart message is not complete."
I've done my research and found similar issues here - I have tried to implement Landuber Kassa's answer but without success.
Also this although my project is not MVC and in any case it did not work.
I am fresh out of ideas and would appreciate the community's thoughts. I am happy to consider any other alternatives if I can be pointed in the right direction.
Ash
My .Net Post method (implementing Landuber Kassa's idea):
[RoutePrefix("BeaufortAppStore/api/Image")]
public class ImageController : ApiController
{
#region Methods
#region Posts
[Route("UploadImage")]
[HttpPost]
public async Task<IHttpActionResult> UploadImage()
{
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
var provider = new MultipartMemoryStreamProvider();
Stream reqStream = Request.Content.ReadAsStreamAsync().Result;
MemoryStream tempStream = new MemoryStream();
reqStream.CopyTo(tempStream);
tempStream.Seek(0, SeekOrigin.End);
StreamWriter writer = new StreamWriter(tempStream);
writer.WriteLine();
writer.Flush();
tempStream.Position = 0;
StreamContent streamContent = new StreamContent(tempStream);
foreach (var header in Request.Content.Headers)
{
streamContent.Headers.Add(header.Key, header.Value);
}
// Read the form data and return an async task.
await streamContent.ReadAsMultipartAsync(provider); // FAILS AT THIS POINT
foreach (var file in provider.Contents)
{
var filename = file.Headers.ContentDisposition.FileName.Trim('\"');
var buffer = await file.ReadAsByteArrayAsync();
//Do whatever you want with filename and its binary data.
}
return Ok();
}
#endregion
#endregion
My angular controller method:
public upload(): void {
//Create config used in ng-file-upload
var config: IFileUploadConfigFile = {
data: this.file, url: "BeaufortAppStore/api/Image/UploadImage/", method: "POST" };
this._dataService.uploadImage(config).then((result: any) => {
this.thumbnail = result.data;
});
}
My angular view (partial view for a directive):
<div class="form-group">
<label for="file" class="control-label col-xs-2">Choose a file</label>
<input id="file" type="file" name="file" class="form-control" ngf-select ngf-pattern="'image/*'"
ng-model="vm.file" />
<img style="width:100px;" ngf-thumbnail="thumbnail || '/thumb.jpg'" />
<button type="submit" ng-click="vm.upload()">Upload</button>
Try this in C#:
[HttpPost]
[Route("Profile/Image")]
public Task<HttpResponseMessage> UploadImgProfile()
{
try
{
if (!ModelState.IsValid) return null;
var currentUser = _userUtils.GetCurrentUser(User);
if (currentUser == null) return null;
HttpRequestMessage request = this.Request;
if (!request.Content.IsMimeMultipartContent())
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.UnsupportedMediaType));
string root = HttpContext.Current.Server.MapPath("~" + Constant.Application.User_Image_Directory);
bool exists = Directory.Exists(root);
if (!exists)
Directory.CreateDirectory(root);
var provider = new MultipartFormDataStreamProvider(root);
var task = request.Content.ReadAsMultipartAsync(provider).
ContinueWith<HttpResponseMessage>(o =>
{
var finfo = new FileInfo(provider.FileData.First().LocalFileName);
string guid = Guid.NewGuid().ToString();
var fileName = guid + "_" + currentUser.IdOwin + ".jpg";
File.Move(finfo.FullName, Path.Combine(root, fileName));
return new HttpResponseMessage()
{
Content = new StringContent(Path.Combine(Constant.Application.User_Image_Directory, fileName))
};
}
);
return task;
}
catch (Exception ex)
{
_logger.LogException(ex);
return null;
}
}
Angular Controller:
//Upload Func
$scope.upload = function (files) {
if (files && files.length) {
for (var i = 0; i < files.length; i++) {
var file = files[i];
$scope.uploading = true;
// $scope.imageName = file.name;
$upload.upload({
url: enviroment.apiUrl + '/api/CurrentUser/Profile/Image',
//fields: { 'username': $scope.username },
file: file
}).progress(function (evt) {
$scope.uploading = true;
var progressPercentage = parseInt(100.0 * evt.loaded / evt.total);
console.log('progress: ' + progressPercentage + '% ' + evt.config.file.name);
$scope.progress = progressPercentage;
}).success(function (data, status, headers, config) {
console.log('file ' + config.file.name + 'uploaded. Response: ' + data);
$scope.imageName = data;
$scope.uploading = false;
$scope.loadSuccess = true;
vm.uploadImage = false;
//AR
var reader = new FileReader();
reader.onload = function (evt) {
$scope.$apply(function ($scope) {
$scope.myImage = evt.currentTarget.result;
});
};
reader.readAsDataURL(files[0]);
//END AR
});
}
}
};
// Stay on Listen upload file
$scope.$watch('files', function (evt) {
$scope.upload($scope.files);
});
HTML:
<div class="row">
<!--UPLOAD-->
<div class="up-buttons">
<div class="clearfix visible-xs-block"></div>
<div class="col-md-12 col-lg-12 col-sm-12 col-xs-12 text-center box-upload-image" data-ng-show="profileCtrl.uploadImage">
<br />
<div id="imgDragDrop" ng-file-drop ng-model="files"
drag-over-class="dragover"
accept="image/*">
<div class="cropArea-bkg">
<h4>
<span class="mdi mdi-account mdi-48px"></span>
<br /><br />
Carica immagine profilo
</h4>
<p>Trascina qui la tua immagine, oppure</p>
<div ng-file-select="" ng-model="files" class="btn btn-secondary" ng-accept="'*.pdf,*.jpg,*.png'" tabindex="0">
Sfoglia sul tuo computer
</div><br>
</div>
</div>
<div ng-no-file-drop class="well bg-danger">File Drag/Drop non è supportato da questo browser</div>
<br />
<div class="text-center">
<div class="progress" ng-show="uploading">
<div class="progress-bar progress-bar-striped" role="progressbar" aria-valuenow="{{ ::progress }}" aria-valuemin="0" aria-valuemax="100" style="width: {{::progress}}% ">
<span class="sr-only">{{ ::progress }}% Complete</span>
</div>
</div>
</div>
</div>
<!--END UPLOAD-->
</div>
</div>

Uploading file ASP Core IIS

I am attempting to upload a file. The code below works on my local machine or running on a remote server running the dll from a command line, but when I try and publish to my test environment and run under iis it fails.
<form method="post" asp-action="Upload" asp-controller="Prebook" enctype="multipart/form-data">
<div class="form-inline">
<div class="col-md-2">
<div class="form-group">
<input type="file" name="files" data-input= "false" multiple class="filestyle" data-buttonName="btn-primary">
</div>
</div>
<div class="col-md-2">
<div class="form-group">
<input type="submit" value="Upload File" class="btn btn-primary" />
</div>
</div>
</div>
</form>
Controller logic
[HttpPost]
public async Task<IActionResult> Upload(ICollection<IFormFile> files)
{
if (await _contextPB.UploadRow.AnyAsync())
{
Danger(string.Format("Please LOAD the existing containers before uploading another file"), true);
return View();
}
int rowCount = 0;
var uploads = Path.Combine(_environment.WebRootPath, "uploads");
var _viewModel = new UploadViewModel();
foreach (var file in files)
{
using (var streamReader = System.IO.File.OpenText(Path.Combine(uploads, file.FileName)))
{
var line = streamReader.ReadLine();
var columnNames = line.Split(new[] { ',' });
if (!ValidateColumnNames(columnNames))
{
Danger(string.Format("Invalid Column Name in Upload file"), true);
return View(_viewModel);
}
while (!streamReader.EndOfStream)
{
var data = line.Split(new[] { ',' });
var uploadRow = new UploadRow();
// validation & assignment logic removed
try
{
_contextPB.Add(uploadRow);
rowCount++;
}
catch (Exception e)
{
Danger(string.Format("<b>{0},{1}</b> database error", uploadRow.Container_Id, e), true);
}
line = streamReader.ReadLine();
}
}
}
}
Try adding a catch block to see what the error is.
I'm assuming a permission issue.
[HttpPost]
public async Task<IActionResult> Upload(ICollection<IFormFile> files)
{
try
{
if (await _contextPB.UploadRow.AnyAsync())
{
Danger(string.Format("Please LOAD the existing containers before uploading another file"), true);
return View();
}
// your code
}
catch (Exception ex)
{
// what is the error?
// add a breakpoint to see
throw;
}
}

How to fix System.NullReferenceException' occurred in App_Web_xxxx.dll in asp.net mvc?

This is a part of my view code for Index action of Manage Controller.
<div class="mngimg">
#using (Html.BeginForm("UploadPhoto", "Manage", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<div class="btn btn-default browseimg">
<input type="file" name="file" id="files" onchange="this.form.submit()" />
</div>
<div class="btn btn-default browseimg">
#Html.ActionLink("Remove Photo", "RemovePhoto", "Manage")
</div>
}
</div>
</div>
}
</dd>
<dt>Password:</dt>
<dd>
[
#if (Model.HasPassword) <!-- Here is my error. The Model is null -->
{
#Html.ActionLink("Change your password", "ChangePassword")
}
else
{
#Html.ActionLink("Create", "SetPassword")
}
]
</dd>
Whenever I open this page and click "Remove Photo" I keep getting an error saying that An exception of type 'System.NullReferenceException' occurred in App_Web_ckoryptg.dll but was not handled in user code. I tried debugging, but I am unable to figure out why my Model.HasPassword is becoming null. Here is my RemovePhoto Action from Manage Controller.
[HttpPost]
public async Task<ActionResult> UploadPhoto(HttpPostedFileBase file)
{
if (file != null && file.ContentLength > 0)
{
var user = await GetCurrentUserAsync();
var userId = user.Id;
var fileExt = Path.GetExtension(file.FileName);
var fnm = userId + ".png";
if (fileExt.ToLower().EndsWith(".png") || fileExt.ToLower().EndsWith(".jpg") || fileExt.ToLower().EndsWith(".gif"))// Important for security if saving in webroot
{
var filePath = HostingEnvironment.MapPath("~/Content/Images/") + fnm;
var directory = new DirectoryInfo(HostingEnvironment.MapPath("~/Content/Images/"));
if (directory.Exists == false)
{
directory.Create();
}
ViewBag.FilePath = filePath.ToString();
file.SaveAs(filePath);
return RedirectToAction("Index", new { Message = ManageMessageId.PhotoUploadSuccess });
}
else
{
return RedirectToAction("Index", new { Message = ManageMessageId.FileExtensionError });
}
}
return RedirectToAction("Index", new { Message = ManageMessageId.Error });// PRG
}
private async Task<ApplicationUser> GetCurrentUserAsync()
{
return await UserManager.FindByIdAsync(User.Identity.GetUserId());
}
I opened a default MVC project that comes with visual studio and I added these extra things that I followed from this tutorial ASP.NET upload images. How do I resolve this?
Edit:
This is my RemovePhoto action.
public ActionResult RemovePhoto()
{
string file = "~/Content/Images/" + User.Identity.GetUserId() + ".png";
if(System.IO.File.Exists(Server.MapPath(file)))
System.IO.File.Delete(Server.MapPath(file));
return View("Index");
}
Just Redirect back to your Index action. That way you don't have to instantiate your Index model in your RemovePhoto action. Can read more about this pattern here.

Categories