Model Binding in razor page does not bind object - razor

I have a small project where I am trying to add to the database. I get a null reference exception but I think the issue is that the model binding does not work properly.
Here is the razor :
<form method="post" class="col-sm-12" enctype="multipart/form-data">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<div class="col-sm-12">
<label>Name</label>
<span></span>
</div>
<div class="col-sm-12">
<input class="col" asp-for="Product.Name" />
<span asp-validation-for="Product.Name" class="text-danger"></span>
</div>
<div class="col-sm-12">
<label>Description</label>
</div>
<div class="col-sm-12">
<textarea cols="60" rows="5" asp-for="#Model.Product.Description"></textarea>
<span asp-validation-for="Product.Description" class="text-danger"></span>
</div>
<div class="col-sm-12 ">
<label>Image</label>
</div>
<div class="col-sm-12">
<input type="file" asp-for="Image" />
<span asp-validation-for="Image" class="text-danger"></span>
</div>
<div class="col-sm-12">
<label>Categories</label>
</div>
<select name="categorycombo" asp-for="Product.CategoryId">
<option value="">Choose Category</option>
#foreach (var category in #Model.Categories)
{
<option value="#category.Id.ToString()">#category.Name.ToString()</option>
}
</select>
<div class="col-sm-12 ">
<input type="submit" value="Add" />
</div>
</div>
</form>
And here is the OnPost method :
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return await GetPage();
}
var path = Path.Combine(_webHostEnvironment.WebRootPath, "Images/Products");
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
if (Image.Length > 0)
{
var fileStream = new FileStream(Path.Combine(path, Image.FileName), FileMode.Create);
***Product.ImageUrl = Path.Combine("Images/Products", Image.FileName);***
await Image.CopyToAsync(fileStream);
}
await _productRepo.Add(Product);
return RedirectToPage("./AddProduct");
}
on the line where I create the ImageUrl I get NullReference exception for product, and it should not be null if the binding is working properly. At the same time maybe there is something I can't see.
I forgot to add the code that does the model binding here it is :
[BindProperty]
public IEnumerable<ProductViewModel> Products { get; private set; }
[BindProperty]
public IEnumerable<CategoryViewModel> Categories { get; private set; }
[BindProperty]
public ProductViewModel Product { get; private set; }
[Required]
[BindProperty]
public IFormFile Image { get; set; }

Solved :
How ?
Simple, though I don't understand exactly why.
[BindProperty]
public ProductViewModel Product { get; private set; }
in the previous snippet we remove the private set; and replace it with set; and it works.
the reasoning I don't understand because I have the same code for a different class and that works, and the only difference between the two is that the other class does don have and IForm File in the form.
Online I found some bits of info that say that the code that does model binding is not part of the page model so adding private or internal to the property setter will prevent it from being set by the model binder.
I don't know what I am talking about but it seems like it fixed it.

Related

Save a photo and video in a database by viewing

