MVC 4 ajax form image upload fails - asp.net-mvc

I am trying to add an image by using MVC 4 ajax form, but it always returns null value. I added my model, controller and view.
My View
#using (Ajax.BeginForm("Create", "Images", new AjaxOptions { HttpMethod = "Post", OnSuccess = "OnSuccess", OnFailure = "OnFailure", UpdateTargetId = "messageDiv" }, new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<div class="editor-field">
#Html.LabelFor(model => model.Image.Description, new { #class = "control-label" })
<div class="controls">
#Html.TextBoxFor(model => model.Image.Description)
</div>
#Html.TextBoxFor(model => model.FileImage, new { type = "file" })
#Html.ValidationMessageFor(model => model.FileImage)
</div>...
}
My Model
public class ImageViewModel
{
public IList<Image> Images { get; set; }
public Image Image { get; set; }
public HttpPostedFileBase FileImage { get; set; }
}
My Controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(ImageViewModel model, HttpPostedFileBase FileImage)
{
if (ModelState.IsValid)
{
var x = FileImage.FileName;
var imageUrl = Path.GetFileName(model.FileImage.FileName);
...
}
}
In this example, I could not get the FileName. it returns always null value. Could you help me to solve this problem. I will be very happy.

I would prefer read the image using the request intead of trying to bind that to a model,
public ActionResult Create(ImageViewModel model)
{
if (Request.Files != null)
{
HttpPostedFileBase file = Request.Files[0]; //assuming that's going to be the first file
if (file.ContentLength > 0)
{
string fileName = Path.GetFileName(file.FileName);
string directory = Server.MapPath("/"); //change ths to your actual upload folder
file.SaveAs(Path.Combine(directory, fileName));
}
}
}

Related

AllowHtml Form Handling: Nothing being written to database

