IList<HttpPostedFileBase> is null after post when using FIleUpload handler - c#

I am attempting to pass a IList of HttpPostedFileBase objects and a "Post" object to an Action. I receive the Post object just fine but my IList is always empty.
See code below...
Controller Action:
[HttpPost]
public ActionResult Create(Post post, IList<HttpPostedFileBase> attachments)
{
if (ModelState.IsValid)
{
var attachmentCounter = attachments.Count;
post.SubmissionDateTime = DateTime.Now;
db.Posts.AddObject(post);
db.SaveChanges();
return RedirectToAction("Index", new { category = post.Category });
}
return View(post);
}
View:
#using (Html.BeginForm("Create", "Posts", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Post</legend>
#Html.HiddenFor(model => model.Category)
#Html.HiddenFor(model => model.Author)
<div class="editor-label">
<label>Photo Attachments:
<span style="color:#666; font-size:12px; font-weight:normal;">(Optional)</span>
</label>
</div>
<div class="editor-field">
#Html.Raw(FileUpload.GetHtml(
name: "attachments",
initialNumberOfFiles: 1,
allowMoreFilesToBeAdded: true,
includeFormTag: false,
addText: "Add another photo...",
uploadText: "").ToString().Replace("<input value\"\" type=\"submit\" />", ""))
</div>
<br />
<p>
<input type="submit" value="Create" class="createButton" style="font-weight:normal;" /> |
#Html.ActionLink("Back to List", "Index", null, new { category = Model.Category }, null)
</p>
</fieldset>
}

I was trying to reproduce your issue, but in my environment all is working. But anyway check once again that your name property in FileUpload exactly the same as second parameter property in controller (even better copypaste it). The Second thing you need to check is the size of file you uploading to the server. If it's bigger then request size allowed in your web config, values will be null.
Also you could check in your method value of Request.Files if it's empty that will means that your file do not even uploading to server. If file will be available, you could get it from there.

Related

Display Error message in Validationsummary with Ajax