I have a program with login and logout.
I have an exercise class, I have a create view that allows me to create exercises that are composed by name, photo, video.
I fill the form in View Create and when I click create this error appears to me
NullReferenceException: Object reference not set to an instance of an object.
WebApplication1.Controllers.ExerciciosGinasiosController.Create(ExerciciosGinasio exerciciosGinasio, IFormFile fotografia, IFormFile video) in ExerciciosGinasiosController.cs
string nome_ficheiro = Path.GetFileName(fotografia.FileName);
In the exercise class I have
[Table("Exercicios_Ginasio")]
public partial class ExerciciosGinasio
{
public ExerciciosGinasio()
{
Inclui = new HashSet<Inclui>();
}
[Key]
[Column("IDExercicios_Ginasio")]
public int IdexerciciosGinasio { get; set; }
[Required]
[Column("nome")]
[StringLength(30)]
public string Nome { get; set; }
[Required]
[Column("texto_descritivo")]
[StringLength(1000)]
public string TextoDescritivo { get; set; }
[Required]
[Column("foto")]
public string Foto { get; set; }
[Required]
[Column("video")]
public string Video { get; set; }
[InverseProperty("IdexerciciosGinasioNavigation")]
public virtual ICollection<Inclui> Inclui { get; set; }
}
}
In the controller belonging to the Exercises class (ExercisesController) I have this method in order to create a new exercise
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("IdexerciciosGinasio,Nome,TextoDescritivo,Foto,Video")] ExerciciosGinasio exerciciosGinasio, IFormFile fotografia,IFormFile video)
{
string caminho = Path.Combine(_hostEnviroment.ContentRootPath, "wwwroot\\Exercicios");
string nome_ficheiro = Path.GetFileName(fotografia.FileName);
string caminho_completo = Path.Combine(caminho, nome_ficheiro);
FileStream fs = new FileStream(caminho_completo, FileMode.Create);
fotografia.CopyTo(fs);
exerciciosGinasio.Foto = caminho_completo;
fs.Close();
string caminho2 = Path.Combine(_hostEnviroment.ContentRootPath, "wwwroot\\Exercicios");
string nome_ficheiro2 = Path.GetFileName(video.FileName);
string caminho_completo2 = Path.Combine(caminho2, nome_ficheiro2);
FileStream _fs = new FileStream(caminho_completo2, FileMode.Create);
video.CopyTo(_fs);
exerciciosGinasio.Video = caminho_completo2;
_fs.Close();
if (ModelState.IsValid)
{
_context.Add(exerciciosGinasio);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(exerciciosGinasio);
}
And in the view of this method I have
#model WebApplication1.Models.ExerciciosGinasio
#{
ViewData["Title"] = "Create";
}
<h4>Criar Exercicio</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create" enctype="multipart/form-data">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Nome" class="control-label"></label>
<input asp-for="Nome" class="form-control" />
<span asp-validation-for="Nome" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="TextoDescritivo" class="control-label"></label>
<input asp-for="TextoDescritivo" type="text" class="form-control" />
<span asp-validation-for="TextoDescritivo" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Foto" class="control-label"></label>
<input asp-for="Foto" type="file" class="form-control" accept=".png, .jpg, .bmp" value="" />
#*<span asp-validation-for="Foto" class="text-danger"></span>*#
<div>
<input type="hidden" name="fotografia" value="0" />
</div>
<div class="form-group">
<label asp-for="Video" class="control-label"></label>
<input asp-for="Video" type="file" class="form-control" />
#*<span asp-validation-for="Video" class="text-danger"></span>*#
</div>
<div>
<input type="hidden" name="video" value="0" />
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<br />
<div>
<a asp-action="Index" asp-controller="Home">Voltar</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Does anyone know what may be causing this error? And how can I solve
NullReferenceException: Object reference not set to an instance of an object.
string nome_ficheiro = Path.GetFileName(fotografia.FileName);
In your View page, we can find you are using hidden field(s) with default value 0 for fotografia and video, but your Create action expects file via IFormFile object, if you debug your code, you would find fotografia is null, which would casue exception while you read FileName property of fotografia.
To fix it, you can modify the code like below.
Inputs of Photo and Video
<div class="form-group">
<label asp-for="Foto" class="control-label"></label>
<input type="file" class="form-control" name="fotografia" accept=".png, .jpg, .bmp" />
</div>
<div>
<input type="hidden" name="Foto" value="0" />
</div>
<div class="form-group">
<label asp-for="Video" class="control-label"></label>
<input type="file" name="fvideo" class="form-control" />
</div>
<div>
<input type="hidden" name="Video" value="0" />
</div>
Controller Action
[HttpPost]
public IActionResult Create([Bind("IdexerciciosGinasio,Nome,TextoDescritivo,Foto,Video")] ExerciciosGinasio exerciciosGinasio,
IFormFile fotografia,
IFormFile fvideo)
{
string nome_ficheiro = Path.GetFileName(fotografia.FileName);
//code logic here
return View(exerciciosGinasio);
}
Test Result

Put a video in a View and play it Aspnet.core