In an ASP.net MVC 5 application, I'm using Summernote.js to display an HTML text editor for users to input a message. My model includes the necessary attribute:
[AllowHtml]
[Display(Name = "Welcome Message")]
public string message { get; set; }
My controller binds all the fields to an object and writes them to the database:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "id,name,surveytype,industryId,start,end,message,resultsdate,resultsfile,resultsfile2")] Survey survey, HttpPostedFileBase bentemplate, HttpPostedFileBase comptemplate)
{
if (ModelState.IsValid)
{
if(bentemplate != null && bentemplate.ContentLength > 0)
{
var filename = Path.GetFileName(bentemplate.FileName);
var fullpath = Path.Combine(Server.MapPath("~/templates"), filename);
bentemplate.SaveAs(fullpath);
survey.bentemplateaddress = fullpath;
}
if (comptemplate != null && comptemplate.ContentLength > 0)
{
var filename = Path.GetFileName(comptemplate.FileName);
var fullpath = Path.Combine(Server.MapPath("~/templates"), filename);
bentemplate.SaveAs(fullpath);
survey.comptemplateaddress = fullpath;
}
db.surveys.Add(survey);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(survey);
}
Relevant view code (the textarea-editor class make it appear as a Summernote editor):
#Html.LabelFor(model => model.message, htmlAttributes: new { #class = "control-label" })
#Html.EditorFor(model => model.message, new { htmlAttributes = new { #class = "form-control input-lg textarea-editor" } })
#Html.ValidationMessageFor(model => model.message, "", new { #class = "text-danger" })
Unfortunately, everything is being written except the welcome message.
Any suggestions on what is causing this?
Thanks!

Uploading images from different input files in MVC

I want to upload Images to a database. The database contains url images and images uploads to folder in file system. I have the following tables in the database,
Furniture
MainFileDetails (1-1 relationship with Furniture) where store the main image
FileDetails (1-Many relationship with Furniture) where we store other images associated with Furniture.
Here is my code:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Furniture furniture , HttpPostedFileBase fileTwo, HttpPostedFileBase file)
{
if (ModelState.IsValid)
{
for (int i = 0; i < Request.Files.Count; i++)
{
fileTwo = Request.Files[i];
if (fileTwo != null && fileTwo.ContentLength > 0)
{
var fileNameTwo = Path.GetFileName(fileTwo.FileName);
MainFileDetails mainFileDetail = new MainFileDetails()
{
FileName = fileNameTwo,
Extension = Path.GetExtension(fileNameTwo),
Id = Guid.NewGuid(),
FurnitureId = furniture.FurnitureId
};
var pathMain = Path.Combine(Server.MapPath("~/Upload/MainPage/"), mainFileDetail.Id + mainFileDetail.Extension);
fileTwo.SaveAs(pathMain);
db.Entry(mainFileDetail).State = EntityState.Modified;
db.SaveChanges();
FileDetail fileDetail = new FileDetail()
{
NameFile = fileNameTwo, //or mainFileDetail.FileName
Extension = Path.GetExtension(fileNameTwo), //or mainFileDetail.Extension
Id = Guid.NewGuid(),
FurnitureId = furniture.FurnitureId //or mainFileDetail. FurnitureId
};
var path = Path.Combine(Server.MapPath("~/Upload/"), fileDetail.Id + fileDetail.Extension);
file.SaveAs(path);
db.Entry(fileDetail).State = EntityState.Added;
}
}
db.Entry(furniture).State = EntityState.Modified;
db.SaveChanges();
TempData["message"] = string.Format("Changes in \"{0}\" has been saved", furniture.Name);
return RedirectToAction("Index");
}
ViewBag.CategoryId = new SelectList(db.Categories, "CategoryId", "Name", furniture.CategoryId);
return View(furniture);
}
My View:
#model FurnitureStore.Entities.Furniture
....
#using (Html.BeginForm("Edit", "Furnitures", FormMethod.Post, new { enctype = "multipart/form-data"}))
{
....
#Html.HiddenFor(model => model.FurnitureId)
#Html.LabelFor(model => model.Name)
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
.... // more inputs for properties of the model
#Html.LabelFor(model => model.CategoryId, "Category")
#Html.DropDownList("CategoryId", null)
#Html.ValidationMessageFor(model => model.CategoryId, "", new { #class =
// Main file
<input type="file" name="fileTwo" />
#if(Model.MainFileDetails.Id == null)
{
<div class="form-control-static">No image</div>
}
else
{
<img src="~/Upload/MainPage/#(Model.MainFileDetails.Id + Model.MainFileDetails.Extension)" width="240" height="240" />
}
// Other files
<input type="file" name="file" multiple="multiple" />
<ul class="attachment">
#foreach (var item in Model.FileDetails)
{
<li style="list-style-type:none; display:inline;">
<img src="~/Upload/#(item.Id + item.Extension)" />
X
</li>
}
</ul>
<input type="submit" value="Save" class="btn btn-primary" />
}
But I have a problem. All photo just uploads to the Upload/MainPage folder. I want to upload photo separately to MainPage in one table and separately to gallery (other table).
How can I fix my code, because last uploaded photo in gallery table replaces photo in MainPage?
The foreach loop in your controller method is looping through every file you have uploaded, and then you create a new MainFileDetails for each file and save it. You need to get the 'main file' from the first file input and save it to MainFileDetails and then loop through the files in the second file input and save each of them to FileDetails.
To solve the immediate problem, change your method to
public ActionResult Edit(Furniture furniture , HttpPostedFileBase fileTwo, IEnumerable<HttpPostedFileBase> file)
and those parameters will be bound with the values for the file inputs. Then its simply
if (ModelState.IsValid)
{
if (fileTwo != null && fileTwo.ContentLength > 0)
{
// create an instance of MainFileDetails and set its properties based on
// the values of fileTwo and save
}
foreach (HttpPostedFileBase image in file)
{
// create an instance of FileDetails and set its details based on
// the values of image and save
}
db.Entry(furniture).State = EntityState.Modified;
db.SaveChanges();
....
There are however numerous other potential issues with your code.
Rule 1. Your editing data, so always use a view model. What is
ViewModel in
MVC?
Its unclear why you need a separate 1 to 1 table for the main image.
The properties for that could be stored in the Furniture table.
Alteratively you could just have the 1 to Many table, and have a
bool IsMainImage property which would allow you to easily swap
which is the main image.
You images table (FurnitureImages would be a more appropriate name)
should include a auto-incremented int ID for the PK and properties
for the file path and file display name
If ModelState is invalid, you return the view, but all the files
are lost and the poor user needs to re-select them all again
Your view models should be
public class FurnitureVM
{
public int? ID { get; set; }
[Required( ... )]
public string Name { get; set; }
.... // other properties of Furniture that you need in the view
[Required( ... )]
public int? Category { get; set; }
public IEnumerable<SelectListItem> CategoryList { get; set; }
public HttpPostedFileBase MainFile { get; set; }
public IEnumerable<HttpPostedFileBase> SecondaryFiles { get; set; }
public ImageVM MainImage { get; set; }
public List<ImageVM> SecondaryImages { get; set; }
}
public class ImageVM
{
public int? ID { get; set; }
public string Path { get; set; }
public string DisplayName { get; set; }
}
And the view
#model yourAssembly.FurnitureVM
....
#using (Html.BeginForm("Edit", "Furnitures", FormMethod.Post, new { enctype = "multipart/form-data"}))
{
// Note: no need for a hidden input for the ID property assuming your using the default routing
#Html.LabelFor(m => m.Name)
#Html.EditorFor(m => m.Name)
#Html.ValidationMessageFor(m => m.Name)
.... // more inputs for properties of the model
#Html.LabelFor(m => m.Category)
#Html.DropDownListFor(m => m.Category, Model.CategoryList, "Please select")
#Html.ValidationMessageFor(m => m.Category)
// Main file
#Html.TextBoxFor(m => m.MainFile, new { type = "file" })
#Html.ValidationMessageFor(m => m.MainFile)
if (Model.MainImage != null)
{
#Html.HiddenFor(m => m.MainImage.ID)
#Html.HiddenFor(m => m.MainImage.Path)
#Html.HiddenFor(m => m.MainImage.DisplayName)
<img src="#MainImage.Path" alt="#MainImage.DisplayName" />
}
// Secondary files
#Html.TextBoxFor(m => m.SecondaryFiles, new { type = "file", multiple = "multiple" })
#Html.ValidationMessageFor(m => m.SecondaryFiles)
for (int i = 0; i < Model.SecondaryImages.Count; i++)
{
#Html.HiddenFor(m => m.SecondaryImages[i].ID)
...
}
<input type="submit" value="Save" />
}
And the controller POST method
public ActionResult Edit(FurnitureVM model)
{
// Save the files
if (model.MainFile != null && model.MainFile.ContentLength > 0)
{
string displayName = model.MainFile.FileName;
string extension = Path.GetExtension(displayName)
string fileName = string.Format("{0}{1}", Guid.NewGuid(), extension)
string path = Path.Combine("~/Images/", fileName);
model.MainFile.SaveAs(Server.MapPath(path));
model.MainImage = new ImageVM() { Path = path, DisplayName = displayName };
}
foreach (HttpPostedFileBase file in model.SecondaryFiles)
{
....
}
if (!ModelState.IsValid)
{
model.CategoryList = new SelectList(....); // repopulate the SelectList
return View(model);
}
// Get the data model from the database
Furniture furniture = db.Furniture.Where(x => x.FurnitureId == model.ID).FirstOrDefault()
// Update properties based on view model
furniture.Name = modell.Name;
...
// Update the main image
if (model.MainImage != null && !model.MainImage.ID.HasValue)
{
FurnitureImages image = new FurnitureImages()
{
image.Path = model.MainImage.Path;
image.DisplayName = model.MainImage.DisplayName;
image.IsMainImage = true;
furniture.Images.Add(image);
}
// Update secondary images
IEnumerable<ImageVM> newImages = model.SecondaryImages.Where(x => x.ID == null);
foreach (ImageVM image in newImages)
{
.... // add to the collection
}
// Save and redirect
}

Repopulating selectlist for dropdownlist on postback when default selected ASP.NET MVC

New to ASP.NET MVC and I've come across this before, however I feel I've gone about fixing it in an incorrect way. I was hoping someone could point me in the direction of the correct way of doing it.
I have a page which the user selects a dropdown value and clicks next. Now, if they don't select an item and select the default value ("Select..."), there is a validation error. The controller seems to lose information about the dropdownlist even though the model is returned to the view on a "postback." So, say I don't basically repeat code in my [HTTPPost] from my [HTTPGet]. I get the error:
There is no ViewData item of type 'IEnumerable<SelectListItem>' that has the key 'SheetIndex'.
on
#Html.DropDownListFor(model => model.SheetIndex, Model.SheetsDropdown, "Select...", new { #class = "form-control" })
So unless I repeat code, I get that error when no selection is made. What am I doing wrong?
ViewModel:
public class SelectSheetViewModel
{
public int? Id { get; set; }
public string Name { get; set; }
[Required]
public string SheetIndex { get; set; }
public string SheetName { get; set; }
public int? ChainId { get; set; }
public int? SheetId { get; set; }
public int? FileId { get; set; }
public IEnumerable<SelectListItem> SheetsDropdown { get; set; }
public HeaderViewModel Header { get; set; }
}
Controller:
[HttpGet]
public ActionResult SelectSheet(int? chainId, int? sheetId, int? fileId)
{
if (sheetId == null || fileId == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var fileDetails = db.FileDetails.Find(fileId);
SelectSheetViewModel selectSheet = new SelectSheetViewModel()
{
Id = fileDetails.FileId,
Name = fileDetails.Name,
ChainId = chainId,
SheetId = sheetId,
FileId = fileId
};
string fileName = fileDetails.UniqueName + fileDetails.Extension;
string relativeFileLocation = "~/uploads/" + fileName;
string absoluteFileLocation = HttpContext.Server.MapPath(relativeFileLocation);
if (System.IO.File.Exists(absoluteFileLocation))
{
DSDBuilder builder = new DSDBuilder();
selectSheet.SheetsDropdown = builder.GetSheets(absoluteFileLocation); // Where I get my selectlist
}
else
{
ModelState.AddModelError("SheetDropdown", "Excel workbook does not exist.");
}
selectSheet.Header = BuildHeaderViewModel(chainId, sheetId);
return View(selectSheet);
}
[HttpPost]
public ActionResult SelectSheet(SelectSheetViewModel selectSheet)
{
if (ModelState.IsValid)
{
FileDetail fileDetails = db.FileDetails.Find(selectSheet.FileId);
string[] sheetIndexAndName = selectSheet.SheetIndex.Split(':');
fileDetails.SheetIndex = Convert.ToInt32(sheetIndexAndName[0]);
fileDetails.SheetName = sheetIndexAndName[1];
db.SaveChanges();
return RedirectToAction("Build", "Sheets", new
{
ChainId = selectSheet.ChainId,
SheetId = selectSheet.SheetId,
FileId = selectSheet.FileId
});
}
// Probably not a good method vvv
var fileDetailsPostBack = db.FileDetails.Find(selectSheet.FileId);
string fileName = fileDetailsPostBack.UniqueName + fileDetailsPostBack.Extension;
string relativeFileLocation = "~/uploads/" + fileName;
string absoluteFileLocation = HttpContext.Server.MapPath(relativeFileLocation);
if (System.IO.File.Exists(absoluteFileLocation))
{
DSDBuilder builder = new DSDBuilder();
selectSheet.SheetsDropdown = builder.GetSheets(absoluteFileLocation);
}
else
{
ModelState.AddModelError("SheetDropdown", "Excel workbook does not exist.");
}
// Probably not a good method ^^^
return View(selectSheet);
}
View:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.ChainId)
#Html.HiddenFor(model => model.SheetId)
#Html.HiddenFor(model => model.FileId)
#Html.HiddenFor(model => model.Header.ChainName)
#Html.HiddenFor(model => model.Header.SheetName)
#Html.HiddenFor(model => model.Header.SheetDescription)
<div class="form-horizontal">
<div class="form-group">
#Html.LabelFor(model => model.SheetIndex, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.SheetIndex, Model.SheetsDropdown, "Select...", new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.SheetIndex, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Next" class="btn btn-default" />
</div>
</div>
</div>
}
I also would not like to repeat that code because it goes to a function which uses Excel.Interop and is kind of resource heavy. Let me know if anyone has a better solution. I'm always trying to improve my code and do things the "correct" way.

Validation Message MVC

My code is as followed and the error message are not displayed:
Index.cshtml
#model WebApp.Models.OrderItems
#using (Html.BeginForm("SaveToDB", "Home", FormMethod.Post, new { #class = "form-group", role = "form" }))
{
#Html.Partial("Information")
}
Partial : Information.cshtml
#model WebApp.Models.OrderItems
<div class="col-lg-4">
<div class="form-group">
<label for="input1" class="col-lg-4 control-label">#Html.LabelFor(model => model.CLInfo.ClientName)</label>
#Html.EditorFor(model => model.CLInfo.ClientName, new { style = "width:250px" })
#Html.ValidationMessageFor(model => model.CLInfo.ClientName)
</div>
</div>
Model :
public class OrderItems
{
public InfoCLInfo{ get; set; }
}
Model : the class for Infos
public class Info
{
[Display(Name = "Client Name")]
[Required]
public string ClientName{ get; set; }
}
The controller
[HttpPost]
[MultipleButton(Name = "action", Argument = "SaveToDB")]
public ActionResult SaveToDB(OrderItems Client)
{
var errors = ModelState.Values.SelectMany(v => v.Errors);
if (ModelState.IsValid)
{
if (_db == null)
_db = new OrderDB();
OrderItems ClientOrig = Session["Clientobj"] as OrderItems;
ClientOrig.CLInfo = Client.CLInfo;
Session["Clientobj"] = null;
}
return RedirectToAction("Index", "Home");
}
[Authorize]
public ActionResult Index (OrderItems Client)
{
int ClientID = Convert.ToInt32(Session["Client"]);
if ClientID == 0)
{
ClientID = 2;
Session["Client"] = ClientID;
}
if (Session["Clientobj"] == null)
{
Client = new OrderItems();
Client.CLOrderID = 123;
Session["Clientobj"] = Client;
}
else
{
Client = Session["Clientobj"] as OrderItems
}
return View(Client);
}
on post the ModelState.IsValid return false which true, but I don't have any message to tell the user where is the error to be fixed.
I tried to add : #Html.ValidationSummary(true) after the BeginForm , but it didn
Any idea please
Thanks
You cannot use RedirectToAction if you want to retain your model state. All errors and what not are kept in the ModelState object, and when you redirect to action it's performing a new get action, which starts fresh with a clean slate.
You need to return the view like you do in the original action.