Goal:
If you retrieve a error based on the input, it should be displayed in ValidationSummary in relation to ajax without the webpage being refreshed.
Problem:
I have tried to make it but it doesn't work so well.
What part am I missing?
Thank you!
Info:
*I have found some webpages but they do not fit exactly to my purpose.
*I'm using ASP.net mvc
#model WebApplication1.Controllers.PersonnelModel
#{
ViewBag.Title = "Ajax";
}
<h2>Ajax</h2>
<h2>AjaxViewModel</h2>
#using (Html.BeginForm("HtmlViewModel", "Home", null))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>PersonnelModel</legend>
<div class="editor-label">
#Html.LabelFor(model => model.UserName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.UserName)
#Html.ValidationMessageFor(model => model.UserName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.MailAdress)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.MailAdress)
#Html.ValidationMessageFor(model => model.MailAdress)
</div>
</fieldset>
<p>
<input type="submit" value="Html Form Action" />
</p>
}
<br/>
<br />
<h2>AjaxViewModel</h2>
#using (Ajax.BeginForm("AjaxViewModel", "Home", new AjaxOptions { UpdateTargetId = "result" }))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>PersonnelModel</legend>
<div id="result"></div>
<div class="editor-label">
#Html.LabelFor(model => model.UserName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.UserName)
#Html.ValidationMessageFor(model => model.UserName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.MailAdress)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.MailAdress)
#Html.ValidationMessageFor(model => model.MailAdress)
</div>
</fieldset>
<p>
<input type="submit" value="Ajax Form Action" />
</p>
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery-ajax-unobtrusive#3.2.4/jquery.unobtrusive-ajax.min.js"></script>
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
namespace WebApplication1.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult HtmlViewModel(PersonnelModel Pmodel)
{
return Content("Hi " + Pmodel.UserName + ", Thanks for the details, a mail will be sent to " + Pmodel.MailAdress + " with all the login details.", "text/html");
}
[HttpPost]
public ActionResult AjaxViewModel(PersonnelModel Pmodel)
{
/*
ModelState.AddModelError("", "login is fail");
return View("Index", Pmodel);
*/
return Content("Hi " + Pmodel.UserName + ", Thanks for the details, a mail will be sent to " + Pmodel.MailAdress + " with all the login details.", "text/html");
}
}
public class PersonnelModel
{
[Required(ErrorMessage = "UserName Required.")]
public string UserName { get; set; }
[Required(ErrorMessage = "Email id Required.")]
public string MailAdress { get; set; }
}
}
EDIT - 03/11/2017 : There is an easy way to do this
Create a partial view for the form, Let's call it Form.cshtml and move the markup needed for the form to that. For your ajax form, set the data-ajax-mode attribute value to replace and data-ajax-update value to the id of the same form.
If you are using Ajax.BeginForm helper method, this is how you will do
#model PersonnelModel
#using (Ajax.BeginForm("Create", "Home",
new AjaxOptions { UpdateTargetId = "myform",
InsertionMode = InsertionMode.Replace },new { id="myform"}))
{
#Html.ValidationSummary("", true)
#Html.TextBoxFor(f => f.UserName)
#Html.ValidationMessageFor(model => model.UserName)
#Html.TextBoxFor(f => f.MailAdress)
#Html.ValidationMessageFor(model => model.MailAdress)
<input type="Submit" id="submit" value="Submit" class="btn btn-default" />
}
Now in your main view, simple call this partial view
#model PersonnelModel
<h2>My form below</h2>
#Html.Partial("Form",Model)
Now in the action method, when model state validation fails, return the partial view.
public ActionResult Create(PersonnelModel model)
{
if (ModelState.IsValid)
{
// to do : Save
}
if (Request.IsAjaxRequest())
{
return PartialView("Form",model);
}
return View(model);
}
Now when you submit the form and model state validation fails, the action method code will return the partial view result with the validation error messages (generated by the validation helpers) and the jquery.unobtrusive-ajax.js library code will replace (because we specified that with data-ajax-mode="replace") the content of the result of the jquery selector #data-ajax-update (the form tag and it's inner contents) with the response coming back from the server.
This should do it. Less client side code, compared to my old approach (below)
The Html.ValidationSummary method will be executed when the razor view gets executed. If you are doing a normal form post (non ajax), your action method usually returns to the same view when Model validation fails (assuming you write code like that) and the razor view code gets executed and the ValidationSummary method will read the validation errors from the model state dictionary and render the error messages.
When you use Ajax.BeginForm helper method the helper will generate some extra data attributes on the form and as long as you have included the jquery.unobtrusive-ajax.min.js script file, the form submit will be hijacked and it will do an ajax form submit instead.
When you do the ajax form submit, if you want to render model validation messages, you need to explicitly read the model validation errors and return that as a JSON response which your client side code can read and display in the UI.
[HttpPost]
public ActionResult Index(PersonnelModel model)
{
if (ModelState.IsValid)
{
return Json(new {status = "success", message= "Thanks for the details"});
}
else
{
var errors = new List<string>();
foreach (var modelStateVal in ViewData.ModelState.Values)
{
errors.AddRange(modelStateVal.Errors.Select(error => error.ErrorMessage));
}
return Json(new {status = "validationerror", errors = errors});
}
}
Now in your view, make sure you have a success handler for your ajax begin form to handle the response json
#using (Ajax.BeginForm("Index", "Home", new AjaxOptions { OnSuccess = "OnSuccess", }))
{
#Html.ValidationSummary("", true)
#Html.TextBoxFor(model => model.MailAdress)
<!--Your other form input elements-->
<input type="submit" value="Html Form Action" />
}
Note that i used #Html.ValidationSummary method with 2 overloads and passing an empty string as the first param. This will always render a ul element within the div which has class validation-summary-valid.
Now create your OnSuccess function and check the response and see whether response has a status property and the value of it is validationerror. If yes, loop through the errors collection and add a new li element with the error.
function OnSuccess(response) {
var $summaryUl = $(".validation-summary-valid").find("ul");
$summaryUl.empty();
if (response.status === 'validationerror') {
$.each(response.errors,
function(a, b) {
$summaryUl.append($("<li>").text(b));
});
} else {
alert(response.message);
}
}

