Create function posting multiple models - c#

I am working on a .NET Core 2.0 application and I have a single view with many instances of two different models, Component and Value. I want to create and post all instances of these models to the database, but I am having trouble posting them all. Currently only the first instance (row) of the two models are posted to the Db.
So my question: Is it possible to do such a thing? And if so is the case, how?
Below is a couple of samples from my code.
My ViewModel:
public class SheetData
{
public int ID { get; set; }
public Sheet Sheet { get; set; }
public Component Components { get; set; }
public Values Values { get; set; }
}
Razorview: (Only a small sample of the whole view)
<div class="row">
<div class="form-group">
<div class="col-md-4">
<span>
<label asp-for="Components.Name" class="control-label"></label>
<input asp-for="Components.Name" class="form-control" value="UpperAnnular" readonly />
</span>
</div>
<div class="col-md-1">
<span>
<label asp-for="Values.OpenTime" class="control-label"></label>
<input asp-for="Values.OpenTime" class="form-control" />
</span>
</div>
<div class="col-md-1">
<span>
<label asp-for="Values.OpenGal" class="control-label"></label>
<input asp-for="Values.OpenGal" class="form-control" />
</span>
</div>
<div class="col-md-1">
<span>
<label asp-for="Values.CloseTime" class="control-label"></label>
<input asp-for="Values.CloseTime" class="form-control" />
</span>
</div>
<div class="col-md-1">
<span>
<label asp-for="Values.CloseGal" class="control-label"></label>
<input asp-for="Values.CloseGal" class="form-control" />
</span>
</div>
</div>
</div>
<div class="row">
<div class="form-group">
<div class="col-md-4">
<span>
<label asp-for="Components.Name" class="control-label"></label>
<input asp-for="Components.Name" class="form-control" value="LowerAnnular" readonly />
</span>
</div>
<div class="col-md-1">
<span>
<label asp-for="Values.OpenTime" class="control-label"></label>
<input asp-for="Values.OpenTime" class="form-control" />
</span>
</div>
<div class="col-md-1">
<span>
<label asp-for="Values.OpenGal" class="control-label"></label>
<input asp-for="Values.OpenGal" class="form-control" />
</span>
</div>
<div class="col-md-1">
<span>
<label asp-for="Values.CloseTime" class="control-label"></label>
<input asp-for="Values.CloseTime" class="form-control" />
</span>
</div>
<div class="col-md-1">
<span>
<label asp-for="Values.CloseGal" class="control-label"></label>
<input asp-for="Values.CloseGal" class="form-control" />
</span>
</div>
</div>
</div>
Controller:
// GET:
public IActionResult Sheet ()
{
var model = new SheetData();
model.Sheet = new Sheet();
model.Components = new Component();
model.Values = new Values();
//model.Events = new Event();
return View(model);
}
// POST: Sheet/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Sheet(SheetData viewModel)
{
Sheet sheet = viewModel.Sheet;
Component component = viewModel.Components;
Values values = viewModel.Values;
if (ModelState.IsValid)
{
//values.ComponentID = component.ComponentID;
_context.Add(sheet);
//await _context.SaveChangesAsync();
_context.Add(values);
//await _context.SaveChangesAsync();
component.SheetID = sheet.ID;
component.ValuesID = values.ID;
_context.Add(component);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return RedirectToAction(nameof(Index));
}

You Values property must be a collection (eg. a List).
Then on your Razor page you can point to each element by its index:
<span>
<label asp-for="Values[0].OpenTime" class="control-label"></label>
<input asp-for="Values[0].OpenTime" class="form-control" />
</span>

Maybe I'm just lazy, but I find it cleaner to use the database directly than to make more viewmodels.
I usually add the models to the "main" model like a virtual, then in the repository, I just include it: return: db.Components.Include(C => C.Values)
This allows me to use the other model just fine in the view, just that I'd have to add .Values whenever I want to add it.

Related

Asp.net core web app - pass id and model to edit action

I am relatively new to asp.net mvc. My project is using clean architecture with repository and UnitOfWork patterns (I know repository pattern is largely debated these days, and that is no concern for this question). It has an asp.net core web api project and a separate MVC core project.
In the api, I have an updateModel action that expects an id to be passed in the URL, and the model to be passed in the body:
[HttpPut("update-book/{id}")]
public IActionResult UpdateBook(int id, [FromBody] ComicBookViewModel book)
{
try
{
var b = _unitOfWork.ComicBooks.GetBookById(id);
if (b != null)
{
_unitOfWork.ComicBooks.UpdateBook(id, book);
return Accepted(book);
}
else
{
return NotFound($"Book with id {id} not found");
}
}
catch (Exception ex)
{
return StatusCode(StatusCodes.Status500InternalServerError, $"{ex.Message}");
}
}
In the MVC app, I have a page that calls the API and displays a list of entities from the database. Each entity has an edit button. The edit button calls this action in the controller:
[HttpGet]
public async Task<IActionResult> EditComic(int id)
{
ComicBookWithAuthorsAndCharactersViewModel? model = null;
string uri = $"https://localhost:5001/api/ComicBook/get-book-by-id/{id}";
HttpClient client = _httpClientFactory.CreateClient(
name: "ComicbookInventory.Api");
var request = new HttpRequestMessage(HttpMethod.Get, uri);
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
model = await response.Content.ReadFromJsonAsync<ComicBookWithAuthorsAndCharactersViewModel>();
}
return View(model);
}
and then displays a simple edit page:
#model ComicBookInventory.Shared.ComicBookWithAuthorsAndCharactersViewModel
#{
ViewData["Title"] = "EditComic";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h1>Edit #Model?.Title</h1>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="EditComic">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Id" class="control-label"></label>
<input asp-for="Id" class="form-control" />
<span asp-validation-for="Id" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Title" class="control-label"></label>
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Description" class="control-label"></label>
<input asp-for="Description" class="form-control" />
<span asp-validation-for="Description" class="text-danger"></span>
</div>
<div class="form-group form-check">
<label class="form-check-label">
<input class="form-check-input" asp-for="IsRead" /> #Html.DisplayNameFor(model => model.IsRead)
</label>
</div>
<div class="form-group">
<label asp-for="DateRead" class="control-label"></label>
<input asp-for="DateRead" class="form-control" />
<span asp-validation-for="DateRead" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Rating" class="control-label"></label>
<input asp-for="Rating" class="form-control" />
<span asp-validation-for="Rating" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Genre" class="control-label"></label>
<input asp-for="Genre" class="form-control" />
<span asp-validation-for="Genre" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="CoverUrl" class="control-label"></label>
<input asp-for="CoverUrl" class="form-control" />
<span asp-validation-for="CoverUrl" class="text-danger"></span>
</div>
<div class="form-group">
#*<input type="hidden" name="Id" value="#Model.Id" />*#
<input type="submit" value="Save" class="btn btn-primary"/>
</div>
</form>
</div>
</div>
<div>
<a asp-action="GetAllComics">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
The submit button at the bottom of the edit page then calls this action in the controller:
[HttpPost]
public ActionResult EditComic(ComicBookWithAuthorsAndCharactersViewModel model)
{
var userId = Request.HttpContext.Request;
string uri = $"https://localhost:5001/api/ComicBook/update-book/{id}";
HttpClient client = _httpClientFactory.CreateClient(
name: "ComicbookInventory.Api");
var put = client.PutAsJsonAsync<ComicBookWithAuthorsAndCharactersViewModel>(uri, model);
put.Wait();
var result = put.Result;
if (result.IsSuccessStatusCode)
{
return RedirectToAction("GetAllComics");
}
return View(model);
}
My question is, when this last action is executed, I need to pass the id and model back to the api. How can I accomplish this?
I'm not necessarily looking for answers, just for someone to point me in the right direction. As I said, I'm new to asp.net core MVC apps.. just need some direction.
I don't get any compile-time errors, but if I set a breakpoint in the
EditComic(ComicBookWithAuthorsAndCharactersViewModel model) method below, the result is set to http 400 on this line:
var result = put.Result is set to Http 400.
My code is here: https://github.com/rnemeth90/ComicBookInventoryApp
I'm working on the main branch currently.
I figured it out just now. I had to mark some properties as nullable in my view model and then re-run the EF core migration to update the db schema. I am not passing these values back to the api from the application in this view (and therefore not passing them to EF core). So that is why I was getting the 400. CharacterIds and AuthorIds are now nullable in this class. Seems to work. Need to test it some more.