Html helper for <input type="file" />

Is there a HTMLHelper for file upload? Specifically, I am looking for a replace of
<input type="file"/>
using ASP.NET MVC HTMLHelper.
Or, If I use
using (Html.BeginForm())
What is the HTML control for the file upload?
HTML Upload File ASP MVC 3.
Model: (Note that FileExtensionsAttribute is available in MvcFutures. It will validate file extensions client side and server side.)
public class ViewModel
{
[Required, Microsoft.Web.Mvc.FileExtensions(Extensions = "csv",
ErrorMessage = "Specify a CSV file. (Comma-separated values)")]
public HttpPostedFileBase File { get; set; }
}
HTML View:
#using (Html.BeginForm("Action", "Controller", FormMethod.Post, new
{ enctype = "multipart/form-data" }))
{
#Html.TextBoxFor(m => m.File, new { type = "file" })
#Html.ValidationMessageFor(m => m.File)
}
Controller action:
[HttpPost]
public ActionResult Action(ViewModel model)
{
if (ModelState.IsValid)
{
// Use your file here
using (MemoryStream memoryStream = new MemoryStream())
{
model.File.InputStream.CopyTo(memoryStream);
}
}
}
You can also use:
#using (Html.BeginForm("Upload", "File", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<p>
<input type="file" id="fileUpload" name="fileUpload" size="23" />
</p>
<p>
<input type="submit" value="Upload file" /></p>
}
Or you could do it properly:
In your HtmlHelper Extension class:
public static MvcHtmlString FileFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression)
{
return helper.FileFor(expression, null);
}
public static MvcHtmlString FileFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
{
var builder = new TagBuilder("input");
var id = helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(ExpressionHelper.GetExpressionText(expression));
builder.GenerateId(id);
builder.MergeAttribute("name", id);
builder.MergeAttribute("type", "file");
builder.MergeAttributes(new RouteValueDictionary(htmlAttributes));
// Render tag
return MvcHtmlString.Create(builder.ToString(TagRenderMode.SelfClosing));
}
This line:
var id = helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(ExpressionHelper.GetExpressionText(expression));
Generates an id unique to the model, you know in lists and stuff. model[0].Name etc.
Create the correct property in the model:
public HttpPostedFileBase NewFile { get; set; }
Then you need to make sure your form will send files:
#using (Html.BeginForm("Action", "Controller", FormMethod.Post, new { enctype = "multipart/form-data" }))
Then here's your helper:
#Html.FileFor(x => x.NewFile)
I had this same question a while back and came across one of Scott Hanselman's posts:
Implementing HTTP File Upload with ASP.NET MVC including Tests and Mocks
Hope this helps.
Improved version of Paulius Zaliaduonis' answer:
In order to make the validation work properly I had to change the Model to:
public class ViewModel
{
public HttpPostedFileBase File { get; set; }
[Required(ErrorMessage="A header image is required"), FileExtensions(ErrorMessage = "Please upload an image file.")]
public string FileName
{
get
{
if (File != null)
return File.FileName;
else
return String.Empty;
}
}
}
and the view to:
#using (Html.BeginForm("Action", "Controller", FormMethod.Post, new
{ enctype = "multipart/form-data" }))
{
#Html.TextBoxFor(m => m.File, new { type = "file" })
#Html.ValidationMessageFor(m => m.FileName)
}
This is required because what #Serj Sagan wrote about FileExtension attribute working only with strings.
To use BeginForm, here's the way to use it:
using(Html.BeginForm("uploadfiles",
"home", FormMethod.POST, new Dictionary<string, object>(){{"type", "file"}})
This also works:
Model:
public class ViewModel
{
public HttpPostedFileBase File{ get; set; }
}
View:
#using (Html.BeginForm("Action", "Controller", FormMethod.Post, new
{ enctype = "multipart/form-data" }))
{
#Html.TextBoxFor(m => m.File, new { type = "file" })
}
Controller action:
[HttpPost]
public ActionResult Action(ViewModel model)
{
if (ModelState.IsValid)
{
var postedFile = Request.Files["File"];
// now you can get and validate the file type:
var isFileSupported= IsFileSupported(postedFile);
}
}
public bool IsFileSupported(HttpPostedFileBase file)
{
var isSupported = false;
switch (file.ContentType)
{
case ("image/gif"):
isSupported = true;
break;
case ("image/jpeg"):
isSupported = true;
break;
case ("image/png"):
isSupported = true;
break;
case ("audio/mp3"):
isSupported = true;
break;
case ("audio/wav"):
isSupported = true;
break;
}
return isSupported;
}
List of contentTypes
This is a little hacky I guess, but it results in the correct validation attributes etc being applied
#Html.Raw(Html.TextBoxFor(m => m.File).ToHtmlString().Replace("type=\"text\"", "type=\"file\""))

Resources