Asp.Net Core Razor Pages - Binding and saving a list of objects - razor-pages

I have to get the photographs for a list of people. How do I generate the <input type="file" asp-for="#person.Photo"> controls in the cshtml page? Right now all the controls have the same name ("person_Photo"). Also how do I access this property in OnPost?
Sample code below for clarity:
Person class
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public IFormFile Photo { get; set; }
}
Create.cshtml.cs
public class CreateModel : PageModel
{
[BindProperty]
public List<Person> People {get; set;}
public async Task<IActionResult> OnGetAsync(int applicationId)
{
var applicants = repo.GetApplicants(applicationId);
People = applicants.Select(a => new Person(){Id=a.Id,Name=a.Name,Photo=null}).ToList();
}
public async Task<IActionResult> OnPostAsync()
{
//People is null below. For example OnGet fills People with 3 Person.
foreach (var person in People)
{
//I need to save the Photo.
//How do I access the IFormFile Photo?
//All the file inputs have the same name when I inspect the HTML.
}
}
}
Create.cshtml
All the file controls have the same name ie. person_Photo
<form method="post" enctype="multipart/form-data">
#foreach (var person in Model.People)
{
<input type="hidden" asp-for="#person.Id" />
<input type="hidden" asp-for="#person.Name" />
<input type="file" asp-for="#person.Photo"/>
}
<button type="submit"/>
</form>

In your OnPost method, you can access the uploaded files by using the
IFormFile property of each person.
The name attribute in the HTML should match the property name of the Person class.
You can use the Request.Form.Files collection to access the uploaded files:
public async Task<IActionResult> OnPostAsync()
{
for (int i = 0; i < People.Count; i++)
{
People[i].Photo = Request.Form.Files[$"People[{i}].Photo"];
// Save the photo
}
}

Related

Calling multiple Blazor components with two binding

I have a blazor component:
#inject IJSRuntime JSRuntime
#using bullDocDBAcess
#using bullDocDBAcess.Models
#inject ITagData _db
#if(tags == null){
<p><em>Loading...</em></p>
}else{
<input type="text" #bind=#tags data-role="tagsinput" />
<input type="hidden" #onchange="onChange" value=#tags />
}
#code{
private string tags = "";
private List<TagModel> tagsList;
private Task<IJSObjectReference> _module;
private Task<IJSObjectReference> Module => _module ??= JSRuntime.InvokeAsync<IJSObjectReference>("import", "./components/tagsinput/tags_inputs_imports.js").AsTask();
[Parameter]
public int organizationId { get; set; } = 0;
[Parameter]
public int documentId { get; set; } = 0;
[Parameter] public EventCallback<Notification> OnTagsChange { get; set; }
private void onChange(Microsoft.AspNetCore.Components.ChangeEventArgs args)
{
tags = (string) args.Value;
OnTagsChange.InvokeAsync(new Notification(tags, documentId.ToString()));
}
protected override async Task OnInitializedAsync()
{
if(organizationId == 0){ //inserção
tags = "";
}else{
tagsList = await _db.GetTagsFromDocument(organizationId, documentId);
foreach (TagModel t in tagsList){
if(String.IsNullOrEmpty(tags)){
tags += t.nome;
}else{
tags += "," + t.nome;
}
}
}
var module = await Module;
await module.InvokeVoidAsync("loadScripts");
}
public async ValueTask DisposeAsync()
{
if (_module != null)
{
var module = await _module;
await module.DisposeAsync();
}
}
}
Basically, the idea is to use a bootstrap input tag (called using jsinterop) and include it several times for each product in a table. When tags and updated in this table, the database should be updates with the tags for that specific values.
The parent component:
(...)
#for(int i=0;i<this.document.Count;i++)
{
DocumentModel p = this.document.ElementAt(i);
<tr>
<td><TagManagement #key=#p.id organizationId=#p.organizacao_id documentId=#p.id OnTagsChange="TagsHandler" /></td>
</tr>
}
(...)
void TagsHandler(Notification notification)
{
Console.WriteLine("Hello"); //only for testing purposes
}
A two-way binding is implemented between the parent and child. However, o have no idea how to handle multiple component invocation with callback function for each one. When a new tag is inserted, only works on the first element of the table and the event is fired two times (because in this test I only have two rows).
I tried to include the key attribute but didn't work. Can you please help me?
Best regards

