I have a simple cshtml page in MVC 4 as following:
#model MvcApplication2.ViewModels.UserViewModel
<html>
<head>
<meta name="viewport" content="width=device-width" />
<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>
</head>
<body>
<div>
#using (Html.BeginForm("Add", "User", FormMethod.Post))
{
#Html.ValidationMessageFor(m => m.FirstName)
#Html.TextBoxFor(m => m.Ime)
<br />
#Html.ValidationMessageFor(m => m.LastName)
#Html.TextBoxFor(m => m.LastName)
<br />
<input id="btnSubmit" type="submit" value="Add user" />
}
</div>
</body>
</html>
And this is my UserViewModel class:
public class UserViewModel
{
[Required(ErrorMessage = "This field is required.")]
public string FirstName;
[Required(ErrorMessage = "This field is required.")]
public string LastName;
}
And this is the action "add" in the controller:
[HttpPost]
public ActionResult Add(User s)
{
if(ModelState.IsValid)
{
Connection.dm.User.Add(s);
Connection.dm.SaveChanges();
}
return RedirectToAction("Index");
}
The issue here is that I get no error messages at all if the user hasn't entered something in the 2 textbox fields... What could it be?
in reference to using try.. catch.. blocks, you could do something like this:
[HttpPost]
public ActionResult Add(User s)
{
if(ModelState.IsValid)
{
try{
Connection.dm.User.Add(s);
Connection.dm.SaveChanges();
}
catch(NullReferenceException)
{
ModelState.AddModelError("Ime", "Please enter something"); // for the first name validation
return View(s);
}
return RedirectToAction("Index");
}
or you could use some conditional logic like this:
[HttpPost]
public ActionResult Add(User s)
{
if(ModelState.IsValid)
{
if(string.IsNullOrWhiteSpace(s.Ime)
{
ModelState.AddModelError("Ime", "Please enter something");
return View(s);
}
else
{
Connection.dm.User.Add(s);
Connection.dm.SaveChanges();
}
}
return RedirectToAction("Index");
}
Related
When clicking a button I am calling a web api with ajax. My form is using JqueryVal, to make form validations, according to my viewmodel data annotations.
My problem is that when I click the button "Listo" in my form, it calls my API, inspite of jqueryval is marking an error( selecting a file is required)
This is my code:
My viewmodel that contains data annotations(the dataannotations are used along with the jquery.validate.js and jquery.validate.unobtrusive.js. As you can see, it is working, but is not preventing the API from being called):
public class CursoViewModel
{
public Guid Id { get; set; }
[MaxLength(125)]
public string Titulo { get; set; }
[Required]
public string Descripcion { get; set; }
[Required(ErrorMessage ="selecciona una imagen para tu curso")]
[DataType(DataType.Upload)]
public HttpPostedFileBase Imagen { get; set; }
}
The class posted to my api
public class person
{
public string name { get; set; }
public string surname { get; set; }
}
The Api code
[HttpPut]
[Route("api/ProfesorCurso/test")]
public string Put(person p)
{
return p.name + p.surname;
}
My View
#model project.ViewModels.CourseViewModel
<form id="Editcurso" method="post" action="#">
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "Please fix the following errors.")
<div class="container">
<div class="form-group">
#Html.LabelFor(c=>c.Titulo)
#Html.TextBoxFor(c => c.Titulo, new {id="titulo", #class="form-control"})
#Html.ValidationMessageFor(m => m.Titulo)
</div>
<div class="form-group">
#Html.LabelFor(c => c.Descripcion)
#Html.TextAreaFor(c => c.Descripcion, new {id="descripcion", #class = "form-control" })
#Html.ValidationMessageFor(m => m.Descripcion)
</div>
<div class="thumbnail" id="imagencurso"></div>
<div class="form-group">
#Html.LabelFor(m => m.Imagen)
#Html.TextBoxFor(m => m.Imagen, new {id="imagen" ,type = "file", data_rule_validCustomer = "true" })
#Html.ValidationMessageFor(m => m.Imagen)
</div>
<button id="submiter" type="submit" class="btn btn-primary">Listo!</button>
</div>
</form>
The scripts in the view
#section scripts
{
#Scripts.Render("~/bundles/jqueryval")
<script>
$(document).ready(function () {
$("#submiter").click(function () {
jQuery.support.cors = true;
var person = new Object();
person.name = "Sourav";
person.surname = "Kayal";
$.ajax({
url: '/api/ProfesorCurso/test',
type: 'PUT',
dataType: 'json',
data: person,
success: function (data) {
console.log(data);
return false;
},
error: function (x, y, z) {
alert('error al postear');
return false;
}
});
});
});
</script>
}
What can I do to prevent ajax to call my api when clicking my form button, if there are Jquery validation errors?
thanks
You should be handling the .submit() event of the form, and then your can check .valid(), and if not cancel the ajax call. Note you should also be cancelling the default submit.
$('#Editcurso').submit(e) {
e.preventDefault(); // prevent the default submit
if (!$(this).valid()) {
return; // exit the function and display the errors
}
....
$.ajax({
....
});
}
As a side note, there is no point adding new { id="titulo" } etc - the HtmlHelper methods that generate form controls already add an id attribute based on the property name
I tried to minimize huge problem to a small one so I created the new sample web project; mvc-empty in VS. I created one view named „Index” in Home controller. Index view code:
#model WebApplication16.ViewModels.Home.IndexVM
#{
ViewBag.Title = "Index";
}
#Html.Partial("~/Views/Home/_Orders.cshtml", Model.Orders)
#section scripts{
<script src="~/Scripts/jquery.validate.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.js"></script>
<script src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
<script>
$("#Type").change(function () {
$('#order-current > img').remove();
var currentOrder = "#Type_" + $("#Type").find('option:selected').text();
var $img = $(currentOrder).clone();
$img.removeClass("hidden");
$("#order-current").append($img);
$("#ajax-form").submit();
});
</script>
}
Home controller code:
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
IndexVM dataVM = new IndexVM();
GetControlsDataSource(dataVM.Orders);
return View(dataVM);
}
private static void GetControlsDataSource(OrdersVM dataVM)
{
List<SelectListItem> typeControlDataSource = new List<SelectListItem>();
foreach (var en in Enum.GetValues(typeof(TypeEnum)))
{
SelectListItem item = new SelectListItem();
item.Text = en.ToString();
item.Value = ((int)en).ToString();
typeControlDataSource.Add(item);
}
dataVM.TypeControlDataSource = typeControlDataSource;
}
[HttpPost]
public ActionResult Pay(IndexVM dataVM)
{
GetControlsDataSource(dataVM.Orders);
if (ModelState.IsValid)
{
dataVM.Orders.Info = "Info bla bla bla";
return PartialView("~/Views/Home/_Orders.cshtml", dataVM.Orders);
}
else
{
return View(dataVM);
}
}
}
There is also a partial view named “_Orders”, which is rendered on the Index view.The code of _Orders partial view:
#model WebApplication16.ViewModels.Home.OrdersVM
#using (Ajax.BeginForm("Pay", "Home", new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "result",
}, new { id = "ajax-form" }))
{
<div id="result">
<div id="order-current">
</div>
<div>
#Html.TextBoxFor(x => x.Name, new { #class = "form-control", style = "margin-top:10px;", id = "Name" })
#Html.ValidationMessageFor(x => x.Name)
</div>
<div>
#Html.DropDownListFor(x => x.Type, Model.TypeControlDataSource, new { #class = "form-control", style = "margin-top:10px;", id = "Type", })
#Html.ValidationMessageFor(x => x.Type)
</div>
<div>
<p>#Model.Info</p>
</div>
<button type="submit" class="btn btn-primary" name="ok"> OK</button>
</div>
}
<div id="orders-container">
<img id="Type_I" src="~/App_Images/Type_I.png" class="img-responsive hidden" />
<img id="Type_II" src="~/App_Images/Type_II.png" class="img-responsive hidden" />
<img id="Type_III" src="~/App_Images/Type_III.png" class="img-responsive hidden"/>
</div>
Index model is described by class IndexVM:
public class IndexVM
{
public IndexVM()
{
this.Orders = new OrdersVM();
}
public OrdersVM Orders { get; set; }
}
_Orders model is described by class OrdersVM:
public class OrdersVM
{
[Required]
public string Name { get; set; }
public string Info { get; set; }
[Required]
public TypeEnum Type { get; set; }
public List<SelectListItem> TypeControlDataSource { get; set; }
}
public enum TypeEnum
{
I,
II,
III
}
After change of value in DropDownListFor control with id=”Type”, the picture from hidden field should be injected by jquery code located in Index view into container with id=”order-current” and after that operation the ajax-form should be submitted. It works properly but after calling
return PartialView("~/Views/Home/_Orders.cshtml", dataVM.Orders);
from the HomeController, the field Info is updated properly but the injected picture from the “order-current” div container is gone. I tried to see what’s wrong in Google Chrome using F12 button and there are no errors but appeared an infinite loop in “browserLink” script. I can’t explain why.
All I want is to see the injected picture in container with id=”order-current after submitting the ajax-form. How to do this and what I did wrong?
Thanks to my friend I finally solved the problem. After updating the “result” container, all events added by jQuery to controls located in this container are unpinned. That’s why it crashes.
The way to make it work correctly is to create a function and pin it to the event OnComplete of AjaxBeginForm. After each update of the result container via ajax, this function is called. I also made a small mistake in Home controller because I inserted a wrong view model class. After all changes, it looks like this;
Home controller code:
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
IndexVM dataVM = new IndexVM();
GetControlsDataSource(dataVM.Orders);
return View(dataVM);
}
private static void GetControlsDataSource(OrdersVM dataVM)
{
List<SelectListItem> typeControlDataSource = new List<SelectListItem>();
foreach (var en in Enum.GetValues(typeof(TypeEnum)))
{
SelectListItem item = new SelectListItem();
item.Text = en.ToString();
item.Value = ((int)en).ToString();
typeControlDataSource.Add(item);
}
dataVM.TypeControlDataSource = typeControlDataSource;
}
[HttpPost]
public ActionResult Pay(OrdersVM dataVM)
{
GetControlsDataSource(dataVM);
if (ModelState.IsValid)
{
dataVM.Info = "Info bla bla bla";
return PartialView("~/Views/Home/_Orders.cshtml", dataVM);
}
else
{
return View(dataVM);
}
}
}
Index view:
#model WebApplication16.ViewModels.Home.IndexVM
#{
ViewBag.Title = "Index";
}
#Html.Partial("~/Views/Home/_Orders.cshtml", Model.Orders)
<div id="orders-container">
<img id="Type_I" src="~/App_Images/Type_I.png" class="img-responsive hidden" />
<img id="Type_II" src="~/App_Images/Type_II.png" class="img-responsive hidden" />
<img id="Type_III" src="~/App_Images/Type_III.png" class="img-responsive hidden" />
</div>
#section scripts{
<script src="~/Scripts/jquery.validate.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.js"></script>
<script src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
<script>
function imageOnChangeEvent() {
$("#ajax-form").submit();
}
function OnCompleteAjaxForm() {
$('#order-current > img').remove();
var currentOrder = "#Type_" + $("#Type").find('option:selected').text();
var $img = $(currentOrder).clone();
$img.removeClass("hidden");
$("#order-current").append($img);
}
</script>
}
_Orders partial view code:
#model WebApplication16.ViewModels.Home.OrdersVM
<div id="result">
#using (Ajax.BeginForm("Pay", "Home", new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "result",
OnComplete = "OnCompleteAjaxForm()"
}, new { id = "ajax-form" }))
{
<div id="order-current">
</div>
<div>
#Html.TextBoxFor(x => x.Name, new { #class = "form-control", style = "margin-top:10px;", id = "Name" })
#Html.ValidationMessageFor(x => x.Name)
</div>
<div>
#Html.DropDownListFor(x => x.Type, Model.TypeControlDataSource, new { #class = "form-control", style = "margin-top:10px;", id = "Type", onchange = "imageOnChangeEvent()" })
#Html.ValidationMessageFor(x => x.Type)
</div>
<div>
<p>#Model.Info</p>
</div>
<button type="submit" class="btn btn-primary" id="button_ok" name="ok"> OK</button>
}
</div>
I have JS function that is basically
<script type="text/javascript">
function doSomething() {
var s = 'some data'
return s; }
</script>
and
#using (Html.BeginForm(new { data_to_send = x))
{
//some form controls that are sent to controller via model
}
Is it possible, and how, to assign value returned by doSomething function to x variable in form?
I don't need x in my model, because it won't go to database. It's just some additional info from user, how to manipulate data in model before saving to database.
edit: Controller action is
public actionresult MyController(string data_to_Send, model) {}
In the View:
#using (Html.BeginForm())
{
#Html.HiddenFor(model => model.X1)
#Html.HiddenFor(model => model.X2)
}
In the Model:
public class YourModel
{
public string X1 { get; set; }
public string X2 { get; set; }
}
In the Controller:
[HttpPost]
public ActionResult Index(YourModel model)
{
string x1 = model.X1;
string x2 = model.X2;
return View(model);
}
The form has to be posted in order to do what you are looking for.
You can post the model along with PostedDateValue
#using (Html.BeginForm("ActionMethod", "Controller", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-field required">
<label for="Posted Data">Posted Data</label>
<input id="txtPostedData" name="PostedDateValue" >
<input type="submit" class="gradient" value="SUBMIT" />
</div>
}
)
Controller
public ActionResult ActionMethod(string PostedDateValue)
{
}
I have a layout page which is used by my MVC site. It has no models or any related controller methods.
But now I want to add a "Search" box at the top. I can't add a normal "Form" because it get's over ridden, or overrides any other forms on the content pages. So I was thinking I might have to do it with Javascript... call a javascript function that then sends the query to a controller, and then moves the user to the result screen.
Is this the right way to do it? Or can I somehow use a normal controller method call from my layout page?
Let's assume you have these models:
public class SearchModel
{
public string SearchTerm { get; set; }
}
public class LoginModel
{
public string UserName { get; set; }
public string Password { get; set; }
}
And that you also had these controllers:
public class HomeController : Controller
{
public ActionResult Search(SearchModel model)
{
return View();
}
}
public class AccountController : Controller
{
public ActionResult Login(LoginModel model)
{
return View();
}
}
You could use something like this as a layout view:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>#ViewBag.Title</title>
</head>
<body>
#using(Html.BeginForm("search", "home", FormMethod.Post, new {}))
{
<input type="search" name="SearchTerm" placeholder="Find..." />
<button type="submit">GO</button>
}
#if(!Request.IsAuthenticated)
{
using(Html.BeginForm("login", "account", FormMethod.Post, new {}))
{
<input type="text" name="UserName" placeholder="..." />
<input type="password" name="Password" placeholder="..." />
<button type="submit">GO</button>
}
}
#RenderBody()
</body>
</html>
The model binder is smart enough to match up request parameters to the correct model properties, even if you don't use the #Html helpers to create your HTML controls. The problem you'll run into is what to do with, say, invalid login attempts. Seems like the typical workflow is to have a normal login action - that lets you dump the user to a proper login page if they enter invalid credentials in, say, a navbar login form.
Another option is to embed the search and login forms as child actions:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>#ViewBag.Title</title>
</head>
<body>
#Html.Action("search", "home")
#Html.Action("login", "account")
#RenderBody()
</body>
</html>
If you go that route, you would modify the controller to return partial views. Here's one implementation of an AccountController:
public class AccountController : Controller
{
[HttpGet, AllowAnonymous]
public ActionResult Login(string returnUrl = "")
{
return View();
}
[HttpPost, AllowAnonymous]
public ActionResult Login(LoginModel model)
{
if(ModelState.IsValid)
{
/* valid user credentials */
return RedirectToAction("index", "home");
}
return View(model);
}
[AllowAnonymous, ChildActionOnly]
public ActionResult NavbarLogin()
{
return PartialView();
}
}
Notice the last action method; marking it as ChildActionOnly prevents the action from being directly requested by the browser. The view for that method might look like this:
#model Your.Fully.Qualified.Namespace.Models.LoginModel
#using(Html.BeginForm("login", "account", FormMethod.Post, new { }))
{
#Html.LabelFor(m => m.UserName)
#Html.TextBoxFor(m => m.UserName)
#Html.ValidationMessageFor(m => m.UserName)
#Html.LabelFor(m => m.Password)
#Html.PasswordFor(m => m.Password)
#Html.ValidationMessageFor(m => m.Password)
<button type="submit">Login</button>
}
The advantage of the strongly-typed helpers is that MVC will create the validation data- attributes that the Unobtrusive Validation library leverages; include the jqueryval bundle on the layout and any view with strongly-typed helpers gets client-side validation automatically.
In this example, the "navbar login" posts to the normal login action, so invalid credentials will result in the user being shown the login page, rather than the page they were originally viewing.
Imagine below MVC parent model:
Model:
Namespace MyProject.SampleModel
{
public class ViewModelExample {
public FirstModel BoolValues { get; set; }
public SecondModel NamesValues { get; set; }
}
}
Namespace MyProject.SampleModel
{
public class FirstModel
{
public bool MyBoolean1 { get; set; }
public bool MyBoolean2 { get; set; }
}
public class SecondModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
}
View:
#model MyProject.SampleModel.ViewModelExample
#using (Html.BeginForm("MyAction", "MyController", FormMethod.Post, htmlAttributes: new { id = "Myform" }))
{
(...)
#Html.CheckBoxFor(m => m.BoolValues.MyBoolean1)
(...)
<input id="submitButton" type="button" value="Add" onclick="InitiateSequence();" />
(...)
}
<script type="text/javascript" src="~/Scripts/jquery-1.7.1.min.js"></script>
<script type="text/javascript">
(...)
function InitiateSequence()
{
// Do some stuff
}
(...)
function ScriptSample() {
if(#(Model.BoolValues.MyBoolean1 ? "true" : "false")
{
// It's true, do some stuff
}
else
{
// It's false, do some stuff
}
}
</script>
Controller:
public ActionResult MyAction(ViewModelExample model)
{
model.FirstModel = new TestsModel(); // I do not instantiate SecondModel as in the view for this controller i do not use it
return View(model);
}
Page is loading correctly, but when I click on the button it says javascript function InitiateSequence is not defined.... what's happening?
That could be because most possibly the function appears where it is not supposed to be. Also don't use inline attributes to bind the handlers, use event binding instead of inline handler.
<input id="submitButton" type="button" value="Add" />
and
<script type="text/javascript">
(...) //whatever code
$(function(){
$('#submitButton').on('click', InitiateSequence);
});