Asp.net core Razor Radio button not passing desired values to controller

I have a problem with my form its not passing radio button selected value to controller but another values are passed successfully when debugging.
Please note Job.PublicSubmissionReviewed its boolean, I noticed the this Job.PublicSubmissionReviewed passed through the form at all.
What am I missing on the radio button:
Here is my form and controller, your assistance will be greatly appreciated:
<form asp-action="Review">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Job.Id" />
<div class="form-group">
<p><strong>Approve or Reject public submission</strong></p>
<div class="form-group form-check-inline">
<label class="form-check-label">
<input class="form-radio-input" name="status" value="True" type="radio" asp-for="Job.PublicSubmissionReviewed" /> Approve
</label>
</div>
<div class="form-group form-check-inline">
<label class="form-check-label">
<input class="form-check-input" name="status" value="False" type="radio" asp-for="Job.PublicSubmissionReviewed" /> Reject
</label>
</div>
</div>
<div class="form-row showapprove" id="True">
<div class="form-group col-md-6">
<label asp-for="Job.JobCategoryId" class="control-label"></label>
<select asp-for="Job.JobCategoryId" class="form-control" asp-items="ViewBag.JobCategoryId">
</select>
</div>
<div class="form-group col-md-6">
<label asp-for="Job.Name" class="control-label"></label>
<input asp-for="Job.Name" class="form-control" />
<span asp-validation-for="Job.Name" class="text-danger"></span>
</div>
<div class="form-group col-md-6">
<label asp-for="Job.JobNo" class="control-label"></label>
<input asp-for="Job.JobNo" class="form-control" />
<span asp-validation-for="Job.JobNo" class="text-danger"></span>
</div>
<div class="form-group col-md-6">
<label asp-for="Job.ContractorId" class="control-label"></label>
<select asp-for="Job.ContractorId" class="form-control" asp-items="ViewBag.ContractorId">
</select>
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-outline-primary"><i class="fas fa-save" aria-hidden="true"></i> Submit</button>
</div>
</form>
public async Task<IActionResult> Review(int id, JobViewModel model)
{
if (id != model.Job.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
var job = _context.Jobs.Where(j => j.Id == id).SingleOrDefault();
if (model.Job.PublicSubmissionReviewed == true) // approve public submission
{
job.PublicSubmissionReviewed = true;
job.PublicSubmissionReviewDate = DateTime.Now;
job.Name = model.Job.Name;
job.New = true;
job.JobNo = model.Job.JobNo;
job.JobCategoryId = model.Job.JobCategoryId;
job.ContractorId = model.Job.ContractorId;
_context.Update(job);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Public));
}
else if (model.Job.PublicSubmissionReviewed == false) // reject public submission
{
job.PublicSubmissionReviewed = true;
job.PublicSubmissionReviewDate = DateTime.Now;
job.New = false;
job.PublicSubmissionRejected = true;
job.PublicSubmissionRejectedDate = DateTime.Now;
job.PublicSubmissionReviewRejectReason = model.Job.PublicSubmissionReviewRejectReason;
_context.Update(job);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Public));
}
}
catch (DbUpdateConcurrencyException)
{
if (!JobExists(model.Job.Id))
{
return NotFound();
}
else
{
throw;
}
}
}
return View(model);
}
The name HTML attribute in a form element is what will be sent to the server to identify that field once you submit the form. Razor's model binding expects a certain name for form fields, and asp-for automatically sets that name for you. Since you've manually specified a name instead, the model binder will not be able to recognize the field, and therefore will not set it in your model.
It looks like you're setting the name in order to group the radio buttons together. Since asp-for will use the same name for a given bound property, you do not need to worry about that; all radio buttons corresponding to a certain property will be grouped together.
Therefore, you can simply remove the name attribute and your code should then work fine:
<input class="form-radio-input" value="True" type="radio" asp-for="Job.PublicSubmissionReviewed" /> Approve
<input class="form-check-input" value="False" type="radio" asp-for="Job.PublicSubmissionReviewed" /> Reject