I have an exercise class and a view that allows me to create exercises.
One of the variables is a video, how can you insert a video in order to save it in the database and present it in the view?
In exercise class I have
[Table("Exercicios_Ginasio")]
public partial class ExerciciosGinasio
{
public ExerciciosGinasio()
{
Inclui = new HashSet<Inclui>();
}
[Key]
[Column("IDExercicios_Ginasio")]
public int IdexerciciosGinasio { get; set; }
[Required]
[Column("nome")]
[StringLength(30)]
public string Nome { get; set; }
[Required]
[Column("texto_descritivo")]
[StringLength(1000)]
public string TextoDescritivo { get; set; }
[Required]
[Column("foto")]
public string Foto { get; set; }
[Required]
[Column("video")]
public string Video { get; set; }
[InverseProperty("IdexerciciosGinasioNavigation")]
public virtual ICollection<Inclui> Inclui { get; set; }
}
}
In the controller Exercises I used the function Create automatically generated in the creation of the controller
In the method I added code to save the image in the database
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("IdexerciciosGinasio,Nome,TextoDescritivo,Foto,Video")] ExerciciosGinasio exerciciosGinasio, IFormFile fotografia,IFormFile video)
{
string caminho = Path.Combine(_hostEnviroment.ContentRootPath, "wwwroot\\Exercicios");
string nome_ficheiro = Path.GetFileName(fotografia.FileName);
string caminho_completo = Path.Combine(caminho, nome_ficheiro);
FileStream fs = new FileStream(caminho_completo, FileMode.Create);
fotografia.CopyTo(fs);
exerciciosGinasio.Foto = caminho_completo;
fs.Close();
string caminho2 = Path.Combine(_hostEnviroment.ContentRootPath, "wwwroot\\Exercicios");
string nome_ficheiro2 = Path.GetFileName(video.FileName);
string caminho_completo2 = Path.Combine(caminho2, nome_ficheiro2);
FileStream _fs = new FileStream(caminho_completo2, FileMode.Create);
video.CopyTo(_fs);
exerciciosGinasio.Video = caminho_completo2;
_fs.Close();
if (ModelState.IsValid)
{
_context.Add(exerciciosGinasio);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(exerciciosGinasio);
}
So how do I put it in the view to insert a video and save it in the database?
In order to be able to present it in the index
#model WebApplication1.Models.ExerciciosGinasio
#{
ViewData["Title"] = "Create";
}
<h4>Criar Exercicio</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create" enctype="multipart/form-data">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Nome" class="control-label"></label>
<input asp-for="Nome" class="form-control" />
<span asp-validation-for="Nome" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="TextoDescritivo" class="control-label"></label>
<input asp-for="TextoDescritivo" type="text" class="form-control" />
<span asp-validation-for="TextoDescritivo" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Foto" class="control-label"></label>
<input asp-for="Foto" type="file" class="form-control" accept=".png, .jpg, .bmp" value="" />
#*<span asp-validation-for="Foto" class="text-danger"></span>*#
<div>
<input type="hidden" name="fotografia" value="0" />
</div>
<div class="form-group">
<label asp-for="Video" class="control-label"></label>
<input asp-for="Video" type="file" class="form-control" />
#*<span asp-validation-for="Video" class="text-danger"></span>*#
</div>
<div>
<input type="hidden" name="video" value="0" />
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<br />
<br />
<br />
<br />
<div>
<a asp-action="Index" asp-controller="Home">Voltar</a>
</div>
<br />
<br />
<br />
<br />
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
I tested it this way, and I got this error
NullReferenceException: Object reference not set to an instance of an object.
WebApplication1.Controllers.ExerciciosGinasiosController.Create(ExerciciosGinasio exerciciosGinasio, IFormFile fotografia, IFormFile video) in ExerciciosGinasiosController.cs, line 64
WebApplication1.Controllers.ExerciciosGinasiosController.Create(ExerciciosGinasio exerciciosGinasio, IFormFile fotografia, IFormFile video) in ExerciciosGinasiosController.cs
string nome_ficheiro = Path.GetFileName(fotografia.FileName);
Can someone help me solve it?

