Upload multiple files with parameters in ASP.NET Core - c#

My view model:
public class FileInfo
{
[Required]
[StringLength(50, ErrorMessage = "TitleErrorMessage", MinimumLength = 2)]
public string Title { get; set; }
[Required]
[StringLength(100, ErrorMessage = "DesErrorMessage", MinimumLength = 3)]
public string Description { get; set; }
[Required]
[DataType(DataType.Upload)]
public IFormFile File { get; set; }
}
The following is _UploadForm partial view file:
#model SessionStateTest.Models.FileInfo
<div class="form-group">
<label>Title</label>
<input class="form-control" asp-for="Title" />
</div>
<div class="form-group">
<label>Description</label>
<input class="form-control" asp-for="Description" />
</div>
<div class="form-group">
<label></label>
<input type="file" asp-for="File" />
</div>
That is used in another View with this code:
<form asp-action="AddUploadForm" asp-controller="Home" method="Post">
<input type="submit" value="Add another file" class="btn btn-sm btn-primary" />
</form>
<form asp-action="Upload" asp-controller="Home" method="Post" enctype="multipart/form-data">
#foreach (var item in Model.Upload)
{
#(await Html.PartialAsync("_UploadForm", item))
}
<div class="col-xs-12">
<input type="submit" value="Upload" class="btn btn-sm btn-info" />
</div>
</form>
Basically AddUploadForm action adds a new view model of type FileInfo to Model.Upload which is my main view model.
The problem is that the list List<FileInfo> vm in Upload action below is totally empty:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Upload(List<FileInfo> vm)
{
.... some other logic
return View();
}
I don't want to use multiple attribute because I would like to force user to provide a title and description for every file.
Any help is kindly appreciated!

Your approach with using _UploadForm generates the following html (let's focus on input's only since this is the most important part)
<input class="form-control" name="Title" />
<input class="form-control" name="Description" />
<input type="file" name="File" />
...
<input class="form-control" name="Title" />
<input class="form-control" name="Description" />
<input type="file" name="File" />
... and so on
So name attributes contains only FileInfo model's properties names without indexes and this is only suitable for the case when your controller expects single model
public IActionResult Upload(FileInfo vm)
And in order to make your html work with your current controller with list of models
public IActionResult Upload(List<FileInfo> vm)
It should look like this
<!-- version 1 -->
<input class="form-control" name="[0].Title" />
<input class="form-control" name="[0].Description" />
<input type="file" name="[0].File" />
...
<input class="form-control" name="[1].Title" />
<input class="form-control" name="[1].Description" />
<input type="file" name="[1].File" />
... and so on
Or
<!-- version 2 -->
<!-- the name before index must match parameter name in controller -->
<input class="form-control" name="vm[0].Title" />
<input class="form-control" name="vm[0].Description" />
<input type="file" name="vm[0].File" />
...
<input class="form-control" name="[1].Title" />
<input class="form-control" name="[1].Description" />
<input type="file" name="vm[1].File" />
... and so on
This is possible to accomplish using tag helpers and partial view in slightly different way. All you need to do is turn partial view's model to list and update asp-for expressions.
_UploadForm.cshtml
#model List<SessionStateTest.Models.FileInfo>
#for (int i = 0; i < Model.Count; i++)
{
<div class="form-group">
<label>Title</label>
<input class="form-control" asp-for="#Model[i].Title" />
</div>
<div class="form-group">
<label>Description</label>
<input class="form-control" asp-for="#Model[i].Description" />
</div>
<div class="form-group">
<label></label>
<input type="file" asp-for="#Model[i].File" />
</div>
}
View
<form asp-action="Upload" asp-controller="Home" method="Post" enctype="multipart/form-data">
#await Html.PartialAsync("_UploadForm", Model.Upload)
<div class="col-xs-12">
<input type="submit" value="Upload" class="btn btn-sm btn-info" />
</div>
</form>
It will generate html like in version 1.

Related

How do I pass an entire object using an HTML form?