Entity Framework "Edit" ActionResult which keeps or uploads new html file input originaly posted and saved through the "Create" ActionResult

I have a MVC 5 controller with views, using Entity Framework project.
I have a controller and view for my Item Model:
public partial class Item
{
public int ItemID { get; set; }
public string ImageURL { get; set; }
}
I changed the Entity Framework Create view and controller to post an html file input and save the file path as the ImageURL
A snippet from the view looks like this:
#using (Html.BeginForm(null, null, FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<div class="form-group">
<fieldset>
<legend>Upload a file</legend>
<div class="editor-field">
#Html.TextBox("file", "", new { type = "file" })
</div>
</fieldset>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
}
The controller's Create ActionResult looks like this:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ItemID,ImageURL")] Item item, HttpPostedFileBase file)
{
try
{
if (ModelState.IsValid && file.ContentLength > 0)
{
var fileName = Path.GetFileName(file.FileName);
var path = Path.Combine(Server.MapPath("~/Uploads"), fileName);
file.SaveAs(path);
item.ImageURL = "/Uploads/" + file.FileName;
db.Items.Add(item);
db.SaveChanges();
}
ViewBag.Message = "Upload successful";
return RedirectToAction("Index");
}
catch
{
ViewBag.Message = "Upload failed";
ViewBag.ItemTypeID = new SelectList(db.ItemTypes, "ItemTypeID", "ItemType1", item.ItemTypeID);
return View(item);
}
}
As you can see I use var fileName = Path.GetFileName(file.FileName); and item.ImageURL = "/Uploads/" + file.FileName; To populate my ImageUrl. The file itself is saved on the server.
Now my issue is that I don't know how to get the "Edit" view and ActionResult to allow me either to keep the same image or upload a new image.
How can I do this?
What I have done so far is in the Edit view to write:
<div class="form-group">
#if (Model.ImageURL == null)
{
<fieldset>
<legend>Upload a file</legend>
<div class="editor-field">
#Html.TextBox("file", "", new { type = "file" })
</div>
</fieldset>
}
else
{
/*Keep or upload new image here*/
}
</div>
What would I need to write in that else {...} and what would I need to write in the controller's public ActionResult Edit(int? id) {...} ?
Just show Image in the img control and below that use another file control , in case user need to change the uploaded image..
<div class="form-group">
#if (Model.ImageURL == null)
{
<fieldset>
<legend>Upload a file</legend>
<div class="editor-field">
#Html.TextBox("file", "", new { type = "file" })
</div>
</fieldset>
}
else
{
/*show Image in an img control using image path*/
<img src="#Model.ImageURL" height="100" width="100" />
<fieldset>
<legend>change the file</legend>
<div class="editor-field">
#Html.TextBox("file", "", new { type = "file" })
</div>
</fieldset>
}
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
and at Action part everything will be same , remove the old file and save new one and update db as well.
also Don't forget to use form as you have used in the create view.
#using (Html.BeginForm(null, null, FormMethod.Post, new { enctype = "multipart/form-data" }))
if image is not uploaded For the particular item How you will edit . You can do create and edit both in single action method by
var itemInDb = db.items
.Where(c => c.itemid== id) // or whatever your key is
.SingleOrDefault();
if (itemInDb != null)
db.Countries.ApplyCurrentValues(item);
else
db.Countries.AddObject(item);
db.SaveChanges();
if multiple images are there for single image you can loop it

action for ajax.beginform throwing 404 from mvc app not from fiddler

