Submitting two HTML forms with one submit button in Razor - c#

First of all sorry for my bad English. I am new to ASP.NET MVC and currently I am doing small "Rent-a-car" project on it.
I want to make a form where administrator of the page can add Cars on page with details like name, year of production and picture. Following some tutorials I made a form to create a car with name and year of production and I made separately a form where administrator can upload a picture of the car.
Now I have two HTML forms with two submit buttons, one for creating a car and second for uploading an image of the car. I want to combine those two forms into one where Administrator could type name of the car, year of production, select a picture which he want's to upload and then submitting all that with one button.
I have no idea how to do that so please review my code below and tell me what changes do I have to make, I will appreciate any help
This is my Car model:
public class Car
{
[Key]
public int CarID { get; set; }
public string Model { get; set; }
public int YearOfProduction { get; set; }
public string Price { get; set; }
public string Photo { get; set; }
public string AlternateText { get; set; }
[NotMapped]
public HttpPostedFileBase File { get; set; } //for image upload
}
This is my Create (car) Action Method in the "Cars" controller:
[Authorize(Roles = "Administrator")]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "CarID,Model,YearOfProduction")] Car car)
{
if (ModelState.IsValid)
{
db.Cars.Add(car);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(car);
}
This is my Upload (image of the car) Action Method in the "Cars" controller:
[HttpPost]
public ActionResult Upload(Car picture)
{
if (picture.File.ContentLength > 0)
{
var fileName = Path.GetFileName(picture.File.FileName);
var path = Path.Combine(Server.MapPath("~/Images/Cars"), fileName);
picture.File.SaveAs(path);
}
return RedirectToAction("Index");
}
This is my HTML form for creating a car:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Car</h4>
<hr/>
#Html.ValidationSummary(true, "", new {#class = "text-danger"})
<div class="form-group">
#Html.LabelFor(model => model.Model, htmlAttributes: new {#class = "control-label col-md-2"})
<div class="col-md-10">
#Html.EditorFor(model => model.Model, new {htmlAttributes = new {#class = "form-control"}})
#Html.ValidationMessageFor(model => model.Model, "", new {#class = "text-danger"})
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.YearOfProduction, htmlAttributes: new {#class = "control-label col-md-2"})
<div class="col-md-10">
#Html.EditorFor(model => model.YearOfProduction, new {htmlAttributes = new {#class = "form-control"}})
#Html.ValidationMessageFor(model => model.YearOfProduction, "", new {#class = "text-danger"})
</div>
</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>
</div>
}
This is my HTML form for uploading an image of the car:
#using (Html.BeginForm("Upload", "Cars", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<table>
<tr>
<td>File:</td>
<td><input type="file" name="File" id="File"/></td>
</tr>
<tr>
<td> </td>
<td><input type="submit" name="submit" value="Upload"/></td>
</tr>
</table>
}