I'll only include the relevant properties of the model and the related lines for simplicity:
Model: Department.cs
Property: public virtual Staff HOD { get; set; }
ControllerMethod:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Add([Bind] Department newDepartment)
{
await _departmentRepository.AddDepartmentAsync(newDepartment);
await _departmentRepository.SaveTransactionAsync(true);
return Redirect("Details" + "/" + newDepartment.Id);
}
View:
#model Department
#inject IStaffRepository staffRepository
#{
// select list
var listOfEmployees = new List<Staff>();
foreach (var staff in await staffRepository.GetAllStaffAsync())
{
listOfEmployees.Add(staff);
}
var selectListHOD = new SelectList(listOfEmployees, "EmployeeId", "Name"); //populates the option element with the correct Employee Id
}
<form class="form-group" method="post">
<div class="d-flex flex-column">
<div class="input-group" style="margin-bottom: 1%">
#Html.DropDownListFor(department => department.HOD, selectListHOD, htmlAttributes: new { #class="form-control" })
</div>
<div class="input-group">
<input class="btn btn-primary" type="submit" value="Save" />
</div>
</div>
</form>
Result:
(I have not included the other properties seen here in the question, but trust me, the properties of the parameter to the controller get correctly populated with them, except for the HOD property)
Source:
<form class="form-group" method="post">
<div class="d-flex flex-column">
<div class="input-group" style="margin-bottom: 1%">
<input class="form-control" id="Id" name="Id" placeholder="Id" type="text" value="" /> </div>
<div class="input-group" style="margin-bottom: 1%">
<input class="form-control" id="Name" name="Name" placeholder="Department" type="text" value="" /> </div>
<div class="input-group" style="margin-bottom: 1%">
<input class="form-control" id="Description" name="Description" placeholder="Description" type="text" value="" /> </div>
<div class="input-group" style="margin-bottom: 1%">
<select class="form-control" id="HOD" name="HOD">
<option value="EMP01">Employee 1</option>
<option value="EMP02">Employee 2</option>
</select>
</div>
<div class="input-group">
<input class="btn btn-primary" type="submit" value="Save" /> </div>
</div>
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8AIwSWkSO0hJqMxy-oQKoeG35LTDmI2N1XHDZL9qeaxRxc17TyZbT2z9Iq0GMPkRyE7HnaX1r4ZSIs0bQATYB7w_A_HZDBXGETMmpdSqlMXZCmf7cH9ECzrNGz0Wuu9zHkE50yI92vPY-GxNPG-pRhs" />
</form>
My guess is that the value being supplied to the controller seems to be wrong. How would I pass the actual object instead of the ID?
I tried using this instead of EmployeeId, but it throws Object Reference not found exception.
You cannot bind an object with <select></select>.You can try to bind Hod.EmployeeId with <select></select>.And add a hidden input to bind Hod.Name.When selected value changes,change the value of hidden input.Here is a demo:
<form class="form-group" method="post">
<div class="d-flex flex-column">
<div class="input-group" style="margin-bottom: 1%">
#Html.DropDownListFor(department => department.HOD.EmployeeId, selectListHOD, htmlAttributes: new { #class = "form-control",#onChange= "AddName()" })
<input hidden asp-for="HOD.Name" />
</div>
<div class="input-group">
<input class="btn btn-primary" type="submit" value="Save" />
</div>
</div>
</form>
#section scripts
{
<script>
$(function () {
AddName();
})
function AddName() {
$("#HOD_Name").val($("#HOD_EmployeeId option:selected").text());
}
</script>
}
result:

razor pages - how to populate form fields from update model

The 'itemId' is transferred to the Update Page, however, form fields are empty. This is my first try to Update the model in Razor Pages. Any help is appreciated.
[BindProperty]
public MenuItemViewModel MenuItem { get; set; }
public void OnGet(int itemId)
{
var the_item = _menuItemService.GetById(itemId);
}
public IActionResult OnPost()
{
if (ModelState.IsValid)
{
var the_item = MenuItem.ToDomainModel();
_menuItemService.Update(the_item);
}
return Page();
}
cshtml:
<div class="container">
<form asp-page="UpdateMenuItem" method="POST">
<div class="form-group">
<label asp-for="#Model.MenuItem.Name">Name:</label>
<input type="text" asp-for="#Model.MenuItem.Name" class="form-control" placeholder="Enter name" />
<span asp-validation-for="#Model.MenuItem.Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="#Model.MenuItem.ImageUrl">Image:</label>
<input type="text" asp-for="#Model.MenuItem.ImageUrl" class="form-control" placeholder="Enter image" />
<span asp-validation-for="#Model.MenuItem.ImageUrl" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="#Model.">Description:</label>
<input type="text" asp-for="#Model.MenuItem.Description" class="form-control" placeholder="Enter description" />
<span asp-validation-for="#Model.MenuItem.Description" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="#Model.MenuItem.Price">Price:</label>
<input type="text" asp-for="#Model.MenuItem.Price" class="form-control" placeholder="Enter price" />
<span asp-validation-for="#Model.MenuItem.Price" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-success">Update</button>
</form>
Try this:
public IActionResult OnGet(int itemId)
{
MenuItem = _menuItemService.GetById(itemId);
return Page();
}
It seems that you are using the 'asp-for' tag too many times and it creates an ambiguity when the Post method tries to find the model:
<label asp-for="#Model.MenuItem.Name">Name:</label>
<input type="text" asp-for="#Model.MenuItem.Name" class="form-control"
placeholder="Enter name" />
<span asp-validation-for="#Model.MenuItem.Name" class="text-danger"></span>
remove asp-for="#Model.MenuItem.Name" from the label and span. Only use it for the input fields like so:
<label>Name:</label>
<input type="text" asp-for="#Model.MenuItem.Name" class="form-control"
placeholder="Enter name" />
<span asp-validation-for="#Model.MenuItem.Name" class="text-danger"></span>
Only do this for the actual models you are binding. Do not use them for labels.
Let me know if that works out for you.