I have the following and am getting a 404 on submit, but from fiddler I'm getting a 500 error about not having the antiforgerytoken. Any thoughts what might be wrong?
ShareController.cs code
// creates the form
[HttpPost]
public ActionResult ShareVideoFormEmail(string vguid)
{
var shareModel = GetShareVideoFormEmailModel(vguid);
return PartialView("ShareVideoFormEmail", shareModel);
}
// handler function
[HttpPost]
[ValidateHoneyPot]
[ValidateAntiForgeryToken]
public ActionResult ProcessShareVideoFormEmail(ShareVideoEmailModel model)
{
//do stuff
return PartialView("Result");
}
ShareVideoFormEmail.cshtml
#model Gcc.GctWebsite.Areas.Forms.Models.Share.ShareVideoEmailModel
#using (Ajax.BeginForm("ProcessShareVideoFormEmail","share",null ,new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "shareVideoFormWithMyFriends",
OnBegin = "onBeginShareEmail", #* show ajaxspinner *#
OnComplete = "onCompleteShareEmail", #* hide ajaxspinner *#
OnSuccess = "onSuccessShareEmail" #* hide ajaxspinner *#
}, new { id = "shareVideoEmailModelForm" }))
{
<div class="modal-header">
<div class="modal-header-title">Share the video "#ViewBag.VideoTitle" with my friends</div>
</div>
<div class="modal-body">
<div id ="shareVideoFormWithMyFriends" class="share-form-email">
#Html.AntiForgeryToken()
#Html.HoneyPot()
#Html.HiddenFor(x => x.UpdateTargetId)
#Html.HiddenFor(x => x.OgMetaTitle)
#Html.HiddenFor(x => x.OgMetaDescription)
#Html.HiddenFor(x => x.OgMetaDefaultImage)
#Html.HiddenFor(x => x.OgMetaImage)
#Html.HiddenFor(x => x.Brand)
#Html.HiddenFor(x => x.VGuid)
#Html.HiddenFor(x => x.TripCode)
#Html.HiddenFor(x => x.EmailSubject)
<!-- big section of input fields for model properties -->
<div class="form-field">
<span class="req-lbl">* = Required</span>
</div>
<div class="form-field form-submit">
<input class="btn btn-main-theme btn-narrow btn-submit-form" value="Send Email" type="submit">
<input type="reset" class="btn btn-grey btn-reset cancel-share-Email" value="Cancel">
</div>
<script>
$(document).ready(function (e) {
$('.required-input').after("<span class=\"rqdfield\">*</span>");
loadJqueryValidationManually("#shareVideoEmailModelForm");
});
</script>
It turns out that in my model in some of the data there were some html no-no's. I had to htmlencode a couple properties before displaying the view, then it worked fine upon postback.
I discovered this by changing the input parameter of the action method to object and removing the honeypot and xsfr validation. Then I got a nice 500 error explaining I had unsafe data.

Making action in controller void

I have this kind of problem: In a view I have one button upload which uploads an image and stores it to some folder. Is there a way to call action upload, but not to return a view? Just to save that content, and then I have another button next which will redirect me to a new view. I tried with return type void, but it sends me to blank page.
This are form buttons for upload and next
#using (Html.BeginForm("Upload", "Account", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
<div class="form-group">
#Html.LabelFor(model => model.Picture, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
<span class="btn btn-primary btn-file">
Choose <input type="file" name="file" />
</span>
<input type="submit" value="Upload" name="buttonName" class="btn btn-primary"/>
</div>
</div>
}
#using (Html.BeginForm("Next", "Account", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
<div class="form-group">
<div class="col-md-10">
<input type="submit" value="Next" name="buttonName" class="btn btn-default" />
</div>
</div>
}
This is action:
public void Upload(HttpPostedFileBase file, string btnSubmit)
{
if (file != null && file.ContentLength > 0)
{
var fileName = Path.GetFileName(file.FileName);
var path = Path.Combine(Server.MapPath("~/Images/"), fileName);
file.SaveAs(path);
}
}
You can use the Content() method to return ContentResult ContentResult by default returns a text/plain as its contentType.
public ActionResult Upload(HttpPostedFileBase file, string btnSubmit)
{
if (file != null && file.ContentLength > 0)
{
var fileName = Path.GetFileName(file.FileName);
var path = Path.Combine(Server.MapPath("~/Images/"), fileName);
file.SaveAs(path);
}
return Content("Success");
}
You could return an EmptyResult.
MVC wants you to use EmptyResult when the action is specifically
intended to return nothing. Unlike all of the previous ActionResults
though, EmptyResult doesn't have a helper. Additionally, if an action
returns null, MVC will detect that and make it return an EmptyResult.
Ok, I searched about this and I think that i need some ajax without refreshing page. I found this File upload using MVC 4 with Ajax. I'll try it to see if this works.
The correct solutions are to use either:
return new HttpStatusCodeResult(HttpStatusCode.OK);
to indicate the upload was received and all processing has completed.
return new HttpStatusCodeResult(HttpStatusCode.Accepted);
to indicate the upload was received and further processing will continue.