You can use the command name in your <input> tag as shown below:
#Html.BeginForm()
{
#Html.AntiForgeryToken()
<!-- Your Razor code for the input form goes here -->
<!-- Now your Upload button -->
<table>
<tr>
<td>File:</td>
<td><input type="file" name="File" id="File"/></td>
</tr>
<tr>
<td> </td>
<td><input type="submit" name="commandName" value="Upload"/></td>
</tr>
</table>
<!-- Finally, your form submit button -->
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" name="commandName" value="Create" class="btn btn-default"/>
</div>
</div>
}
And in your controllers' action method accept it as a parameter.
public ActionResult Create(Car car, string commandName)
{
if(commandName.Equals("Create"))
{
// Call method to create new record...
}
else if (commandName.Equals("Upload"))
{
// Call another method to upload picture..
}
}
If you research, there is another elegant generic solution, where you can just apply a multiple-button attribute on the Action method and it will automatically call the controller action. However, you need to be careful about getting values in that case ( *It's out of scope for this question). You can refer: How do you handle multiple submit buttons in ASP.NET MVC Framework?

You can add the input file field in the other form and merge both action method's code into one.
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Car</h4>
<hr/>
#Html.ValidationSummary(true, "", new {#class = "text-danger"})
<div class="form-group">
#Html.LabelFor(model => model.Model, htmlAttributes: new {#class = "control-label col-md-2"})
<div class="col-md-10">
#Html.EditorFor(model => model.Model, new {htmlAttributes = new {#class = "form-control"}})
#Html.ValidationMessageFor(model => model.Model, "", new {#class = "text-danger"})
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.YearOfProduction, htmlAttributes: new {#class = "control-label col-md-2"})
<div class="col-md-10">
#Html.EditorFor(model => model.YearOfProduction, new {htmlAttributes = new {#class = "form-control"}})
#Html.ValidationMessageFor(model => model.YearOfProduction, "", new {#class = "text-danger"})
</div>
</div>
<div>
<label>File</label>
<input type="file" name="File" id="File"/>
</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>
</div>
}
and the action method, save the file to disk and store the filename in your db table records so that you can retrieve this file later (for view purposes).
[Authorize(Roles = "Administrator")]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Car car)
{
if (ModelState.IsValid)
{
if (car.File.ContentLength > 0)
{
var fileName = Path.GetFileName(car.File.FileName);
var path = Path.Combine(Server.MapPath("~/Images/Cars"), fileName);
picture.File.SaveAs(path);
car.Photo = fileName;
}
db.Cars.Add(car);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(car);
}
Also, you may consider generating a unique file name so that you won't overwrite the existing files on disk if user is updating the file with same name for different records.
You can use this code to generate a random file name.
var fileName = Path.GetFileName(car.File.FileName);
fileName = Path.GetFileNameWithoutExtension(fileName) +
"_" +
Guid.NewGuid().ToString().Substring(0, 4) + Path.GetExtension(fileName);
var path = Path.Combine(Server.MapPath("~/Images/Cars"), fileName);
//continue with saving
Also remember, the best way to prevent over posting is by using a view model with properties needed for your view. You can create a view model and use that to transfer data from view to action method. In that case, you can totally remove the property (decorated with [NotMapped] from your entity class)

Have a look at this.
It looks like it might help you combine your two forms (and controller actions) into one

Related

Cannot perform runtime binding on a null reference | Not Sure How The Reference Is Null

Context:
I'm trying to build a feature in my MVC Application where an Employer can take an Application submitted by a Student, and convert the information in the Application to create an Employee. However, I'm running into the Cannot perform runtime binding on a null reference error.
Controller:
public ActionResult Onboard (int id)
{
var application = db.Applications.
Include(a=>a.JobPosting.Employer).
Include(a=>a.Student)
.FirstOrDefault(a => a.ApplicationID == id);
return View(application);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Onboard([Bind(Include = "ApplicationID,JobPostingID,StudentID")] Application application)
{
{
if (ModelState.IsValid)
{
db.Employees.Add(new Employee()
{
Student = application.Student,
JobPosting = application.JobPosting,
Employer = application.JobPosting.Employer
});
db.SaveChanges();
return RedirectToAction("Details", "Employees");
}
}
return View(application);
}
View:
#model InTurn_Model.Application
#{
ViewBag.Title = "Onboard";
Layout = "~/Areas/Employers/Views/Shared/_Employers.cshtml";
}
<h2>Onboard</h2>
#using (Html.BeginForm("Onboard","Applications",FormMethod.Post,null))
{
#Html.AntiForgeryToken()
<input id="StudentID" type="hidden" value="#Model.StudentID" />
<input id="JobPostingID" type="hidden" value="#Model.JobPostingID" />
<input id="EmployerID" type="hidden" value="#Model.JobPosting.EmployerID" />
<div class="form-horizontal">
<h4>Employee</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.StudentID, "StudentID", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<span>#Model.Student.FirstName #Model.Student.LastName</span>
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.JobPostingID, "JobPostingID", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<span> #Model.JobPosting.Position</span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Hire" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
When I run a debug, the breakpoint on
return View(application)
stores the relevant information from the 1st Onboard controller.
However, the breakpoint on public ActionResult Onboard([Bind(Include = "ApplicationID,JobPostingID,StudentID")] Application application), the 'application' is storing values of 0 for ApplicationID, JobPostingID and StudentID. It's not null technically, but they aren't the correct values.
My question is, what am I doing wrong that the controllers are not passing the correct data? How do I fix this issue?
Is there a better way to achieve this?
I am incredibly new to MVC, and this is the first application that I'm building from the ground up. Any help is appreciated. Thank you!
Looks like I didn't need to use the [HTTPPost] or [ValidateAntiForgerty]. My thought was treating this like a create- but I guess because I'm not actually creating new data- rather transferring one piece of data to another- I don't need to use that.
Here's what I wound up doing:
public ActionResult NewHire(int id)
{
var application = db.Applications.Find(id);
db.Employees.Add(new Employee()
{
StudentID = application.Student.StudentID,
JobPostingID = application.JobPosting.JobPostingID,
EmployerID = application.JobPosting.Employer.EmployerID
});
db.SaveChanges();
return View();
}

Update specific fields using EF in asp.net MVC

I have an application that needs to be able to update data. There is a table called "HomePage" with multiple fields. For ease of use, I want the user to update just 2 fields in this table(Description1 and Description2). While trying to achieve this, it does update these 2 fields but the rest of the data in the other fields in the table are deleted. Please check below code
Controller.cs
// GET: HomePages/Edit/5
public ActionResult EditDescription(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
HomePage homePage = db.HomePages.Find(id);
if (homePage == null)
{
return HttpNotFound();
}
return View(homePage);
}
// POST: HomePages/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult EditDescription([Bind(Include = "ID,Description1,Description2,TestName1,TestName2,TestName3,TestComp1,TestComp2,TestComp3,TestDesc1,TestDesc2,TestDesc3,FooterAddress,FooterEmail,FooterTel,FooterFax")] HomePage homePage)
{
if (ModelState.IsValid)
{
db.Entry(homePage).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(homePage);
}
cshtml
#using (Html.BeginForm()) { #Html.AntiForgeryToken()
<div class="form-horizontal">
<hr /> #Html.ValidationSummary(true, "", new { #class = "text-danger" }) #Html.HiddenFor(model => model.ID)
<div class="form-group">
#Html.LabelFor(model => model.Description1, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextAreaFor(model => model.Description1, 5, 100, null) #Html.ValidationMessageFor(model => model.Description1, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Description2, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextAreaFor(model => model.Description2 , 8, 100, null) #Html.ValidationMessageFor(model => model.Description2, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" style="color:white;background-color:#ad301c" />
</div>
</div>
</div>
}
You should use the #Html.HiddenFor tag helper for the fields that you do not want to edit. This way the view will send the field data back to the POST method in your controller. For example: <input type="hidden" asp-for="TestName1" /> or #Html.HiddenFor(x => x.TestName1) will pass the TestName1 field to the post method in your controller.
You are binding all model and gets only 2 elements.
[Bind(Include = "ID,Description1,Description2,TestName1,TestName2,TestName3,TestComp1,TestComp2,TestComp3,TestDesc1,TestDesc2,TestDesc3,FooterAddress,FooterEmail,FooterTel,FooterFax")]
So try to do this without Bind option.

Upload Images while creating new Model

I'm going to create profile for my users in ASP.Net MVC application. Users creation controller is something like this:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(UserProfileViewModel userViewModel)
{
if (ModelState.IsValid)
{
....
}
return View(userViewModel);
}
Besides, each user model got several properties including one photo. Everything goes well till I want to add an input field to accept photo.
#Html.TextBoxFor(model => model.ImageUrl, new { type = "file" })
And I add below field to UserProfileViewModel:
[Display(Name = "Profile Photo")]
public HttpPostedFileBase ImageUrl { get; set; }
Among snippets to upload a photo and answers to my previous question, it seems uploading photo was considered as a separate task. i.e. I need an individual form and controller to upload a photo (like first part of this answer).
I want to know are there any methods that I can create whole user profile in one form and pass its photo to the same controller (which included photo in UserProfileViewModel)?
I need to note I don't know to use jQuery or AJAX and I want to use standard HTML helpers for this task.
Update: My View Looks like this:
#model ProjectManager.Models.UserProfileViewModel
#{
ViewBag.Title = "Create";
}
<h2>#ViewBag.Title</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>User Profile</h4>
<hr />
#Html.ValidationSummary(true)
<div class="form-group">
#Html.LabelFor(model => model.Description, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Description)
#Html.ValidationMessageFor(model => model.Description)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Name, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Age, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Age)
#Html.ValidationMessageFor(model => model.Age)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ImageUrl, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextBoxFor(model => model.ImageUrl, new { type = "file" })
#Html.ValidationMessageFor(model => model.ImageUrl)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="ثبت" class="btn btn-default" />
</div>
</div>
</div>
}
<div class="rtl text-right">
#Html.ActionLink("Back To List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
File inputs are not sent in the request unless your form element contains the enctype = "multipart/form-data" attribute. Change the view code to
#using (Html.BeginForm("Create", "User", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
....
}
All i have got, your question is I want to know are there any methods that I can create whole user profile in one form and pass its photo to the same controller (which included photo in UserProfileViewModel)?
Yes. It is possible. If you overwrite the form as Stephen Muecke said, you should get the photo with viewmodel. If you get null in viewmodel, you can retrieve the file(photo) from the request also.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(UserProfileViewModel userViewModel)
{
if (ModelState.IsValid)
{
HttpPostedFileBase fileUploadObj= Request.Files[0];
//for collection
HttpFileCollectionBase fileUploadObj= Request.Files;
....
}
return View(userViewModel);
}
Hope this helps :)
You need to use an BeginForm() that allows you to add htmlAttributes, and because you need to add
new {enctype = "multipart/form-data" }
#using (Html.BeginForm("UserProfileViewModel ", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
Controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult UserProfileViewModel(UserProfileViewModel userViewModel)
{
if (ModelState.IsValid)
{
HttpPostedFileBase fileUpload= Request.Files[0];
//for collection
HttpFileCollectionBase fileUpload= Request.Files;
....
}

Render view with modified Model

I´m new in MVC, and I´m working in a simple FORM, but,I have a problem,
for some reason I want to change the bound model in the controller operation and
render the view, but this not happen.
For example:
public class Product{
int id {get; set;}
string description {get; set;}
}
and the controller method:
[POST]
public ActionResult Edite(Product p){
p.description = "HELLOOOOOO!!!"
return View(P);
}
the view:
#model WebSite.Models.Product
#{
ViewBag.Title = "Modificar (Product)";
}
<h2>Modificar (Product)</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4></h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.Id)
<div class="form-group">
#Html.LabelFor(model => model.description, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.description, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.description, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Guardar" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Volver a la Lista", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
but in the rendered view, the description of the product is not "HELLOOOOO!!!!".
Why MVC render the view with the user entered values and not with the new model I want?
maybe isn't the best way to resolve the problem, but this post solved my problem:
Asp.net MVC ModelState.Clear