How to properly filter data using checkboxes?

I have a asp.net core project and I got a problem.
In the view, I have a code like this
<div class="check-box-inner mt-10">
<h4 class="title">Power (W) </h4>
<div class="filter-check-box">
<input name="power_cold" value="1000" type="checkbox" id="power_cold1">
<label for="power_cold1">Less 2000 </label>
</div>
<div class="filter-check-box">
<input name="power_cold" value="3000" type="checkbox" id="power_cold2">
<label for="power_cold2">2000-3000</label>
</div>
<div class="filter-check-box">
<input name="power_cold" value="4000" type="checkbox" id="power_cold3">
<label for="power_cold3">3000-4000</label>
</div>
<div class="filter-check-box">
<input name="power_cold" value="5000" type="checkbox" id="power_cold4">
<label for="power_cold4">4000-5000</label>
</div>
<div class="filter-check-box">
<input name="power_cold" value="30000" type="checkbox" id="power_cold5">
<label for="power_cold5">Больше 5000</label>
</div>
</div>
In the controller, I get it this way. And I am trying to filter the data using Linq.
But I don't know how I can apply a less or more condition to a list of values.How can this problem be solved? Maybe I should return data in a different format from the view?
public ActionResult Summary(List<int> power_cold)
{
ProductLis products = new ProductLis()
{
Products = repository.Products.Where(f=>f.Power<=(something)&&f.Power>=(something))
}
}
I would suggest using complex ViewModel for Controller Action to rich your goal:
<div class="check-box-inner mt-10">
<h4 class="title">Power (W) </h4>
<div class="filter-check-box">
<input name="LessThen2000" value="true" type="checkbox" id="power_cold1">
<label for="power_cold1">Less 2000 </label>
</div>
<div class="filter-check-box">
<input name="Between2000And3000" value="true" type="checkbox" id="power_cold2">
<label for="power_cold2">2000-3000</label>
</div>
<div class="filter-check-box">
<input name="Between3000And4000" value="true" type="checkbox" id="power_cold3">
<label for="power_cold3">3000-4000</label>
</div>
<div class="filter-check-box">
<input name="Between4000And5000" value="true" type="checkbox" id="power_cold4">
<label for="power_cold4">4000-5000</label>
</div>
<div class="filter-check-box">
<input name="MoreThen5000" value="true" type="checkbox" id="power_cold5">
<label for="power_cold5">Больше 5000</label>
</div>
</div>
And ViewModel which represents this structure:
public class GetSummaryInput{
public bool LessThen2000 {get;set;}
public bool Between2000And3000 {get;set;}
public bool Between3000And4000{get;set;}
public bool Between4000And5000 {get;set;}
public bool MoreThen5000 {get;set;}
}
Then inside action use it like this:
public ActionResult Summary(GetSummaryInput input)
{
IQueriable<Products> query = repository.Products;
if(input.LessThen2000){
query = query.Where(pr=> pr.Power <= 2000);
}
if(input.Between2000And3000){
query = query.Where(pr=> pr.Power >= 2000 && pr.Power <= 3000);
}
//other properties in the same way
}

Is is any way to show errors in the corresponding form?