Uploading file ASP MVC 4.0

i'm trying to upload a file with ASP MVC4, i give you my situation:
I have a model class "Movie" with some atributes (doesn't matter)
I want to add some code to my controller and View part without touching the model, because we want to make the image difrent to the model.
So, here's an example of my view code, i'll bold the lines added by me:
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Movie</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Duration)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Duration)
#Html.ValidationMessageFor(model => model.Duration)
</div>
//ADDED BY ME:
<div class="editor-label">
<p>Select a file</p>
</div>
<div class="editor-field">
<input type="file" name="fileUpload" />
</div>//END OF MY CODE
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
So you can see that i have some code generated by ASP which allows me to add a new user to the db, and a "input" added by me to upload the image.
The problem is that when i try to recover that image from the controller, the "Request.Files" atributte is empty, so i can't recover any image, and of course i can't upload it, here's my controller code:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Movie movie)
{
if (ModelState.IsValid)
{
db.MovieContext.Add(movie);
db.SaveChanges();
foreach (string file in Request.Files)
{
var postedFile = Request.Files[file];
postedFile.SaveAs(Server.MapPath("~/UploadedFiles") + pelicula.Id);
}
return RedirectToAction("Index");
}
return View(movie);
}
Don't know why the "Request.Files" is empty, so if anyone can help me it would be great, Thank you so much
Try the below:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Movie movie, HttpPostedFile fileUpload)
{
if (ModelState.IsValid)
{
db.MovieContext.Add(movie);
db.SaveChanges();
var postedFile = fileUpload;
postedFile.SaveAs(Server.MapPath("~/UploadedFiles") + pelicula.Id);
return RedirectToAction("Index");
}
return View(movie);
}
Lets see if we can find out whats wrong. Try the following and show us the errors:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Movie movie, HttpPostedFile fileUpload)
{
if (ModelState.IsValid)
{
db.MovieContext.Add(movie);
db.SaveChanges();
var postedFile = fileUpload;
postedFile.SaveAs(Server.MapPath("~/UploadedFiles") + pelicula.Id);
return RedirectToAction("Index");
}
var content = "";
foreach (ModelState modelState in ViewData.ModelState.Values)
{
foreach (ModelError error in modelState.Errors)
{
content += error.ErrorMessage + ", " + error.Exception + "<br/>";
}
}
return Content(content);
//return View(movie);
}
Antevirus has the answer but I hat to dig into comments to find it out. In short the problem for was that I didnt specify any encodings for my form which is
enctype = "multipart/form-data"
Here is the full code:
#using (Html.BeginForm("Create", "Members", FormMethod.Post, new { #id = "member-form", #enctype = "multipart/form-data" }))
{
<div class="form-group">
<label class="control-label col-md-2">Picture</label>
<div class="col-md-10">
<input id="fileUpload" type="file" name="fileUpload" class="file-loading">
</div>
</div>
}
I am using file-input for bootstrap

Categories

Resources