I'm attempting to upload a number and a file through an Html-Helper form in my MVC 5 application. After submitting the model, it seems that my model.File property from the view model always returns null.
Before adding the model.File != null check, my application was throwing a NullReferenceException.
I have my form logic contained in a partial view called UploadFile.cshtml, since my form logic requires a different model than my index.cshtml.
How do I read the File property from my form in my [HttpPost] controller action?
index.cshtml
#Html.Action("UploadFile", "Home")
UploadFile.cshtml
#model FileViewModel
#using (Html.BeginForm("FormUpload", "Home", FormMethod.Post))
{
<div class="form-group">
#Html.TextBoxFor(m => m.Marker)
</div>
<div class="form-group">
#Html.TextBoxFor(m => m.File, new { type = "file" })
</div>
<input type="submit" name="submit" value="Submit" />
}
HomeController
public PartialViewResult UploadFile()
{
return PartialView("UploadFile", new FileViewModel());
}
// /Home/FormUpload
[HttpPost]
public ActionResult FormUpload(FileViewModel model)
{
if (ModelState.IsValid)
{
if (model.File != null && model.File.ContentLength > 0 && model.File.ContentType == "application/pdf")
{
// Convert file to byte[] and upload
// ...
ViewBag.Message = "File Uploaded Successfully";
}
else
{
ViewBag.Message = "File Not Uploaded";
}
}
return RedirectToAction("Index");
}
FileViewModel
public class FileViewModel
{
public int ID { get; set; }
public int Marker { get; set; }
public string Filename { get; set; }
public HttpPostedFileBase File { get; set; }
}
You are missing enctype attribute for the form, you need to specify that for file case:
#using (Html.BeginForm("FormUpload", "Home",
FormMethod.Post,
new { enctype = "multipart/form-data" }))
See this article for further details.
Html.BeginForm("FormUpload", "Home", FormMethod.Post))
should be
#using (Html.BeginForm("FormUpload", "Home", FormMethod.Post, new {enctype = "multipart/form-data"}))
Related
My ASP.NET MVC viewmodel is designed as below
public class Customer
{
public int CustomerId{ get; set; }
public string CustomerName{ get; set; }
public Address CustomerAddress {get;set;}
}
public class Address
{
public int AddressId{ get; set; }
public string HouseName{ get; set; }
public string Location{get;set;}
}
How can I bind Address object properly in cshtml page so that it should be available after form Submit.
In cshtml page I want bind it the properties as below
#model CustomerManagement.Model.ViewModels.Customers.Customer
#using (Html.BeginForm())
{
#Html.EditorFor(model => model.CustomerName, new { htmlAttributes = new { #class = "form-
control readOnlySchdCode", #readonly = "readonly" } })
#Html.Hidden("AddressId", Model.Address.AddressId)
#Html.Hidden("HouseName", Model.Address.HouseName)
}
In controller form submit will look like as below
public async Task<ActionResult> AddCustomer(Customer model)
{
//How can i access Address properties eg:model.CustomerAddress.AddressId??
}
Can anyone share a sample code on how to bind the Above viewmodel properly in cshtml using razor template and how properties are properly retrieved in Action method while form submit.
You could try this way.
Client side:
#using Newtonsoft.Json.Linq
#using WebAppDemo.Models
#model WebAppDemo.Models.Customer
#{
ViewData["Title"] = "Home Page";
}
#{
ViewBag.Title = "Home Page";
}
<br />
#using (Html.BeginForm("AddCustomer", "Home", FormMethod.Post, new { id = "Form1" }))
{
<div class="row">
<div class="col-lg-2">Cust Id</div>
<div class="col-lg-10">
#Html.TextBoxFor(a => a.CustomerId, new { #class = "form-control" })
</div>
</div>
<br />
<div class="row">
<div class="col-lg-2">Customer Name</div>
<div class="col-lg-10">
#Html.TextBoxFor(a => a.CustomerName, new { #class = "form-control" })
</div>
</div>
<br />
<div class="row">
<div class="col-lg-2">Address</div>
<div class="col-lg-10">
#Html.TextBoxFor(a => a.CustomerAddress.HouseName, new { #class = "form-control" })
</div>
</div>
<br />
<div class="row">
<div class="col-lg-12"><input type="submit" value="Submit" class="btn btn-primary"></div>
</div>
}
Form Output Should Be Like:
Controller:
[HttpPost] //attribute to get posted values from HTML Form
public ActionResult AddCustomer(Customer model)
{
return Ok();// For testing I just kept it as `Ok` You can return `View()`;
}
Note: Please try to pass value on form as per Model property descriptions. Other than you might
get null value.
Output:
Hope it will help you. Let me know if you have any further concern.
Here is a little example
public class BigViewModel : IModelOptions
{
public bool Confirm { get; set; }
public SmallViewModel SmallView { get; set; }
}
public class SmallViewModel
{
public string Stuff{ get; set; }
}
public interface IModelOptions
{
SmallViewModel SmallView { get; set; }
}
and our controller would be like
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(BigViewModel model)
{
var smallVIewModelInfo = model.SmallView.Stuff;
var bigViewModelConfirm = model.Confirm;
return View();
}
}
using view like
#model MvcApplication1.Models.BigViewModel
I’m new to MVC and I would like to create a simple view that allows a user to enter some data => submit the form => perform some calculation on the server => and post back a model with the calculated data to the same view.
I found a way to make it work, but I’m not sure if this is the best way to do it and what bothers me is that after the submit, all the data from the model properties are visible in the browser address bar :
http://localhost:53718/?Number=4&Result=16
Here is the code for a very simplified szenario:
The Model:
public class CalculationModel
{
public int Number { get; set; }
public int Result { get; set; }
}
The Controler:
public class HomeController : Controller
{
public ActionResult Index(CalculationModel model)
{
return View(model);
}
[HttpPost]
public ActionResult Calculate(CalculationModel model)
{
model.Result = model.Number * model.Number;
return RedirectToAction("Index", "Home", model);
}
}
The View:
#model WebApplication1.CalculationModel
#{
ViewBag.Title = "Home Page";
}
<h2>Simple Calculation</h2>
#using (Html.BeginForm("Calculate", "Home", FormMethod.Post)){
<div class="form-horizontal" data-crypto-conversion-form>
<div class="form-group">
<div class="input-group">
#Html.EditorFor(model => model.Number, new { htmlAttributes = new { #class = "form-control " } })
</div>
</div>
<div class="form-group">
<div class="input-group">
#Html.EditorFor(model => model.Result, new { htmlAttributes = new { #class = "form-control " } })
</div>
</div>
<div class="form-group">
<input type="submit" value="Convert" class="btn btn-primary btn-sm submitConversion" />
</div>
</div>
}
I’m not sure if it’s a good idea to do a RedirectToAction from the HttpPost method: return RedirectToAction("Index", "Home", model); - are there any better ways ?
Is there any way to prevent the model property values from displaying in the url / browser address bar ?
Thanks a lot for your help.
The RedirectToAction method basically returns a 302 response with the new url in the location header and browser will issue a new GET request to that. When you pass a simple model along with that, it will be converted to querystring values and will be added to the new url in location header.
If you do not want to see the values in querystring, you can return to the Index view with the same view model object from your HttpPost action method. Make sure to clear the model state dictionary so that the new value of Result property will be rendered. You can use the ModelState.Clear() method do so.
[HttpPost]
public ActionResult Calculate(CalculationModel model)
{
model.Result = model.Number * model.Number;
ModelState.Clear();
return View("Index", model);
}
If you still want to return A RedirectResult response, you can do that, but pass the model data via TempData. Take a look at this post
How do I include a model with a RedirectToAction?
I would change the overall approach and use the same view, because it seems you might want to calculate over and over again. A Get Index method just to display the data to process and/or the result, and then a Post method to do the actual processing.
So, the Controller:
[HttpGet]
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(CalculationModel model)
{
model.Result = model.Number * model.Number;
model.Processed = true;
return View(model);
}
The model
public class CalculationModel
{
public int Number { get; set; }
public int Result { get; set; }
public bool Processed { get; set; }
public CalculationModel()
{
Processed = false;
}
}
And the view, which will display the result only after a post.
#model MvcApplication2.Models.CalculationModel
#{
ViewBag.Title = "Home Page";
}
<h2>Simple Calculation</h2>
#using (Html.BeginForm("Index", "Home", FormMethod.Post))
{
<div class="form-horizontal" data-crypto-conversion-form>
<div class="form-group">
<div class="input-group">
#Html.EditorFor(model => model.Number, new { htmlAttributes = new { #class = "form-control " } })
</div>
</div>
#*#(Model.Result)*#
<div class="form-group">
<div class="input-group">
#*#Html.EditorFor(model => model.Result, new { htmlAttributes = new { #class = "form-control " } })*#
#{if (Model!=null && Model.Processed)
{
<p>#(Model.Result)</p>
}
}
</div>
</div>
<div class="form-group">
<input type="submit" value="Convert" class="btn btn-primary btn-sm submitConversion" />
</div>
</div>
}
I searched the internet for possible problems that might be causing this and found nothing ...
When I put the IEnumerable<HttpPostedFileBase> FilesUploadedEvent as action parameter alongside ViewModel everything seems to be working fine.
[HttpPost]
public ActionResult Create(EventViewModel model, IEnumerable<HttpPostedFileBase> FilesUploadedEvent)
{
if (ModelState.IsValid)
{
...
Problem is when I try to put it inside object in my ViewModel
#model ViewModels.Event.EventViewModel
#using (Html.BeginForm("Create", "Event", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.ValidationSummary(true, null, new { #class = "validation-msg" })
#Html.EditorFor(m => m.BasicInfoSection)
...
<input type="submit" value="Add" />
}
Controller after removing 2nd parameter:
[HttpPost]
public ActionResult Create(EventViewModel model)
{
if (ModelState.IsValid)
{
...
ViewModel:
public class EventViewModel
{
public BasicInfoSection BasicInfoSection { get; set; }
...
And we put the parameter inside ViewModels object:
public class BasicInfoSection : IValidatableObject
{
public string Remarks { get; set; }
public IEnumerable<HttpPostedFileBase> FilesUploadedEvent { get; set; }
...
and here is editor template for BasicInfoSection :
#model ViewModels.Event.Parts.BasicInfoSection
<input id="FilesUploadedEvent" name="FilesUploadedEvent" type="file" data-role="upload" multiple="multiple" autocomplete="off">
#(Html.TextAreaFor(m => m.Remarks, new { #class = "form-control", style = "height:200px;" }))
...
Also if the form wont validate on some other fields is it possible to return Files in the postBack? I read it is not because of security reasons. Is Ajax validation the only method than?
The name attribute of the file input is not correct. Because FilesUploadedEvent is a property of BasicInfoSection, then in order to be bound on post back, the input would need to be
<input name="BasicInfoSection.FilesUploadedEvent" type="file" .. />
Note also how the textareahas the name attribute name="BasicInfoSection.Remarks"
I am having some difficulties in validation of form.
Since I want to populate combobox from database (another table, I'm using viewbags to do this), is there a way to use ComboBoxFor in this case so I could use System.ComponentModel.DataAnnotations and jquery.validate.js?
View:
#model MyProject.OpenAccess.Document
#using (Html.BeginForm("CreateDocument", "Create", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<label>Select file:</label><br />
<input type="file" name="file" /><br />
<label>Filetype</label><br />
#Html.DropDownList("fileType", (IEnumerable<SelectListItem>)ViewBag.ListFiletypes, "-Filetypes", new { #class = "filetype-cb" }) <br />
<input type="submit" value="Add"/>
}
Controller:
public ActionResult CreateDocument(HttpPostedFileBase file, string fileType)
{
if (file != null && file.ContentLength > 0)
{
Document doc = new Document()
{
Filetype = fileType
}
if (this.TryValidateModel(doc))
{
this.dbContext.Add(doc);
this.dbContext.SaveChanges();
id = doc.ID;
//save document using document ID
}
}
}
EDIT:
Here is my current implementation of this:
Layout.cshtml
Remember to add:
<script src="#Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
View
#model MyProject.OpenAccess.Document
#using (Html.BeginForm("CreateDocument", "Create", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<label>Select file:</label><br />
<input type="file" data-val="true" data-val-required="Please select a file!" name="file" /><br />
<label>Filetype</label><br />
#Html.DropDownListFor(m => m.FileType, (IEnumerable<SelectListItem>)ViewBag.ListFiletypes, "", new { #class = "filetype-cb" })
<input type="submit" value="Add"/>
}
Controller
[HttpGet]
public ActionResult CreateDocument()
{
ViewBag.ListFiletypes = new SelectList(this.dbContext.ListFiletypes.ToList(), "FileType", "FileType");
//Populate dropdownlist from database
}
[HttpPost]
public ActionResult CreateDocument(HttpPostedFileBase file, Document doc)
{
if (file != null && file.ContentLength > 0)
{
if (this.TryValidateModel(doc))
{
this.dbContext.Add(doc);
this.dbContext.SaveChanges();
id = doc.ID;
//save document using document ID
}
}
}
Model
public partial class Document
{
private int _iD;
public virtual int ID
{
get
{
return this._iD;
}
set
{
this._iD = value;
}
}
private string _fileType;
[Required(ErrorMessage = "Required!")]
public virtual string FileType
{
get
{
return this._fileType;
}
set
{
this._fileType = value;
}
}
}
Is there good reasons to use Viewmodel over Viewbag to populate DropDownListFors (found this from http://www.codeproject.com/Articles/687061/Multiple-Models-in-a-View-in-ASP-NET-MVC-MVC)?
Only problem with this implementation is that I can't validate file on client-side (so user couldn't post empty file).
EDIT: Got this working by just adding:
<input type="file" data-val="true" data-val-required="Please select a file!" name="file" />
Normally you have 3 different parts, which build a proper implementation of your scenario.
The ViewModel:
In a proper implementation, you got for every view its own Viewmodel. In your case that would be something like this:
public class CreateDocumentViewModel
{
[Required]
public IEnumerable<SelectListItem> filetypes { get; set; }
// Maybe some more attributes you need in the view?
}
Note: here you can use the DataAnnotations you want.
The View:
The view contains the actual data in the dropdown.
#model CreateDocumentViewModel
#using (Html.BeginForm("CreateDocument", "Create", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<label>Select file:</label><br />
<input type="file" name="file" /><br />
<label>Filetype</label><br />
#Html.DropDownListFor(model => model.filetypes, model.filetypes, "-Filetypes", new { #class = "filetype-cb" })
<br />
<input type="submit" value="Add"/>
}
The controller:
The controller you need, has 2 actions. A GET- and a POST-action.
[HttpGet]
public ActionResult CreateDocument()
{
CreateDocumentViewModel model = new CreateDocumentViewModel();
model.filetypes = FillFileTypesFromDB; // Here you fill the data for the dropdown!
return View(model);
}
Finally you need the POST-action which saves back to your db.
[HttpPost]
public ActionResult CreateDocument(HttpPostedFileBase file, string fileType)
{
if (file != null && file.ContentLength > 0)
{
Document doc = new Document()
{
Filetype = fileType
}
if (this.TryValidateModel(doc))
{
this.dbContext.Add(doc);
this.dbContext.SaveChanges();
id = doc.ID;
//save document using document ID
}
}
}
I hope I fully understood your problem and it helps solving it.
I have an MVC 3 project where I have 1 view LoginRegister which contains 2 views with forms for login and pre-register. The problem is after incorrectly completing the pre-register form and using PartialView("LoginRegister", loginRegisterViewModel) validation messages aren't displayed due the ModelState being lost. Before reading the next paragraph it's probably best to jump to the CODE.
Debugging the PartialView("LoginRegister", loginRegisterViewModel) and stepping into the PreRegister view to the following #Html.ErrorMessageFor(model => model.Email). Where the ModelState does not contain the Email key (see below) and hence doesn't display an error message.
private static MvcHtmlString ErrorMessageHelper(this HtmlHelper htmlHelper, ModelMetadata modelMetadata, string expression, string validationMessage, IDictionary<string, object> htmlAttributes)
{
string modelName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(expression);
if (!htmlHelper.ViewData.ModelState.ContainsKey(modelName)) // ModelState contains no keys, e.g. Email not found and a null is returned
{
return null;
}
// code continues here to render error message
}
CODE
ViewModels
LoginRegisterViewModel
namespace Site.ViewModels.Account
{
public class LoginRegisterViewModel
{
public bool IsDialog { get; set; }
public PreRegisterViewModel PreRegister { get; set; }
public QuickLoginViewModel QuickLogin { get; set; }
}
}
PreRegisterViewModel
using FluentValidation.Attributes;
using Site.ViewModels.Validators;
namespace Site.ViewModels.Account
{
[Validator(typeof(PreRegisterViewModelValidator))]
public class PreRegisterViewModel
{
public string Email { get; set; }
public string ConfirmEmail { get; set; }
}
}
Views
LoginRegister
#model Site.ViewModels.Account.LoginRegisterViewModel
#{
Layout = ViewBag.Layout;
}
<div id="loginReg" class="mod-loginReg">
<h2>Login and Registration</h2>
#{Html.RenderPartial("PreRegister", Model.PreRegister, new ViewDataDictionary());}
#{Html.RenderPartial("QuickLogin", Model.QuickLogin, new ViewDataDictionary());}
</div>
PreRegister
#using Site.Classes.MVC.Extensions;
#model Site.ViewModels.Account.PreRegisterViewModel
#using (Html.BeginForm("PreRegister", "Account", FormMethod.Post, new { #class = "form-errorContainer" }))
{
<div class="mod-loginReg-register">
<h3>Register</h3>
<p>Please enter your email address to register.<br /><br /></p>
<div class="mod-loginReg-form">
<div class="field-item">
#Html.LabelFor(model => model.Email)
#Html.TextBoxFor(model => model.Email, new { #maxlength = "255", #class = "Textbox required email" })
#Html.ErrorMessageFor(model => model.Email)
</div>
<div class="field-item">
#Html.LabelFor(model => model.ConfirmEmail)
#Html.TextBoxFor(model => model.ConfirmEmail, new { #maxlength = "255", #class = "Textbox required email" })
#Html.ErrorMessageFor(model => model.ConfirmEmail)
</div>
<div class="button-group">
#Html.Button("Register", "Register")
</div>
</div>
</div>
}
AccountController
[RequireHttps]
public ActionResult LoginRegister(int showDialog)
{
var loginRegisterViewModel = new LoginRegisterViewModel();
if (showDialog == 1)
{
ViewBag.Layout = "~/layouts/structure/dialog.cshtml";
loginRegisterViewModel.IsDialog = true;
}
else
{
ViewBag.Layout = "~/layouts/structure/2column.cshtml";
}
return PartialView(loginRegisterViewModel);
}
[RequireHttps]
public ActionResult PreRegister()
{
return PartialView();
}
[HttpPost]
[RequireHttps]
public ActionResult PreRegister(PreRegisterViewModel model)
{
if (ModelState.IsValid)
{
return PartialView("PreRegisterComplete");
}
var loginRegisterViewModel = new LoginRegisterViewModel { PreRegister = model, QuickLogin = new QuickLoginViewModel() };
return PartialView("LoginRegister", loginRegisterViewModel);
}
This is caused by the viewData being cleared out when new ViewDataDictionary() is called.
#{Html.RenderPartial("PreRegister", Model.PreRegister, new ViewDataDictionary());}
#{Html.RenderPartial("QuickLogin", Model.QuickLogin, new ViewDataDictionary());}
According to MSDN ViewDataDictionary is used for:
Represents a container that is used to pass data between a controller
and a view.
I assume you have a good reason for this otherwise you would not have taken the trouble to add the additional parameter.
If you change it to:
#Html.RenderPartial("PreRegister", Model.PreRegister)
#Html.RenderPartial("QuickLogin", Model.QuickLogin)
the ModelState built up in the controller should be available on your views.