How to pass input value from the form which is not part of any model?

In razor view I have a form:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Person</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.Id)
<div class="form-group">
#Html.LabelFor(model => model.FirstName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.FirstName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.FirstName, "", new { #class = "text-danger" })
</div>
</div>
etc. for long time,but I want to pass values from these below to controller:
<div class="form-group">
<input class="form-control text-box single-line valid" id="FetchDate" name="FetchDate" type="date">
<input class="form-control text-box single-line valid" id="FetchTime" name="FetchTime" type="time">
</div>
<div class="form-group">
<div class="col-md-offset-0 col-md-10">
<input type="submit" value="Save" class="btn btn-default" style="width: 200px; font-weight: bold;" />
</div>
</div>
</div>
}
Submit(type="submit) button invokes POST edit action in Person controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "Id,FirstName")] Person person) {
if (ModelState.IsValid) {
db.Entry(person).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(person);
}
We can see that the only parameter is Person person, but inside the form I added two fields which have nothing in common with a Person but I also would like to pass values from them to POST Edit by submit button:
<div class="form-group">
<label class="control-label col-md-2" >If you want this client to be fetched to you later, fullfil the data.</label>
<input class="form-control text-box single-line valid" id="FetchDate" name="FetchDate" type="date">
<input class="form-control text-box single-line valid" id="FetchTime" name="FetchTime" type="time">
</div>
Question: How to pass values to controller's POST Edit action which are not part of any model from my Razor view(above)?
I tried this and time is null or empty string(didn't check yet):
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "Id,FirstName")] Person person, [Bind(Include="FetchTime")] String time) {
Debug.WriteLine("Time " + time);
if (ModelState.IsValid) {
db.Entry(person).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(person);
}
you can do this:
public ActionResult Edit([Bind(Include = "Id,FirstName")] Person person, string FetchDate, string FetchTime) {
if (ModelState.IsValid) {
db.Entry(person).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(person);
}

Categories