I am using ASP MVC 4.0 and would like to understand the basics of custom validation. In this particular case, the model is not at all strongly typed with the controller or view so I need something different.
What I'd like to do is accept a new username on signup to my service, look in the database, and re-present the original form with a message if that username is taken.
This is my input form:
#{
ViewBag.Title = "Index";
}
<h2>New account</h2>
<form action= "#Url.Action("submitNew", "AccountNew")" method="post">
<table style="width: 100%;">
<tr>
<td>Email:</td>
<td> </td>
<td><input id="email" name="email" type="text" /></td>
</tr>
<tr>
<td>Password:</td>
<td> </td>
<td><input id="password" name="password" type="password" /></td>
</tr>
<tr>
<td>Confirm Password:</td>
<td> </td>
<td><input id="passwordConfirm" name="passwordConfirm" type="password" /></td>
</tr>
<tr>
<td></td>
<td> </td>
<td><input id="Submit1" type="submit" value="submit" /></td>
</tr>
</table>
</form>
and here is my controller method upon submit:
public ActionResult submitNew()
{
SomeService service = (SomeService)Session["SomeService"];
string username = Request["email"];
string password = Request["password"];
bool success = service.guestRegistration(username, password);
return View();
}
If success is false, I'd just like to re-present the form with a message indicating so. I'm missing the basics of this error flow. Would you please help? thanks in advance.
You could add a ViewBag item
bool success = service.guestRegistration(username, password);
if (!success)
{
ViewBag.Error = "Name taken..."
}
return View();
But you should create a view model...
public class ViewModel
{
public string UserName {get; set;}
//...other properties
}
...strongly type your view and use the built in html helpers...
#model ViewModel
//...
#using BeginForm("SubmitNew", "AccountNew", FormMethod.Post)()
{
//...
<div>#Html.LabelFor(m => m.Username)</div>
<div>#Html.TextBoxFor(m => m.Username)</div>
<div>#Html.ValidationMessageFor(m => m.Username)</div>
}
...and leverage ModelState in the controller
[HttpPost]
public ActionResult SubmitNew(ViewModel viewModel)
{
if(ModelState.IsValid)
{
SomeService service = (SomeService)Session["SomeService"];
bool success = service.guestRegistration(viewModel.username, viewModel.password);
if (success)
{
return RedirectToAction("Index");
}
ModelState.AddModelError("", "Name taken...")"
return View(viewModel);
}
}
...or even write your own validator and just decorate your model property, eliminating the need to check success in the controller.
Related
This question already has answers here:
Post an HTML Table to ADO.NET DataTable
(2 answers)
Closed 4 years ago.
I can't get the values in controller from the post method used on the view and I don't know why. I saw a lot of forums and I thought that I was doing the right thing, but it doesn't seem to be the correct way to do because I can't get the values.
First I was using just the DisplayFor but that just shows and not pass the values then I join the HiddenFor to use that like an input like I saw in other forums. But the model always returns zero values.
AtribuiçãoController:
public IActionResult Create(List<Main> main)
{
return RedirectToAction(nameof(Index));
}
Index.cshtml:
#model ModelsLibrary.Main
<form asp-action="Create">
<table class="table">
<thead>
<tr>
<th>
<label>Disciplina</label>
</th>
<th>
<label>Turno</label>
</th>
<th>
<label>Tipologia</label>
</th>
<th>
<label>Docente</label>
</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.Turno)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.MetaDisciplina.Nome)
#Html.HiddenFor(modelItem => item.MetaDisciplina.Nome)
</td>
<td>
#Html.DisplayFor(modelItem => item.LetraTurno)
#Html.HiddenFor(modelItem => item.LetraTurno)
</td>
<td>
#Html.DisplayFor(modelItem => item.Tipologia.Tipo)
#Html.HiddenFor(modelItem => item.Tipologia.Tipo)
</td>
<td>
<select asp-for="Docente" asp-items="ViewBag.Docente"></select>
</td>
</tr>
}
</tbody>
</table>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</form>
Index Method
[HttpGet]
public IActionResult Index()
{
var Turno = new List<Turno>();
Main main = new Main();
main.Turno = Turno;
ViewData["Ano"] = new SelectList(_context.AnoLetivo, "Ano", "Ano");
return View(main);
}
Hi have a problem binding the values to the controller. The method create should get values in the attributes but i don't know why and heaven don't know why the values not pass... I not pass a list to the view but i want one list in the create method.... If someone can help.
Your problem is in your Controller. Your create method do redirect to your Index method but you don't pass your List main
I think your code should be like this:
[HttpGet]
public ActionResult Index()
{
List<Main> main = (List<Main>)TempData["yourData"];
if (main == null)
{
main = new List<Main>();
}
return View("Index", main);
}
public IActionResult Create(List<Main> main)
{
TempData["yourData"] = main;
return RedirectToAction(nameof(Index));
}
The way your want to redirect your data is called the POST-REDIRECT-GET pattern. If your want more information about it, visit this website : https://andrewlock.net/post-redirect-get-using-tempdata-in-asp-net-core/
--------------------EDIT--------------------
You have a binding problem while passing data from View to Controller. You can use an TempData in your View to pass your complete Model to your controller:
#model List<WebApplication1.Models.TestObject>
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#foreach (var item in Model)
{
#Html.DisplayFor(m => item.ToDisplay)
}
<form action="/Home/Create" method="post">
<button type="submit">Submit</button>
</form>
<script>
$('form').submit(function() {
#{ TempData["FullModel"] = Model; }
});
</script>
In this exemple, i use a jquery script to create a TempData and send it to the controller
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
List<TestObject> main = (List<TestObject>)TempData["yourData"];
if (main == null)
{
main = new List<TestObject>();
main.Add(new TestObject("1"));
main.Add(new TestObject("2"));
main.Add(new TestObject("3"));
main.Add(new TestObject("4"));
main.Add(new TestObject("5"));
}
return View("Index", main);
}
[HttpPost]
public ActionResult Create()
{
List<TestObject> main = (List<TestObject>)TempData["FullModel"];
TempData["yourData"] = main;
return RedirectToAction(nameof(Index));
}
}
Hope it was helpfull
I am having some trouble. I have a view as below
#model IEnumerable<Tipstr.Models.Posts>
<div class ="mainC">
<div class = "leftContent">
#Html.Partial("_LeaderboardPartial")
</div>
<div class = "rightContent">
#foreach (var item in Model) {
<h1 class ="ptitle">
#Html.DisplayFor(modelItem => item.title)
</h1>
<p class ="date">
posted #Html.DisplayFor(modelItem => item.date)
</p>
<p class ="post">
#Html.DisplayFor(modelItem => item.body)
</p>
<hr />
}
</div>
</div>
<div class="Cpad">
<br class="clear" /><div class="Cbottom"></div><div class="Cbottomright">
</div>
</div>
And I have a partial, _LeaderboardPartial, as shown
#model IEnumerable<Tipstr.Models.Leaderboard>
<table id="pattern-style-a" summary="Meeting Results">
<thead>
<tr>
<th scope="col">Username</th>
<th scope="col">Tipster Score</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model) {
<tr>
<td>#Html.DisplayFor(modelItem => item.userName)</td>
<td> #Html.DisplayFor(modelItem => item.score)</td>
</tr>
}
</tbody>
</table>
I can't seem to get it to work I think it has something to do with passing in one model from the layout and then another from the partial. How can I get around this? I get the following error:
The model item passed into the dictionary is of type 'System.Collections.Generic.List1[Tipstr.Models.Posts]', but this dictionary requires a model item of type 'System.Collections.Generic.IEnumerable1[Tipstr.Models.Leaderboard]'.
As #David Tansey said, you can have a view model that contains a reference to the two types
public class MyViewModel
{
public MyViewModel(IEnumerable<Posts> posts,
IEnumerable<Leaderboard> leaderboard)
{
//you can add null checks to ensure view model invariants
this.Posts = posts;
this.Leaderboard = leaderboard;
}
public IEnumerable<Posts> Posts { get; private set; }
public IEnumerable<Leaderboard> Leaderboard{ get; private set; }
}
And in your main view you will call Html.Partial like this
#model MyViewModel
<div class ="mainC">
<div class = "leftContent">
#Html.Partial("_LeaderboardPartial", this.Model.Leaderboard)
</div>
....
If you do this, you should change the foreach to iterate through Model.Posts instead of Model
#foreach (var item in this.Model.Posts)
This line of code:
#Html.Partial("_LeaderboardPartial") in the first view implies sending along its own model IEnumerable<Tipstr.Models.Posts> to the partial.
The partial expects a reference to type IEnumerable<Tipstr.Models.Leaderboard>.
Perhaps you need a view model that contains a reference to one of each of these types?
I have a SQL Server 2012 in which I have AWARD table with two columns TITLE and MONTH. TITLE is varchar(256) and cannot be NULL. MONTH is int and can be NULL.
With VS2012 Ultimate and EF 5.0.0, the TextBoxFor helper in MVC4 app is not producing validation (data-val="required" and data-val-required="required message") for the TITLE columne above, but in the same View, MONTH is getting the correct validation markup. The .edmx designer does show TITLE is NonNullable, BUTT, the automatically generated AWARD.cs file does not have the [Required]
attribute for the TITLE column.
What can I try?
#model MyProject.Models.AWARD
#{
ViewBag.Title = "Add Award";
Layout = "~/Views/Shared/_EditorLayout.cshtml";
}
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Add Award</legend>
<table>
<tr>
<td>
#Html.LabelFor(model => model.TITLE)
</td>
<td>
#Html.TextAreaFor(model => model.TITLE)
<br />#Html.ValidationMessageFor(model => model.TITLE)
</td>
</tr>
<tr>
<td>
#Html.LabelFor(model => model.MONTH)
</td>
<td>#Html.DropDownListFor(model => model.MONTH, new SelectList(MyProject.Models.Helpers.Months, "key","value"), "[Not Selected]")
<br />#Html.ValidationMessageFor(model => model.MONTH)
</td>
</tr>
<tr>
<td>
<input type="submit" value="Add" />
</td>
<td>
#Html.ActionLink("Cancel", "Index", null, new { #class = "cancel-button" })</td>
</tr>
</table>
</fieldset>
}
You shouldn't really be binding your views directly to your data mapping entities. You should create view model classes to wrap the data you pass to and from your view and then populate your data objects from the controller.
You can then perform the required validation on your view model without affecting your generated mapping classes.
Model
public class AwardViewModel
{
[Required, StringLength(30)]
public string Title { get; set; }
....
}
View
#model AwardViewModel
#using (Html.BeginForm()) {
#Html.EditorFor(m => m.Title)
#Html.ValidationMessageFor(m => m.Title)
...
}
Controller
[HttpPost]
public ActionResult Create (AwardViewModel model)
{
/* Create new AWARD in entity context, populate
with relevant fields from model and commit. */
}
I have a simple loginform based on the following modelitem
public class LogOnModelItem
{
[Required(ErrorMessage="Brukernavn er påkrevd")]
[DisplayName("Brukernavn")]
public string UserName { get; set; }
[Required(ErrorMessage="Passord er påkrevd")]
[DataType(DataType.Password)]
[DisplayName("Passord")]
public string Password { get; set; }
[DisplayName("Husk meg?")]
public bool RememberMe { get; set; }
}
The view is :
<h2>Login</h2>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<table>
<tr>
<td>#Html.LabelFor(model => model.UserName)</td>
<td>
#Html.EditorFor(model => model.UserName)
#Html.ValidationMessageFor(model => model.UserName)
</td>
</tr>
<tr>
<td>#Html.LabelFor(model => model.Password)</td>
<td>
#Html.EditorFor(model => model.Password)
#Html.ValidationMessageFor(model => model.Password)
</td>
</tr> <tr>
<td>#Html.LabelFor(model => model.RememberMe)</td>
<td>
#Html.EditorFor(model => model.RememberMe)
#Html.ValidationMessageFor(model => model.RememberMe)
</td>
</tr>
</table>
<p>
<input type="submit" value="Create" />
</p>
}
The controller:
public ActionResult Login(LogOnModelItem lmi)
{
return View(lmi);
}
When I load this up in the browser the validation is apparently run on pageload. This then outlines the username and password in red, with the error message supplied after each line. I have 3 questions for this, in order of importance:
1: How do I make sure the validation is not run until after the user presses submit.
2: How do I display the validation summary?
3: How do I make the "username" field choosen by default and ready to accept input?
Answer to #1:
public ActionResult Login()
{
return View();
}
[HttpPost]
public ActionResult Login(LogOnModelItem lmi)
{
return View(lmi);
}
Answer to #2
#Html.ValidationSummary(false)
Answer to #3
<script type="text/javascript">
$(document).ready(function () {
$('#UserName').focus();
});
</script>
1: How do I make sure the validation is not run until after the user presses submit
This should already be the case. If you have a GET action rendering this form and supplying a view model there won't be any validation errors. It's the POST action that would show the validation messages and if you have client validation enabled it could also happen onblur.
2: How do I display the validation summary?
You already did: #Html.ValidationSummary(true)
3: How do I make the "username" field choosen by default and ready to accept input?
You could use javascript:
$(function() {
$('#UserName').focus();
});
<form action="/Villa/Add" method="post">
<table>
<tr>
<td>
Name:
</td>
<td>
<%= Html.TextBox("name") %>
<%= Html.ValidationMessage("Name") %>
</td>
</tr>
<tr>
<td>
</td>
<td>
<input type="submit" value="Add" />
</td>
</tr>
</table>
</form>
My form is above, how do I retrieve the values in my controller?
Thanks a lot in advance! Hard to find the right material because of different Previews of MVC being released and being different.
This works for ASP.Net MVC Beta.
public ActionResult Add( string name ) {
....
}
or
public ActionResult Add( FormCollection form ) {
string name = form["Name"];
}
or
public ActionResult Add( [Bind(Prefix="")]Villa villa ) {
villa.Name ...
}
Have you tried something like this? Pseudocode...
public class VillaController : Controller
{
public ActionResult Add(string name)
{
// Code...
}
}
It belongs to your url routes, you defined.
In your case the form ist looking for an controller named "Villa" and the action inside of it named "Add".
Maybe you should read ScottGu's blog post: http://weblogs.asp.net/scottgu/archive/2008/09/02/asp-net-mvc-preview-5-and-form-posting-scenarios.aspx