Adding css class to input tag thymleaf + spring

I am new to spring + thymleaf, I have implemented a form with validation all are working fine but if form field is empty or null, I have to make the filed red. which I am not able to do. please find the code below: class name and id are renamed
My Class:
public class Comp implements Serializable {
private static final long serialVersionUID = 1L;
#JsonProperty("name")
#NotNull
#NotBlank
private String name;
#JsonProperty("accId")
#NotNull
#NotBlank
private String accId;
}
My Controller method:
#RequestMapping(value = "/companyAccountHtml", method = RequestMethod.GET)
public String companyAccount(HttpServletRequest request, Model model) {
model.addAttribute("companyAccess", new CompanyAccess());
return "addcompanyaccount";
}
#RequestMapping(value = "/addCompanyAccount", method = RequestMethod.POST)
public String addCompanyAccount(#Valid CompanyAccess companyAccess, BindingResult result,
HttpServletRequest request, Model model) {
if(result.hasErrors()){
return "addcompanyaccount";
}
return "redirect:/cred";
}
HTML:
<form id="comp" name="comp" action="#" th:action="#{/addCompanyAccount/}"
th:object="${companyAccess}" method="post" >
<div class="c" style="margin-left: auto; margin-right: auto; float: none;">
<p class="hed">Add Company Account</p>
<label for="ad">Name*</label>
<input type="text" th:field="*{name}" name="name" maxlength="40"
th:classappend="${#fields.hasErrors('name')}? has-error : ">
<label for="acc">Id*</label>
<input type="text" th:field="*{accId}" name="accId" maxlength="12"
th:classappend="${#fields.hasErrors('accId')}? has-error : ">
</div>
<div class="clear"></div>
<div class="vltl_button-wrapper">
<input type="submit" id="submitBtn" class="ccc" value=" Save "></button>
<input type="button" class="ccc" value="Cancel" onClick="document.location.href='../cred'">
</div>
</form>
CSS
.has-error{
border:1px solid red!important;
}
Getting error in th:classappend="${#fields.hasErrors('name')}? has-error : " with the input tag. If I make a use of p tag the red line is coming down of input text, I want the input box will be red if fields are blank or null. I tried th:if also which is not populating the text filed itself only on error it shows.
Please let me know how to proceed. Already a lot of CSS return on input tag
Thank you.
You need to add the false condition to your th:classappend expression. For example on the name field:
<input type="text" th:field="*{name}" name="name" maxlength="40"
th:classappend="${#fields.hasErrors('name')}? has-error : ''">
Note the empty single-quoted string after : in the th:classappend. What you have currently is an invalid ternary expression. If you used a ternary in Java it would be:
int x = booleanCondition ? 1 : ; // Error because the right side doesn't have a value
The same concept applies here.

Thymeleaf + Spring MVC - selected date field on HTML5 is null on controller