Asp.Net Core: How to properly pass data from view to controller?

I am trying to pass some data from a view to a controller, using a view model.
In view I have something like:
#model LoginRegisterViewModel
<input type="text" name="Email" id="Email" placeholder="Email">
<input type="password" class="form-control" name="Password" id="Password" placeholder="Password">
View Model:
public class LoginModel
{
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
[DataType(DataType.Password)]
public string Password { get; set; }
[Display(Name = "Remember Me")]
public bool RememberMe { get; set; }
public string ReturnUrl { get; set; }
}
public class RegisterModel
{
public string Username { get; set; }
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
[DataType(DataType.Password)]
public string Password { get; set; }
public string ReturnUrl { get; set; }
}
public class ResetModel
{
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
public string ReturnUrl { get; set; }
}
public class LoginRegisterViewModel
{
public LoginModel Login { get; set; }
public RegisterModel Register { get; set; }
public ResetModel Reset { get; set; }
}
And in controller:
public async Task<IActionResult> Login(LoginRegisterViewModel model, string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (ModelState.IsValid)
{
var userOrEmail = model.Login.Email;
SignInResult result;
if (userOrEmail.IndexOf('#') > 0)
result = await _signInManager.PasswordEmailSignInAsync(userOrEmail, model.Login.Password, model.Login.RememberMe, false);
else
result = await _signInManager.PasswordSignInAsync(userOrEmail, model.Login.Password, model.Login.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
return LocalRedirect(returnUrl);
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
}
return View(model);
}
I am doing something wrong because I get the following error: NullReferenceException: Object reference not set to an instance of an object.
referencing this line: var userOrEmail = model.Login.Email;
you are binding to the wrong properties.
The controller action expect an object of type LoginRegisterViewModel, and it's the type of the model your view is expecting , but your controls are binding to wrong property .
for example <input type="text" name="Email" id="Email" placeholder="Email"> this maps to model.Email (where model is an object of LoginRegisterViewModel) , and LoginRegisterViewModel doesn't have an Email property but it has a LoginModel property which has the Email property , so you should bind your input to model.Login.Email .
Ps : you can use the MVC core tag helpers to bind properties to model
<input asp-for="model.Login.Email" name="anyothername" class="form-control" />
or even razor syntax
#Html.EditorFor(model => model.Login.Email)
Just got it. I should have used name="Login.Email" and name="Login.Password" in the view.

how to bind data to a user defined class asp.net core 2

I defined a model look like this
public class X {
int a;
MyClass b;
}
and I have an action look like this
public IActionResult Test(X x){}
now when user submit data I want to manipulate that data and then assign it to b.
how can I do it?
You didn't provide the detailed explanation such as how you want to submit data , suppose you have a book model class that contains a child Author property :
public class Book
{
public int id { get; set; }
public string name { get; set; }
public Author author { get; set; }
}
public class Author {
public int authorId { get; set; }
public string authorName { get; set; }
}
In your view , using Tag Helpers in forms , you can bind to Author.authorName :
#model Book
<form asp-controller="Home" asp-action="RegisterInput" method="post">
BookID: <input asp-for="id" /> <br />
BookName: <input asp-for="name" /><br />
AuthorID: <input asp-for="author.authorId" /> <br />
AuthorName: <input asp-for="author.authorName" /><br />
<button type="submit">Register</button>
</form>
In controller , you will find the input values will automatically bind to related properties :
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RegisterInput(Book book)
{
return View();
}

ValidationResult and ViewModels

I have recently created a new application that is using MVC4 and EF5. The tables already existed, so we are using Database first. This is all new to me, it is my first time in MVC or EF.
I have some models in my EMDX file, and in one of them I inherited IValidatableObject and put some code in the Validate function. This was working fine, then I changed my view to use a ViewModel, now I get an error from validate, and I am stumped. It is still calling my validate function, but no longer posting it back to the screen, i just get a yellow screen.
Error:
Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.
Model:
public partial class Names : IValidatableObject {
public int Id { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
// some logic, this works
}
}
ViewModel:
public class NamesVM {
public Names Name { get; set; }
// some other stuff in this model, but not part of this problem
}
Controller: Edit Function:
[HttpPost]
public ActionResult Edit(NamesVM nvm) {
if (ModelState.IsValid) {
dbCommon.Entry(nvm.Name).State = EntityState.Modified;
dbCommon.SaveChanges();
return RedirectToAction("Index");
}
return View(nvm);
}
View:
#model NamesVM
<div class="editor-label">
#Html.LabelFor(model => model.Name.Id)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name.Id)
#Html.ValidationMessageFor(model => model.Name.Id)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Name.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name.Name)
#Html.ValidationMessageFor(model => model.Name.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Name.Active)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name.Active)
#Html.ValidationMessageFor(model => model.Name.Active)
</div>
<input type="submit" value="Save" />
The codes works fine if everything is correct on the screen, but when validation fails, i don't get a nice error, I get a yellow screen. I am sure I am missing something, I just don't know what.
finally solved it. The IValidatableObject needs to be moved to the ViewModel, as well as the logic for it.
Model
public partial class Names { //: IValidatableObject { //Remove the IValidateableObject from here
public int Id { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
// this whole method can be commented out
//public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
// some logic, this works
//}
}
View Model
public class NamesVM : IValidatableObject { // add the IValidatableObject to your view model
public Names Name { get; set; }
// some other stuff in this model, but not part of this problem
// and move your validation logic to here
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
// some logic, this works
}
}

Populating a select box in a form using related ID in MVC3

I have a very simple data structure with two models. The first containing UserName, UserQuestion and userLocationID and another with LocationName and LocationID, the locationID in the first table is related to the LocationName the second table. However I've not specified any relationship. I've set up the data structure using the code first method in used here .
I would like to create a form which has two text inputs for a user to enter their name and question and a select box that is populated with all the locationNames from the second table. However I can't seem to create the model that allows me to do so. Do I need to make a separate ViewModel?
Does anyone know of a simple tutorial that will explain how to do this?
I'm quite new at MVC, and the dot net framework. . And I've had a look at this answer but I can't seem to modify it to fit my needs. So Apologies if I'm asking for something really basic.
I can give an example in one controller, one view and three C# classes. To use this code, create an empty MVC2 project in visual studio and add a reference to Entity Framework dll version 4.1. If you need help as to where to put these files I recommend Steve Sanderson's MVC2 book.
public class User
{
public int ID { get; set; }
public string UserName { get; set; }
public string Question { get; set; }
public virtual Location Category { get; set; }
}
public class Location
{
public int ID { get; set; }
public string LocationName { get; set; }
}
Repository
using System.Data.Entity;
using System.Collections.Generic;
using System.Linq;
public class Repository : System.Data.Entity.DbContext
{
public DbSet<User> User { get; set; }
public DbSet<Location> Locations { get; set; }
public Repository()
{
this.Database.Connection.ConnectionString =
#"Server=.;Database=Test;Integrated Security=SSPI";
if (!this.Database.Exists())
{
this.Database.Create();
this.Locations.Add(new Location { LocationName = "Queensway" });
this.Locations.Add(new Location { LocationName = "Shepherds Bush" });
this.SaveChanges();
}
}
public IEnumerable<Location> GetLocations()
{
return this.Locations.Where(x => x.ID > -1);
}
public Location GetLocation(int id)
{
return this.Locations.First(x => x.ID == id);
}
public void SaveUser(User user)
{
this.User.Add(user);
this.SaveChanges();
}
}
Controllers\HomeContoller.cs:
using System.Web.Mvc;
public class HomeController : Controller
{
Repository repo = new Repository();
[HttpGet]
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(User user, int categoryId)
{
user.Category = repo.GetLocation(categoryId);
repo.SaveUser(user);
return View();
}
}
Views\Home\Index.aspx
<%# Page Language="C#" Inherits="System.Web.Mvc.ViewPage<User>" %>
<html>
<body>
<% using (Html.BeginForm())
{%>
Username: <%: Html.TextBoxFor(model => model.UserName) %><br />
Question: <%: Html.TextBoxFor(model => model.Question) %><br />
Location: <select name="categoryId">
<% foreach (var location in new Repository().GetLocations())
{%>
<option value="<%= location.ID %>">
<%= location.LocationName %></option>
<%} %>
<br />
</select>
<p>
<input type="submit" value="Create" />
</p>
<% } %>
</body>
</html>

Resources