I have an ASP.NET Core 3.1 Razor Pages application.
It is quite simple when it is only one form on a page, but how correctly show the errors when there are few forms?
Example:
/ManageUser.cshtml
<div>
<form method="post">
<div class="validation-summary-valid text-danger" asp-validation-summary="All"></div>
<div class="form-group">
<label asp-for="OldPassword"></label>
<input asp-for="OldPassword" class="form-control" />
</div>
<div class="form-group">
<label asp-for="NewPassword1"></label>
<input asp-for="NewPassword1" class="form-control" />
</div>
<div class="form-group">
<label asp-for="NewPassword2"></label>
<input asp-for="NewPassword2" class="form-control" />
</div>
<button type="submit" class="btn btn-primary" asp-page-handler="ChangePassword">Change password</button>
</form>
</div>
<!-- let's say it is the other "tab" -->
<div>
<form method="post">
<div class="validation-summary-valid text-danger" asp-validation-summary="All"></div>
<div class="form-group">
<label asp-for="ChangeEmail"></label>
<input asp-for="ChangeEmail" class="form-control" />
</div>
<button type="submit" class="btn btn-primary" asp-page-handler="ChangeEmail">Change email</button>
</form>
</div>
And, if on the backend side any error is found:
public async Task<IActionResult> OnPostChangeEmailAsync([FromService] UserManager<User> userManager)
{
//... skipped for brevity
ModelState.AddModelError("", "User not found or deleted");
return Page();
}
then this error is shown for both forms. Any way to show it for corresponding form only?
PS: Please do not propose to make the errors field-bound: it is not the case and the fields in the example is just for the simplicity.
Taking field validation as an example, you can try the following code.
View:
<div>
<form method="post">
<span class="text-danger">#Html.ValidationMessage("PasswordError")</span>
<div class="form-group">
<label asp-for="UserModel.OldPassword"></label>
<input asp-for="UserModel.OldPassword" class="form-control" />
<span asp-validation-for="UserModel.OldPassword" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="UserModel.NewPassword1"></label>
<input asp-for="UserModel.NewPassword1" class="form-control" />
<span asp-validation-for="UserModel.NewPassword1" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="UserModel.NewPassword2"></label>
<input asp-for="UserModel.NewPassword2" class="form-control" />
<span asp-validation-for="UserModel.NewPassword2" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary" asp-page-handler="ChangePassword">Change password</button>
</form>
</div>
<div>
<form method="post">
<span class="text-danger">#Html.ValidationMessage("EmailError")</span>
<div class="form-group">
<label asp-for="UserModel.ChangeEmail"></label>
<input asp-for="UserModel.ChangeEmail" class="form-control" />
<span asp-validation-for="UserModel.ChangeEmail" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary" asp-page-handler="ChangeEmail">Change email</button>
</form>
</div>
The backend side(you can remove the attributes):
public async Task<IActionResult> OnPostChangeEmailAsync(UserModel userModel)
{
if (!ModelState.IsValid)
{
ModelState.Remove("UserModel.OldPassword");
ModelState.Remove("UserModel.NewPassword1");
ModelState.Remove("UserModel.NewPassword2");
ModelState.AddModelError("EmailError", "User not found or deleted");
}
return Page();
}
public async Task<IActionResult> OnPostChangePassword(UserModel userModel)
{
if (!ModelState.IsValid)
{
ModelState.Remove("UserModel.ChangeEmail");
ModelState.AddModelError("PasswordError", "Password is inconsistent ");
}
return Page();
}
Result:

Get 400 error after submitting form C# MVC .NET Core

In my application, i want to submit my form and post the data into the method in my controller.
The controller looks like this:
public IActionResult Toevoegen()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Toevoegen(Customer customer)
{
if (ModelState.IsValid)
{
var cust = customer;
return RedirectToAction("Toevoegen");
}
else
return View();
}
The form looks like this:
#model Customer
<h2>Create</h2>
<form class="m-1 p-1" method="post">
<div class="form-group">
<label asp-for="CompanyName"></label>
<input asp-for="CompanyName" class="form-control" />
<span asp-validation-for="CompanyName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Email"></label>
<input asp-for="Email" type="text" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Tel"></label>
<input asp-for="Tel" type="text" class="form-control" />
<span asp-validation-for="Tel" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="City"></label>
<input asp-for="City" type="text" class="form-control" />
<span asp-validation-for="City" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
I've added #addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers in my _ViewImports.
When I go to the "toevoegen" page, I keep getting the 400 error.
Did some research and I found something about the validateantiforgerytoken.
Does someone know what i'm doing wrong?

Categories