How can I execute an edit method for model?

I wanted to know how to modify or delete (Edit async or delete) 1 or 2 models belonging to the same view
Like this example we have
public class FruitsViewModel
{
public List<Abricot> Abricots { get; set; }
public List<Banane> Bananes { get; set; }
public List<Citron> Citrons { get; set; }
}
#model namespace de FruitsViewModel
#foreach(var abricot in Model.Abricots)
{
// Affiche les propriétés d'un abricot
}
#foreach(var banane in Model.Bananes)
{
// Affiche les propriétés d'une banane
}
#foreach(var citron in Model.Citrons)
{
// Affiche les propriétés d'un citron
}
public ActionResult ActionController()
{
var vm = new FruitsViewModel();
vm.Abricots = // Récupère les abricots depuis la base de données
vm.Bananes = // Récupère les bananes depuis la base de données
vm.Citrons = // Récupère les citrons depuis la base de données
return View(vm);
}
i do like your code,there is no exception,
if I click on the save button normally the banae model will be changed and I will write to index which displays this update
`enter code here` `#foreach (var banane in Model.personnels)
{
<form asp-action="EditBanane" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="#banane.Id" class="control-label"></label>
<input asp-for="#banane.Id" class="form-control" />
<span asp-validation-for="#banane.Id" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="#banane.Name" class="control-label"></label>
<input asp-for="#banane.Name" class="form-control" />
<span asp-validation-for="#banane.Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="#banane.Color" class="control-label"></label>
<input asp-for="#banane.Color" class="form-control" />
<span asp-validation-for="#banane.Color" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="#banane.BananeP" class="control-label"></label>
<input asp-for="#banane.BananeP" class="form-control" />
<span asp-validation-for="#banane.BananeP" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
----action Edit
public ActionResult EditBanane(int id, [Bind("id,Name,Color,BananeP")] Banane banane)
{
_context.Update(banane);
_context.SaveChangesAsync();
return RedirectToAction("Index");
how I can do an Edit in method public async Task Edit(int id, [Bind("id,..,")] ....)on the banana or lemon model
If you'd like to edit/update model item of banana or lemon etc, you can try these approaches.
Approach 1: put input fields of model item in a form and specify the action method for form submission, like below.
#foreach (var fruit in Model.Abricots)
{
// Affiche les propriétés d'un abricot
//...
}
#foreach (var banane in Model.Bananes)
{
// Affiche les propriétés d'une banane
<form asp-action="EditBanane" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="#banane.Id" class="control-label"></label>
<input asp-for="#banane.Id" class="form-control" />
<span asp-validation-for="#banane.Id" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="#banane.Name" class="control-label"></label>
<input asp-for="#banane.Name" class="form-control" />
<span asp-validation-for="#banane.Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="#banane.Color" class="control-label"></label>
<input asp-for="#banane.Color" class="form-control" />
<span asp-validation-for="#banane.Color" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="#banane.BananeP" class="control-label"></label>
<input asp-for="#banane.BananeP" class="form-control" />
<span asp-validation-for="#banane.BananeP" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
}
#foreach (var fruit in Model.Citrons)
{
// Affiche les propriétés d'un citron
//...
}
Action method
[HttpPost]
public IActionResult EditBanane(Banane banane)
{
//...
Approach 2: generate expected data and make request using Fetch API or Ajax etc to post data to corresponding action method with JavaScript code.
I do like your code,and thers is no exception and no update of the model in the page Index (principal page)
#foreach (var banane in Model.Bananes)
{
<form asp-action="EditBanane" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="#banane.Id" class="control-label"></label>
<input asp-for="#banane.Id" class="form-control" />
<span asp-validation-for="#banane.Id" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="#banane.Name" class="control-label"></label>
<input asp-for="#banane.Name" class="form-control" />
<span asp-validation-for="#banane.Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="#banane.Color" class="control-label"></label>
<input asp-for="#banane.Color" class="form-control" />
<span asp-validation-for="#banane.Color" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="#banane.BananeP" class="control-label"></label>
<input asp-for="#banane.BananeP" class="form-control" />
<span asp-validation-for="#banane.BananeP" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
----action Edit
public ActionResult EditBanane(int id, [Bind("id,Name,Color,BananeP")] Banane banane)
{
_context.Update(banane);
_context.SaveChangesAsync();
return RedirectToAction("Index");
and thanks a lot for your response,
I do like that but unfortunately it does not work yet the update has not done
#foreach (var item in Model.bananes)
{
<div class="col-md-4">
<form asp-action="ViewEdit" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" value=#Html.DisplayFor(modelItem => item.id)>
<div class="form-group">
<label asp-for="#item.Name" class="control-label"></label>
<input class="form-control" value="#Html.DisplayFor(modelItem => item.Name)">
</div>
<div class="form-group">
<label asp-for="#item.Color" class="control-label"></label>
<input class="form-control" value="#Html.DisplayFor(modelItem => item.Color)">
</div>
<div class="form-group">
<label asp-for="#item.BananeP" class="control-label"></label>
<input class="form-control" value="#Html.DisplayFor(modelItem => item.BananeP)">
</div>
<div class="form-group">
<input type="submit" value="save" class="btn btn-info text-white " />
</div>
</form>
</div>}
public IActionResult ViewEdit( [Bind("id,Name,Color,BananeP ")] Banane item)
{
_context.Update(item);
_context.SaveChangesAsync();
return RedirectToAction("Index");}

Binding generic value back to controller via post method

I can't figure out how to bind back generic value from view when sending it back to controller via post method.
I have generic class PageModel that inherites from GenericModel. PageModel has additional parameter Page (class that have two int parameters, pageSize and pageNumber). GenericModel has only generic value of T named viewModel and nothing more. The problem is that standard C# binder binds Page parameter without any issues, but no matter what I'm trying to do ViewModel is never filled in.
Models:
public class GenericModel<T>
{
public T ViewModel { get; set; }
public GenericModel(T viewModel)
{
ViewModel = viewModel;
}
public GenericModel()
{
}
}
public class PageModel<T> : GenericModel<T>
{
public Page Page { get; set; }
public PageModel(T viewModel, Page page) : base(viewModel)
{
Page = page;
}
public PageModel() : base()
{
}
}
Generic Parameter:
public class UserModel
{
public string Id { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string ConfirmPassword { get; set; }
public IList<string> Roles { get; set; }
public IList<string> NewRoles { get; set; }
}
Controller method
[HttpPost]
public async Task<IActionResult> Update(PageModel<UserModel> model)
{
var isUpdated = await _userServices.Update(model.ViewModel);
ViewData["IsUpdated"] = isUpdated;
return View("Index", await _userServices.GetUsersPage(model.Page.PageNumber, model.Page.PageSize));
}
View:
#model PageModel<UserModel>
#{
ViewData["Title"] = "Update";
}
<h1>Update</h1>
<form asp-controller="Admin" asp-action="Update" method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="ViewModel.Id"></label>
<input type="text" asp-for="ViewModel.Id" disabled="disabled" class="form-control" />
<span asp-validation-for="ViewModel.Id" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ViewModel.Password"></label>
<input type="password" asp-for="ViewModel.Password" class="form-control" />
<span asp-validation-for="ViewModel.Password" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ViewModel.ConfirmPassword"></label>
<input type="password" asp-for="ViewModel.ConfirmPassword" class="form-control" />
<span asp-validation-for="ViewModel.ConfirmPassword" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ViewModel.Roles"></label>
<input type="text" asp-for="ViewModel.Roles" class="form-control" hidden="hidden" />
<span asp-validation-for="ViewModel.Roles" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Page.PageNumber"></label>
<input type="number" asp-for="Page.PageNumber" class="form-control" hidden="hidden" />
<span asp-validation-for="Page.PageNumber" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Page.PageSize"></label>
<input type="number" asp-for="Page.PageSize" class="form-control" hidden="hidden" />
<span asp-validation-for="Page.PageSize" class="text-danger"></span>
</div>
#foreach (var availableRole in (IList<IdentityRole>)ViewData["AllRoles"])
{
<div class="form-check">
<input class="form-check-input" type="checkbox" name="newRoles" value="#availableRole.Name"
checked="#Model.ViewModel.Roles.Contains(availableRole.Name)">
<label class="form-check-label">#availableRole</label>
</div>
}
<button type="submit" class="btn btn-success">Change</button>
Do you know what can be the issue? Is naming convention wrong? I've already tried to find answer on stack, but with no luck.
**
EDIT 1:
**
This is what is generated by ASP.NET (Copied from google chrome)
<form method="post" action="/Admin/Update/439c61f5-d721-4e10-b49c-6263b7929c6a">
<div class="text-danger validation-summary-valid" data-valmsg-summary="true"><ul><li style="display:none"></li>
</ul></div>
<div class="form-group">
<label asp-for="ViewModel.Id"></label>
<input type="text" asp-for="ViewModel.Id" disabled="disabled" class="form-control" />
</div>
<div class="form-group">
<label for="ViewModel_Password">Password</label>
<input type="password" class="form-control" data-val="true" data-val-length="The Password must be at least 6 and at max 100 characters long." data-val-length-max="100" data-val-length-min="6" id="ViewModel_Password" name="ViewModel.Password">
<span class="text-danger field-validation-valid" data-valmsg-for="ViewModel.Password" data-valmsg-replace="true"></span>
</div>
<div class="form-group">
<label for="ViewModel_ConfirmPassword">Confirm password</label>
<input type="password" class="form-control" data-val="true" data-val-equalto="The password and confirmation password do not match." data-val-equalto-other="*.Password" id="ViewModel_ConfirmPassword" name="ViewModel.ConfirmPassword">
<span class="text-danger field-validation-valid" data-valmsg-for="ViewModel.ConfirmPassword" data-valmsg-replace="true"></span>
</div>
<div class="form-group">
<label for="ViewModel_Roles">Roles</label>
<input type="text" class="form-control" hidden="hidden" id="ViewModel_Roles" name="ViewModel.Roles" value="System.Collections.Generic.List`1[System.String]">
<span class="text-danger field-validation-valid" data-valmsg-for="ViewModel.Roles" data-valmsg-replace="true"></span>
</div>
<div class="form-group">
<label for="Page_PageNumber">PageNumber</label>
<input type="number" class="form-control" hidden="hidden" data-val="true" data-val-required="The PageNumber field is required." id="Page_PageNumber" name="Page.PageNumber" value="1">
<span class="text-danger field-validation-valid" data-valmsg-for="Page.PageNumber" data-valmsg-replace="true"></span>
</div>
<div class="form-group">
<label for="Page_PageSize">PageSize</label>
<input type="number" class="form-control" hidden="hidden" data-val="true" data-val-required="The PageSize field is required." id="Page_PageSize" name="Page.PageSize" value="5">
<span class="text-danger field-validation-valid" data-valmsg-for="Page.PageSize" data-valmsg-replace="true"></span>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="newRoles" value="Administrator">
<label class="form-check-label">Administrator</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="newRoles" value="User" checked="checked">
<label class="form-check-label">User</label>
</div>
<button type="submit" class="btn btn-success">Change</button>
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8Hsh_QOxexZLjyWnCjnNzz2ZhIuA-aPvi2MKBsCF12vrc6FY2yJnWGo1Nq9xNtwuiq7dTKdt93hUTJUf0PDD8RX1CJGgo0Jc3eDJMf8Ymh13fz0K60_S-uajXL3lNZalJJ58idjvok61tqa6oddepoWN1A0BUYHsk-h-PdfUCswqBSrt9GsHdbRvS5NrIitiZQ"></form>
Form data from google dev tools:
ViewModel.Password:
ViewModel.ConfirmPassword:
ViewModel.Roles: System.Collections.Generic.List%601%5BSystem.String%5D
Page.PageNumber: 1
Page.PageSize: 5
newRoles: User
__RequestVerificationToken: CfDJ8Hsh_QOxexZLjyWnCjnNzz2AyLo8vWEn4NIFS4brVjbJsYdFKgY97yj5IO8trooYDKkrdxPlFrGk2hmPQrmoEKgmGSno2jdOwGud-5Fcy8ewmp7K2pl1XODyKXxYSaZ3TngHSl1KLpMb9REpq4_Nqh45sdO1eBaMqpw3dfgCoAbvc6m2nEKJlDaRm_2eaKTX-w
Decoded form (the same as above):
ViewModel.Password=&ViewModel.ConfirmPassword=&ViewModel.Roles=System.Collections.Generic.List%601%5BSystem.String%5D&Page.PageNumber=1&Page.PageSize=5&newRoles=User&__RequestVerificationToken=CfDJ8Hsh_QOxexZLjyWnCjnNzz2AyLo8vWEn4NIFS4brVjbJsYdFKgY97yj5IO8trooYDKkrdxPlFrGk2hmPQrmoEKgmGSno2jdOwGud-5Fcy8ewmp7K2pl1XODyKXxYSaZ3TngHSl1KLpMb9REpq4_Nqh45sdO1eBaMqpw3dfgCoAbvc6m2nEKJlDaRm_2eaKTX-w
EDIT 2
I've changed the disable tag from id to readonly, and now ID is being sent. I don't thing I've changed anything else but I think thanks to that every single other property is added, but IList with roles. There still is a problem. Roles don't bind at all.
ViewModel.Id: fde0b182-3b24-4d5b-b31e-3680b7cfd4de
ViewModel.Password: 19923845379
ViewModel.ConfirmPassword: 1284326898838
ViewModel.Roles: System.Collections.Generic.List`1[System.String]
Page.PageNumber: 1
Page.PageSize: 5
newRoles: Administrator
newRoles: User
__RequestVerificationToken: CfDJ8Hsh_QOxexZLjyWnCjnNzz1Hy5YNNny4vPyGFd434Uybuo-O-dMFHT7eSKvd7JKA_dqxlQDIFlNzVsSjTDT6lzl7d2AU6Pj1nVRUyux3OZ2cGbyyBVKZHUI0Kq4u_MAx_b_29SUVSYh5Tpayc-0WPdcjGfxKLvL1qrbn6qeHcNk356VsPG1AC5cphft3frjILA
EDIT 3 [Solved]
Thanks to #itminus help I could solve the issue. Below you have changed form with two versions of the same - Roles with
<form asp-controller="Admin" asp-action="Update" method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="ViewModel.Id"></label>
<input type="text" asp-for="ViewModel.Id" readonly class="form-control" />
<span asp-validation-for="ViewModel.Id" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ViewModel.Password"></label>
<input type="password" asp-for="ViewModel.Password" class="form-control" />
<span asp-validation-for="ViewModel.Password" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ViewModel.ConfirmPassword"></label>
<input type="password" asp-for="ViewModel.ConfirmPassword" class="form-control" />
<span asp-validation-for="ViewModel.ConfirmPassword" class="text-danger"></span>
</div>
#for(int i = 0; i < Model.ViewModel.Roles.Count(); i++) {
<div class="form-group">
<label asp-for="ViewModel.Roles[i]"></label>
<input type="text" asp-for="ViewModel.Roles[i]" class="form-control" hidden="hidden" />
<span asp-validation-for="ViewModel.Roles[i]" class="text-danger"></span>
</div>
}
<div class="form-group">
<input type="number" asp-for="Page.PageNumber" class="form-control" hidden="hidden" />
<span asp-validation-for="Page.PageNumber" class="text-danger"></span>
</div>
<div class="form-group">
<input type="number" asp-for="Page.PageSize" class="form-control" hidden="hidden" />
<span asp-validation-for="Page.PageSize" class="text-danger"></span>
</div>
#foreach (var availableRole in (IList<IdentityRole>)ViewData["AllRoles"])
{
<div class="form-check">
<input class="form-check-input" type="checkbox" name="ViewModel.NewRoles[]" value="#availableRole.Name"
checked="#Model.ViewModel.Roles.Contains(availableRole.Name)">
<label class="form-check-label">#availableRole</label>
</div>
}
<button type="submit" class="btn btn-success">Change</button>
</form>
What is being sent from form now. (Example values)
ViewModel.Id: bf872461-f429-497f-8159-898e1dfafad9
ViewModel.Password: 1394657234609243070
ViewModel.ConfirmPassword: 2346-7345-67356667
ViewModel.Roles[0]: User
Page.PageNumber: 1
Page.PageSize: 5
ViewModel.NewRoles[]: Administrator
ViewModel.NewRoles[]: User
__RequestVerificationToken: CfDJ8Hsh_QOxexZLjyWnCjnNzz1AvzPUt8DrSfqpOni-Q65jAFnRqNs875teRTned_OQ6S7GV19WwWRqE7yRgTNmeeX9Twg4RjgQR_JEibtFYpHN6zbDJgLeauyyHxwOukRSTl0GZIRii5VaQBUxIbK6A8qHxgl4bn5-01YLj8bOXV9Ze7TvdP3MTX7ghYcAprPUJQ
Is naming convention wrong?
Yes. The name convention is :
Dot (.) represents property
[] represents collection index or dictionary
However, in your previous code, NewRoles fields have the name of newRoles:
<input ... name="newRoles" ...>
which is not correct. The same goes for the Roles property.
So you need change you code in following way:
Roles:
<label asp-for="ViewModel.Roles"></label>
<input type="text" asp-for="ViewModel.Roles" class="form-control" hidden="hidden" />
<span asp-validation-for="ViewModel.Roles" class="text-danger"></span>
#for(var i=0; i < Model.ViewModel.Roles.Count(); i++)
{
<input type="hidden" name="ViewModel.Roles[]" value="#Model.ViewModel.Roles[i]" />
}
NewRoles:
#foreach (var availableRole in (IList<IdentityRole>)ViewData["AllRoles"])
{
<div class="form-check">
<input class="form-check-input" type="checkbox"
name="ViewModel.NewRoles[]"
value="#availableRole.Name"
checked="#Model.ViewModel.Roles.Contains(availableRole.Name)">
<label class="form-check-label">#availableRole</label>
</div>
}

Posting varying amount of images from a form

I'm attempting to build a form with a few textbox inputs and a file input for someone to upload images.
When a file is picked, I have some JS go get a partial view and render it in the form. After the new partial is rendered, I clone and copy the file input element and place it as a hidden input in the new partial to be used later in the form. There's some other fields in the partial that relate to the image that was selected.
Once that's all done, the file input element is reset, and the user can select another image, generating a new partial view, etc.
The form looks like it's being rendered correctly, but when I try to submit, VS memory/cpu usage spikes, and the request never makes it to the controller. Any advice or help here is appreciated!
Screenshot of the form
ViewModel:
public class ProjectCreate
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public DateTime ProjectDate { get; set; }
public List<Image> GalleryImages { get; set; }
}
Image ViewModel:
public class Image
{
public int Id { get; set; }
public int GalleryIndex { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public IFormFile ImageFile { get; set; }
}
Form:
<form asp-action="Create" enctype="multipart/form-data">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group col-sm-4">
<label asp-for="Title" class="control-label"></label>
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
<div class="form-group col-sm-4">
<label asp-for="Description" class="control-label"></label>
<input asp-for="Description" class="form-control" />
<span asp-validation-for="Description" class="text-danger"></span>
</div>
<div class="form-group col-sm-4">
<label asp-for="ProjectDate" class="control-label"></label>
<input asp-for="ProjectDate" class="form-control" />
<span asp-validation-for="ProjectDate" class="text-danger"></span>
</div>
<div class="form-group col-sm-4">
<label class="control-label">Project Images</label>
<input id="ImageUpload" type="file" class="form-control" accept="image/jpeg, image/png, image/gif" />
</div>
#* Display images here *#
<div id="pending-images" class="row"></div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
The 'pending-images' div is where the partialview is rendered to.
PartialView:
<div class="card mb-3" style="width: 18rem">
<img class="card-img-top" id="pending-image-#Model.Id" src="" style="max-height: 18rem; max-width: 18rem;" />
<div class="card-body">
<div class="form-group">
<label class="control-label">Index</label>
<input id="GalleryImages[#Model.Id].GalleryIndex" name="GalleryImages[#Model.Id].GalleryIndex" class="form-control" value="#Model.Id" />
</div>
<div class="form-group">
<label class="control-label">Name/Title</label>
<input id="GalleryImages[#Model.Id].Name" name="GalleryImages[#Model.Id].Name" class="form-control" />
</div>
<div class="form-group">
<label class="control-label">Description</label>
<input id="GalleryImages[#Model.Id].Description" name="GalleryImages[#Model.Id].Description" class="form-control" />
</div>
<input type="file" id="GalleryImages[#Model.Id].ImageFile" name="GalleryImages[#Model.Id].ImageFile" style="display: none;" />
</div>
</div>
Rendered HTML:
<form enctype="multipart/form-data" action="/Projects/Create" method="post" novalidate="novalidate">
<div class="form-group col-sm-4">
<label class="control-label" for="Title">Title</label>
<input class="form-control valid" type="text" id="Title" name="Title" value="" aria-invalid="false">
<span class="text-danger field-validation-valid" data-valmsg-for="Title" data-valmsg-replace="true"></span>
</div>
<div class="form-group col-sm-4">
<label class="control-label" for="Description">Description</label>
<input class="form-control valid" type="text" id="Description" name="Description" value="" aria-invalid="false">
<span class="text-danger field-validation-valid" data-valmsg-for="Description"
data-valmsg-replace="true"></span>
</div>
<div class="form-group col-sm-4">
<label class="control-label" for="ProjectDate">Project Date</label>
<input class="form-control valid" type="date" data-val="true"
data-val-required="The Project Date field is required." id="ProjectDate" name="ProjectDate" value=""
aria-describedby="ProjectDate-error" aria-invalid="false">
<span class="text-danger field-validation-valid" data-valmsg-for="ProjectDate"
data-valmsg-replace="true"></span>
</div>
<div class="form-group col-sm-4">
<label class="control-label">Project Images</label>
<input id="ImageUpload" type="file" class="form-control" accept="image/jpeg, image/png, image/gif">
</div>
<div id="pending-images" class="row">
<div class="col-sm-4">
<div class="card mb-3" style="width: 18rem">
<img class="card-img-top" id="pending-image-0" src="data:image/jpeg;base64,xxx"
style="max-height: 18rem; max-width: 18rem;">
<div class="card-body">
<div class="form-group">
<label class="control-label">Index</label>
<input id="GalleryImages[0].GalleryIndex" name="GalleryImages[0].GalleryIndex"
class="form-control" value="0">
</div>
<div class="form-group">
<label class="control-label">Name/Title</label>
<input id="GalleryImages[0].Name" name="GalleryImages[0].Name" class="form-control">
</div>
<div class="form-group">
<label class="control-label">Description</label>
<input id="GalleryImages[0].Description" name="GalleryImages[0].Description"
class="form-control">
</div>
<input id="GalleryImages[0].ImageFile" type="file" class="form-control"
accept="image/jpeg, image/png, image/gif" name="GalleryImages[0].ImageFile"
style="display:none;">
</div>
</div>
</div>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary">
</div>
<input name="__RequestVerificationToken" type="hidden" value="xxx">
</form>
When there's more than one image trying to be uploaded, only the first image shows up the in the Fiddler info. I can also upload that or anything else if it would be helpful. I tried to include everything though. Is there a better way to accomplish this? I'm thinking about trying to hook into imgur or something similar if I can't work this out.
You should use your input like this :
<input type="file" multiple="multiple" name="files" id="files" />
I don't know if you can use the type="image" here, but you can certainly use your controler to check if the user only uploaded images.
Or you may be able to add this parameter to the input.
accept="image/png, image/jpeg"

Categories

Resources