I have a very simple implementation of the DefaultModelBinder, I need it to fire some custom validation.
public class MyViewModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
ModelStateDictionary modelState = bindingContext.ModelState;
var model = (MyViewModel)base.BindModel(controllerContext, bindingContext);
var result = ValidationFactory.ForObject<MyViewModel>().Validate(model);
CustomValidation(result, modelState);
return model;
}
}
MyViewModel is a public sealed class.
The model binder is registered in the Global.asax this way:
ModelBinders.Binders.Add(typeof(MyViewModel), new MyViewModelBinder());
The problem is that the model is never populated! But the MVC default model binder (I remove the registration in global.asax) works fine.
This is the view HTML:
<table>
<tr>
<td><label for="Name">Name</label></td>
<td><input id="Name" name="Name" type="text" value="" /></td>
</tr>
<tr>
<td><label for="Code">Code</label></td>
<td><input id="Code" name="Code" type="text" value="" /></td>
</tr>
</table> </div>
Every field matches a property of the model.
From the information you provided I am unable to reproduce the problem. Here's what I did.
View model:
public sealed class MyViewModel
{
public string Name { get; set; }
public string Code { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
// at this stage the model is populated perfectly fine
return View();
}
}
Index View:
<%# Page Language="C#" Inherits="System.Web.Mvc.ViewPage" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
</head>
<body>
<% using (Html.BeginForm()) { %>
<table>
<tr>
<td><label for="Name">Name</label></td>
<td><input id="Name" name="Name" type="text" value="" /></td>
</tr>
<tr>
<td><label for="Code">Code</label></td>
<td><input id="Code" name="Code" type="text" value="" /></td>
</tr>
</table>
<input type="submit" value="OK" />
<% } %>
</body>
</html>
Model binder:
public class MyViewModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var model = (MyViewModel)base.BindModel(controllerContext, bindingContext);
// at this stage the model is populated perfectly fine
return model;
}
}
So now the question is, how does your code differs than mine and what is it in those CustomValidation and Validate methods?
Related
I'm new to Asp.net core and was wondering what I am doing wrong.
I have models like this that are populating my View:
Public class Person
{
public string Id { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
}
Public class Collection
{
public List<Person> People { get; set; }
}
A View that takes a form of checkbox items and is meant to set 'isSelected' to true.
#model Collection
#using (Html.BeginForm("PostSelectedPerson", "Person", FormMethod.Post))
{
<tbody>
#for (int i = 0; i < Model.People.Count; i++)
{
<tr>
<td><input type="checkbox" name="#Model.People[i].IsSelected" value="true" checked /></td>
#Html.HiddenFor(model => model.People[i].IsSelected)
<td>
#Model.People[i].Id
</td>
</tr>
<input type="submit" value="Submit" name="Submit" />
}
</tbody>
}
And an Action that is meant to be passed the updated Collection model.
[HttpPost]
public IActionResult PostSelectedPerson(Collection c)
{
//return something.
}
The View is populated, but when the form is submitted, the action gets an empty Collection.
I expect a Collection with a list of Person with isSelected set to true for those with checkboxes marked.
The View is populated, but when the form is submitted, the action gets an empty Collection.
If you'd like to include both Id and IsSelected info in formdata and post them to action, please use additional hidden form field for Id property and specify name attribute for hidden field to match property of custom model class, which would help bind value to properties of the model automatically, like below.
#using (Html.BeginForm("PostSelectedPerson", "Person", FormMethod.Post))
{
<table>
<tbody>
#for (int i = 0; i < Model.People.Count; i++)
{
<tr>
<td>
<input asp-for="#Model.People[i].IsSelected">
</td>
<td>
#Model.People[i].Id
<input type="hidden" name="People[#i].Id" value="#Model.People[i].Id" />
</td>
<td>
#Model.People[i].Name
</td>
</tr>
}
</tbody>
</table>
<input type="submit" value="Submit" />
}
Test Result
I expect a Collection with a list of Person with isSelected set to true for those with checkboxes marked.
If you want only selected person data are post to action, you can dynamically generate/populate formdata based on selected rows, then make ajax request to post it to your action on JavaScript client.
I read the book 'Pro Entity Framework Core 2 for ASP.NET MVC'. I'm currently at the beginning of chapter 12 and have some problems. I have this controller:
namespace DataApp.Controllers
{
public class HomeController : Controller
{
private IDataRepository repository;
public HomeController(IDataRepository repo)
{
repository = repo;
}
public IActionResult Index()
{
return View(repository.GetAllProducts());
}
public IActionResult Create()
{
ViewBag.CreateMode = true;
return View("Editor", new Product());
}
[HttpPost]
public IActionResult Create(Product product)
{
repository.CreateProduct(product);
return RedirectToAction(nameof(Index));
}
public IActionResult Edit(long id)
{
ViewBag.CreateMode = false;
return View("Editor", repository.GetProduct(id));
}
[HttpPost]
public IActionResult Edit(Product product)
{
repository.UpdateProduct(product);
return RedirectToAction(nameof(Index));
}
[HttpPost]
public IActionResult Delete(long id)
{
repository.DeleteProduct(id);
return RedirectToAction(nameof(Index));
}
}
}
The index view looks like this:
#model IEnumerable<DataApp.Models.Product>
#{
ViewData["Title"] = "Products";
Layout = "_Layout";
}
<table class="table table-sm table-striped">
<thead>
<tr><th>ID</th><th>Name</th><th>Category</th><th>Price</th></tr>
</thead>
<tbody>
#foreach (var p in Model)
{
<tr>
<td>#p.Id</td>
<td>#p.Name</td>
<td>#p.Category</td>
<td>$#p.Price.ToString("F2")</td>
<td>
<form asp-action="Delete" method="post">
<a asp-action="Edit"
class="btn btn-sm btn-warning" asp-route-id="#p.Id">
Edit
</a>
<input type="hidden" name="id" value="#p.Id" />
<button type="submit" class="btn btn-danger btn-sm">
Delete
</button>
</form>
</td>
</tr>
}
</tbody>
</table>
<a asp-action="Create" class="btn btn-primary">Create New Product</a>
If I run the application and click the Edit or Create button, I don't get the Editor view. If I navigate in the browser to /Home/Edit, then the view is shown. What can be the problem?
You can find the complete source code for this chapter here:
Chapter 2
Please note, that I'm at the beginning of the chapter and the files in the source code may contain more, that I currently have, but according to the book, it should work on this stage too.
What I want to do is a parametrized report, i would love to use SSRS or other fancy tools for this but it's sort of dangerous at this point because i don't really want to mess around with the company server and I dont have much time; Also If it's a tool it should be a free and light tool and i didn't find one by now.
So, my idea is making a simple controller with Index that will return a List to View according to parameters and the View will use that ViewModel as Model then the users can export that list to CSV or PDF, the problem is: MVC is asking for a real db model to complete the scaffolding, how can this be done then?
Controller (I call an stored proc here)
public class ReporteEntregasPresentacionController : Controller
{
private EntregaMedicamentosEntities db = new EntregaMedicamentosEntities();
public ActionResult Index(DateTime DateFrom, DateTime DateTo)
{
ReporteEntregasPresentacionViewModel rep = new ReporteEntregasPresentacionViewModel();
string sqlQuery = "[dbo].[spEntregasPorPresentacion] ({0},{1})";
Object[] parameters = { DateFrom, DateTo };
rep.LstEntregasPresentacionViewModel = db.Database.SqlQuery<ItemEntregasPresentacionViewModel>(sqlQuery, parameters).ToList();
return View(rep);
}
}
ViewModel:
public class ReporteEntregasPresentacionViewModel
{
public int index;
public List<ItemEntregasPresentacionViewModel> LstEntregasPresentacionViewModel;
}
public class ItemEntregasPresentacionViewModel {
public string idProducto { get; set; }
public string Descripcion { get; set; }
public string EntregasTotales { get; set; }
}
I don't have a View now but i should be something like this:
#model EntregaMedicamentos.Models.ReporteEntregasPresentacionViewModel
<link href="~/Content/css/styles.css" rel="stylesheet" />
#{
ViewBag.Title = "ReporteEntregasPresentacion";
}
<h2>ReporteEntregasPresentacion</h2>
#using (Html.BeginForm("Index", "Entrega", FormMethod.Post))
{
<div class="card text-white bg-secondary">
<h5 class="card-header">Search</h5>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="input-group">
#Html.TextBox("DateFrom", ViewBag.currentFilter1 as DateTime?, new { #class = "form-control", placeholder = "Desde fecha", #readonly = "true", type = "datetime" })
#Html.TextBox("DateTo", ViewBag.currentFilter2 as DateTime?, new { #class = "form-control", placeholder = "Hasta fecha", #readonly = "true", type = "datetime" })
<button id="Submit4" type="submit" style='font-size:22px ;color:blue'><i class='fas fa-search'></i></button>
</div>
</div>
</div>
</div>
</div>
}
<br>
<table class="table table-striped ">
<tr class="table-primary">
<th>
Código
</th>
<th>
Producto
</th>
<th>
Entregas Totales
</th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.idProducto)
</td>
<td>
#Html.DisplayFor(modelItem => item.Descripcion)
</td>
<td>
#Html.DisplayFor(modelItem => item.Valor)
</td>
</tr>
}
</table>
I ended up creating a real table/model and then it worked fine with the viewmodel. Thanks.
Maybe (actually I'm sure) it's me, but I cannot seem to figure out how to retrieve list items as part of a model object. The post here seems to satisfy everyone but neither answer is relatable in my limited understanding.
I need to get the items that are checked so I can update the Db. Sounds simple.
My Model:
public class UserAdminModel
{
public Guid UserId { get; set; }
public string UserName { get; set; }
public List<UserRole> UserRoles { get; set; }
public string csvAllRolls { get; set; }
}
public class UserRole
{
public Guid RoleId { get; set; }
public string UserRoleName { get; set; }
public bool UserisinRole { get; set; }
}
My View:
<% using (Html.BeginForm("UpdateRoles", "UserAdmin", FormMethod.Post))
{%>
<input type="hidden" id="UserId" name="UserId" value="<%: Model.UserId %>" />
...
<% foreach (var role in Model.UserRoles)
{ %>
<tr>
<td> </td>
<td colspan="2" nowrap="nowrap"><%: role.UserRoleName %></td>
<td> </td>
<td>
<input type="checkbox" id="UserRoles" name="UserRoles" value="<%: role.UserRoleName %>"
<% if (role.UserisinRole) { %>
checked="checked"
<% } %>
/></td>
</tr>
<% } %>
...
<input type="submit" name="Submit" value="Update Roles" /></td>
<% } %>
My Controller:
[HttpPost]
public ActionResult UpdateAllRoles(UserAdminModel model)
{
Guid uid = new Guid( Request["UserId"]);
return RedirectToAction("Index", "MyController");
}
The UserId comes through fine but the rest of the model is null. Any help would be appreciated.
You need to use a for loop so that your form controls have the correct name attributes to bind to your model (I'll leave it to you to convert from razor to aspx)
#using (Html.BeginForm("UpdateRoles", "UserAdmin", FormMethod.Post))
{
#Html.HiddenFor(m => m.UserId) // or add this as a route parameter in BeginForm()
...
for(int i = 0; i < Model.UserRoles.Count; i++)
{
#Html.HiddenFor(m => m.UserRoles[i].RoleId)
#Html.CheckBoxFor(m => m.UserRoles[i].UserisinRole)
#Html.LabelFor(m => m.UserRoles[i].UserisinRole, Model.UserRoles[i].UserRoleName)
}
<input type="submit" name="Submit" value="Update Roles" />
}
When you submit the form, model.UserRoles will contain all roles and you can get the selected roles using
var selectedRoles = model.UserRoles.Where(r => r.UserisinRole);
Side note: Using a <table> does not seem appropriate here.
I am trying to run a simple Spring 3 MVC project to save form data but when I submit data the page goes to add.html with empty forms and no data is save in Mysql neither it is shown on the page.
Controller
package com.app.a;
/**
* Handles requests for the application home page.
*/
#Controller
#RequestMapping(value="/")
public class HomeController {
#Autowired
private Personrepo personRepo;
#RequestMapping(method=RequestMethod.GET)
public String showForm(ModelMap model){
List<Person> persons = personRepo.getAll();
model.addAttribute("persons", persons);
Person person = new Person();
person.setId(UUID.randomUUID().toString());
model.addAttribute("person", person);
return "home";
}
#RequestMapping(value="/add", method=RequestMethod.POST)
public ModelAndView add(#ModelAttribute(value="person") Person person,BindingResult result){
ModelAndView mv = new ModelAndView("home");
if(!result.hasErrors()) {
personRepo.add(person);
person = new Person();
person.setId(UUID.randomUUID().toString());
mv.addObject("person", person);
}
mv.addObject("persons", personRepo.getAll());
return mv;
}
#RequestMapping(value="/edit", method=RequestMethod.POST)
public ModelAndView edit(#ModelAttribute(value="person") Person person,BindingResult result) {
ModelAndView mv = new ModelAndView("home");
if(!result.hasErrors()) {
personRepo.edit(person);
person = new Person();
mv.addObject("person", person);
}
mv.addObject("persons", personRepo.getAll());
return mv;
}
#RequestMapping(value="/delete", method=RequestMethod.POST)
public ModelAndView update(#ModelAttribute(value="person") Person person,BindingResult result){
ModelAndView mv = new ModelAndView("home");
if(!result.hasErrors()) {
personRepo.delete(person.getId());
//personRepo.delete(person);
person = new Person();
mv.addObject("person", person);
}
mv.addObject("persons", personRepo.getAll());
return mv;
}
}
Home .jsp
<%# page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%#taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<%#taglib prefix="core" uri="http://java.sun.com/jsp/jstl/core"%>
<%# taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<%# taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%# taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
</head>
<body>
<table align="center">
<c:forEach items="${persons}" var="person">
<tr>
<td>Welcome:</td>
<td><c:out value="${person.firstName}" /></td>
</tr>
<tr>
<td>Your lastname:</td>
<td><c:out value="${person.lastName}" /></td>
</tr>
</c:forEach>
</table>
<form:form modelAttribute="person" method="post" action="add.html">
<form:hidden path="id" />
<table>
<tr>
<td><form:label path="firstName">First Name:</form:label></td>
<td><form:input path="firstName" /></td>
</tr>
<tr>
<td><form:label path="lastName">Last Name</form:label></td>
<td><form:input path="lastName" /></td>
</tr>
<tr>
<td><form:label path="money">Money</form:label></td>
<td><form:input path="money" /></td>
</tr>
</table>
<input type="submit" value="Save" />
</form:form>
</body>
</html>
From the looks of it, unless you are doing content negotiation, you don't have a controller method mapped to the URL add.html.
Compare:
<form:form modelAttribute="person" method="post" action="add.html">
and
#RequestMapping(value="/add", method=RequestMethod.POST)
Make it
<form:form modelAttribute="person" method="post" action="add">
and you should be fine.