The following input field for a date object works, apparently, nicely. But when it reaches the controller, the value for executionDate is null.
<form role="form" action="#" th:object="${pojo}" th:action="#{/scheduler/create}" method="post">
<div class="col-lg-5 col-sm-5 col-xs-10" >
<div class="well with-header">
<div class="header">
<label th:for="datepicker0">Execution Date: </label>
</div>
<div class="form-group input-group">
<input id="datepicker0" type="text" name="executionDate" th:field="*{executionDate}" class="form-control"></input>
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
</div>
</div>
</div>
// rest of the page
</form>
Relevant part of controller is:
#RequestMapping(value = "/scheduler/create", method = RequestMethod.POST)
public String createSchedulerPost(#Valid #ModelAttribute("pojo") SchedulerPojo pojo, BindingResult result, ModelMap model) {
System.out.println(pojo.getDescription());
System.out.println(pojo.isRecurrent());
System.out.println(pojo.getExecutionDate());
System.out.println(pojo.getStartDate());
System.out.println(pojo.getTerminationDate());
System.out.println(pojo.getFailStrategy());
(...) // I'm just verifying whether the SchedulerPojo pojo object has values for now...
}
The SchedulerPojo DTO is:
public class SchedulerPojo {
private String id;
private String description;
private Date executionDate;
private boolean recurrent;
private Date startDate;
private Date terminationDate;
private SchedulerFailStrategy failStrategy;
// other attributes, getters and setters
}
Other, fields as the description String and recurrent boolean checkbox inputs return the given value on the HTML.
What am I missing here?
According to Thymeleaf+Spring tutorial th:field generates code the same as you set id and name tags:
<input type="text" th:field="*{datePlanted}" />
Equivalent code :
<input type="text" id="datePlanted" name="datePlanted" th:value="*{datePlanted}" />
Maybe you need to remove id=datepicker0 to executionDate and bind the class on datepicker?
<input class="date" type="text" th:field="*{executionDate}" class="form-control"></input>
...
<script>
$(document).ready(function () {
$('.date').datepicker({dateFormat: 'dd.mm.yy'});
});
</script>

How to populate select element in thymeleaf springboot?

I am writing a web application using thymeleaf, springboot. I have one model class with id, name, address, state and district fields. I have 3 tables user table, state table with id, state_name, state_code, and district table with id, district_code, district_name, state_code.
How to map List of states to user table state field?.
Q1. When I load my newrecord view it should fatch all record from state table and populate in select element?.
Q2. When I select state from select fatch all dstrict list of that state?.
Q3. When I open same record in edit mode state and district list show its default value?.
Controller
#RequestMapping("user/new")
public String newUser(Model model){
model.addAttribute("user", new User());
return "userform";
}
#RequestMapping("user/edit/{id}")
public String update(#PathVariable Integer id, Model model){
model.addAttribute("user", userService.getProductById(id));
return "userform";
}
Model
#Entity
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String name;
private String address;
private String state;
private String dist;
//getter setter
}
public class State {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer state_id;
private String state_name;
private String state_code;
}
Thymeleaf view
<html xmlns:th="http://www.thymeleaf.org">
<head lang="en">
</head>
<body>
<div class="container">
<h2>User Details</h2>
<div>
<form class="form-horizontal" th:object="${user}" th:action="#{/user}" method="post">
<input type="hidden" th:field="*{id}"/>
<div class="form-group">
<label class="col-sm-2 control-label">Name:</label>
<div class="col-sm-10">
<input type="text" class="form-control" th:field="*{name}"/>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Address:</label>
<div class="col-sm-10">
<input type="text" class="form-control" th:field="*{address}"/>
</div>
</div>
**//NOTE:- These select list not working I am able to display above information not this list**
*<div class="form-group">
<label class="col-sm-2 control-label">State:</label>
<div class="col-sm-10">
<select class="form-control" name="selectState" th:field="*{state}" >
<option th:each="sopt:${state}" th:value="?" th:text="?">State</option>
</select>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Dist:</label>
<div class="col-sm-10">
<select class="form-control" name="selectDistrict" th:field="*{dist}" >
<option th:each="sopt:${dist}" th:value="?" th:text="?">Dist</option>
</select>
</div>
</div>*
<div class="row">
<button type="submit" class="btn btn-default">Submit</button>
</div>
</form>
</div>
</div>
</body>
</html>
For Q1 i got solution by using What is #ModelAttribute in Spring MVC?
#ModelAttribute("states")
public List<State> populateStates() {
List<State> listStates = new ArrayList<State>();
for (State e : stateService.listAllState()) {
System.out.println(e.getState_code());
listStates.add(e);
}
return listStates;
}
Q3 problem also solved by this when you open it in edit mode it will select your saved value
<select th:field="*{states}" id="state" onChange="onstatechange(this)" class="infobox">
<option value="0">Select Your State</option>
<option th:each="state : ${states}" th:value="${state.state_id}" th:text="${state.state_name}"></option>
</select>
For Q2 you can use ajax call or javascript onChange can solve my problem.