I'm trying to implement remote validation in MVC. I have read several tutos and questions already posted here, but there is no answer.
Controller :
public class GroupsController: Controller
{
[HttpPost]
public ActionResult TestRemoteValidation(string Name)
{
return Json(false);
}
}
View :
#using (Html.BeginForm("Index", "Defaults", FormMethod.Post))
{
#Html.TextBoxFor(model => model.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Name, "", new { #class = "text-danger" })
<input type="submit" class="btn btn-primary" value="Enregistrer" />
}
And Model :
public class Group
{
[Key]
public int Id { get; set; }
[Display(Name = "Nom du Groupe")]
[Required]
[Remote("TestRemoteValidation", "Groups", HttpMethod = "POST", ErrorMessage = "Remote fired")]
//[CustomRemoteValidation("TestRemoteValidation", "Groups", AdditionalFields = "Id")]
public string Name { get; set; }
public virtual ICollection<ApplicationUser> ApplicationUsers { get; set; }
}
Generated HTML Code:
<input data-val="true" data-val-remote="Remote fired" data-val-remote-additionalfields="*.Name" data-val-remote-type="POST" data-val-remote-url="/Groups/TestRemoteValidation" data-val-required="Le champ Nom du Groupe est requis." htmlAttributes="{ class = form-control }" id="Name" name="Name" type="text" value="" />
I'm using Metadata because it's an entity-->not the problem, I checked with an other ViewModel and it's the same.
[Required] and [StringLength(10)] are fired.
When I put a breakpoint in TestRemoteValidation, nothing happens.
For instance I'm able to perform the remote validation with a custom remote attribute class and Model.IsValid override, but I don't understand why this way doesn't work. Any idea?
Assuming Vehicule is the #model being used in the view
#model Vehicule
then the controller should expected that model
public class DefaultsController : Controller {
[HttpGet]
public ActionResult Index() {
var model = new Vehicule();
return View(mdoel);
}
[HttpPost]
public ActionResult Index(Vehicule model) {
if(ModelState.IsValid) {
//...do something
//..possible redirect
}
//if we get this far something is wrong with data
return View(model);
}
}
The model binder will take validation into account when binding the model from the request.
add
[AllowAnonymous]
to
[HttpPost]
public ActionResult TestRemoteValidation(string Name)
Please make sure that you have included below libraries and in correct order in your view
<script src="~/scripts/jquery.js"></script>
<script src="~/scripts/jquery.validate.js"></script>
<script src="~/scripts/jquery.validate.unobtrusive.js"></script>
these libraries are required for remote validation to work.
Related
I'm working in a C# project. In my ReglasDispositivos view there are three diferent actions which a client can do: add a device, update a device and delete once. The controller calls ReglasDispositivos() (get) view passing a ClienteReglasDispositivos model. Lets go, for example, to adding a device. How can i post the info that the client is posting?
If my view only have one action, I know that I have to add in the controller [HttpPost] and [HttpGet] ReglasDispositivos() method. But when a view has three diferentes actions?
ClientController:
[HttpGet]
public ActionResult ReglasDispositivos()
{
ClienteReglasDispositivos model = new ClienteReglasDispositivos();
return View(model);
}
ReglasDispositivos view (Adding a device parte) (Im not sure about that BeginForm):
#model SistemaGestion.Models.ClienteReglasDispositivos
...
<div class="view-tables">
<div id="myAdd" class="div-table">
#using (Html.BeginForm("AgregarDispositivo", "Client", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
<fieldset class="fieldset-box">
<div class="editor-field">
#Html.TextBoxFor(model => model.NombreDispositivo, new { Class = "YourBackgroundClass", Placeholder = "Nombre del dispositivo" });
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.HorasDeUso, new { Class = "YourBackgroundClass", Placeholder = "KwPorHora" });
</div>
<div class="editor-field">
#Html.CheckBoxFor(model => model.EsInteligente, new { Class = "YourBackgroundClass" });
</div>
<input type="submit" class="submit-login" value="AGREGAR" />
</fieldset>
}
</div>
ClienteReglasDispositivos
public class ClienteReglasDispositivos
{
[Required]
public string NombreDispositivo { get; set; }
[Display(Name = "Es inteligente?")]
public bool EsInteligente { get; set; }
public double KwPorHora { get; set; }
public float HorasDeUso { get; set; }
public string Condicion { get; set; }
}
You can have multiple actions with identical name due to polymorphysm. Asp decide what action you call by [Http(XX)].
You can have
[HttpGet]
public ActionResult ReglasDispositivos()
{
ClienteReglasDispositivos model = new ClienteReglasDispositivos();
return View(model);
}
[HttpPost]
public ActionResult ReglasDispositivos([FromForm] ClienteReglasDispositivos model)
{
if(ModelState.IsValid(model)) {
//do something
}
return View(model);
}
Also you can call different action name and return same view
[HttpGet]
public ActionResult ReglasDispositivos()
{
ClienteReglasDispositivos model = new ClienteReglasDispositivos();
return View(model);
}
[HttpPost]
public ActionResult ReglasDispositivosPost([FromForm] ClienteReglasDispositivos model)
{
return View("ReglasDispositivos", model);
}
Read more
My ViewModel looks like this:
public class ProjectViewModel
{
public ICollection<Project> Projects { get; set; }
}
My controller:
[HttpPost]
[Route("ChooseProject")]
public ActionResult ChooseProject(Project projects)
{
return RedirectToAction("Index", "Application");
}
And finally my view:
#using ServicePortal.Web.Controllers
#model ServicePortal.Web.Models.ProjectViewModel
#{
Layout = "~/Views/Shared/_Layout.cshtml";
ViewBag.Title = "Choose project";
}
<div class="pure-form pure-form-aligned">
#using (Html.BeginForm("ChooseProject", "Login", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="innerForm formgroup">
#Html.ValidationSummary(true, "", new { #class = "" })
#Html.DropDownListFor((model => model.Projects), new SelectList(Model.Projects,"ProjectKey", "ProjectName"))
<div class="row">
<button type="submit" class="button pull-right">Go on</button>,
</div>
</div>
}
</div>
The dropdownlist is populated with the correct values, but I would like to return either a Project or a string with ProjectKey to the controller. However, when I submit the form and check the value in the parameter in the controller, it is always null, both when I try to return a Project and when I try with string. I don't get any errors. What am I doing wrong?
Try this out:
public class ProjectViewModel
{
public string SelectedProjectKey { get; set; } // string may need to be int depending on what type ProjectKey is in your class
public ICollection<Project> Projects { get; set; }
}
In the view:
#Html.DropDownListFor((model => model.SelectedProjectKey), new SelectList(Model.Projects,"ProjectKey", "ProjectName"))
Your controller action:
public ActionResult ChooseProject(ProjectViewModel model)
This will get you the key of whatever project you selected in model.SelectedProjectKey and you can use that in your controller chooseproject action method to do whatever you intended.
I have a problem saving List items to database. When a field is to be saved(in HTTP POST Create) some of the Lists properties don't have to be saved so I have allowed nullabe for such. However there is one field that I retrieve from the Form and save it. Since the classes are complex I'll restrict the code I'll post here (I'll use one field since the Exception generated is the same).
StringValues is the Class that several List fields inherit from, such as TestPlanChecklist in this case.
public class TestPlanChecklist:StringValues
{
}
public class StringValues
{
[Key]
public int id { get; set; }
public int ChangeRequestsID { get; set; }
public string Value { get; set; }
public ChangeRequests ChangeRequests { get; set; }
}
Part of my Model class
public class ChangeRequests
{
[Required]
public List<TestPlanChecklist> TestPlanChecklist { get; set; }
[Required]
public List<PostActivityChecklist> PostActivityChecklist { get; set; }
[Required]
public List<CMBApproval> CMBApproval { get; set; }
[Required]
public List<TechnicalFeasibility> TechnicalFeasibility { get; set; }
}
In my Create view, this is the code that renders textboxes for TestPlanChecklist field
<div class="form-group">
#Html.LabelFor(model => model.TestPlanChecklist, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<div>
<label class="numbers"> 1 </label>
<input type="text" class="TestPlanChecklist" name="TestPlan" />
<input type="button" value="+" class="roundButton" onclick="add('TestPlanChecklist', 'TestPlan')" />
<input type="button" value="-" class="roundButton" onclick="removeElement('TestPlan')" />
</div>
<div>
<label class="numbers"> 2 </label>
<input type="text" class="TestPlanChecklist" name="TestPlan" />
</div>
<div>
<label class="numbers"> 3 </label>
<input type="text" class="TestPlanChecklist" name="TestPlan" />
</div>
#Html.ValidationMessageFor(model => model.TestPlanChecklist, "", new { #class = "text-danger" })
</div>
</div>
</div>
And HttpPost Create method
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "TestPlanChecklist,PostActivityChecklist,PostActivityChecklist,CMBApproval,TechnicalFeasibility")] ChangeRequests changeRequests,
string[] TestPlan)
{
changeRequests.TestPlanChecklist = new List<TestPlanChecklist>();
foreach (var test in TestPlan)
changeRequests.TestPlanChecklist.Add(new TestPlanChecklist { Value = test });
//SendEmails(TechnicalFeasibility, User.Identity.Name, ChangeUrgency, Priority, DescriptionOfChange, Reason);
//SendEmails(CMBApproval, User.Identity.Name, ChangeUrgency, Priority, DescriptionOfChange, Reason);
if (ModelState.IsValid)
{
db.ChangeRequests.Add(changeRequests);
db.SaveChanges();
return RedirectToAction("List");
}
return View(changeRequests);
}
Kindly note that am just using one field to ask this question, thats why I have removed code for initializing the other List fields.
ModelState.IsValid
returns false. I realize that all the List fields have an error, which states that its impossible to typecast from System.String to the particular class. This is funny since I assign only one field value retrieved from the form and the rest are nullable which makes sense.
Where am I going wrong?
Thanks in advance.
ModelState.IsValid check validation rules, that comes from DataAnnotations in your case [Required] Attribute in ChangeRequests ViewModel.
If you want to use this validation and make some of your List properties nullable you should delete this attribute.
You can check the errors of the validation.
var errores = new List<ModelError>();
foreach (ModelState modelState in ViewData.ModelState.Values)
{
foreach (ModelError error in modelState.Errors)
{
errores.Add(error);
}
}
You get ModelState errors because ChangeRequest has [Required] attributes and gets validated by Mvc. It's unclear to me why you're using that action signature but it's a bad approach. You should rely on ViewModels and not Models directly.
public ActionResult Create(ChangeRequestViewModel viewModel)
{
if(ModelState.IsValid == false) return View(viewModel);
var changeRequest = new ChangeRequest();
foreach(var testPlan in viewModel.TestPlans) {
changeRequest.TestPlanChecklist.Add(new TestPlanChecklist { Value = testPlan }
}
// ...
}
I've got two models.
Model A for example contains basic get set for atrributes (i.e)
[Required]
[StringLength(100)]
[Display(Name = "Comment: ")]
public string Comment { get; set; }
public bool IsSuccess { get; set; }
Model two uses attributes in modal a, i.e
public ModelA Deposit { get; set; }
public ModelA Withdrawal { get; set; }
public ModelA Transfer { get; set; }
In my View all I am doing is using 3 forms in one page with very similar fields.
For same reason Withdrawal and transfer work with no problems. The code is almost identical in all 3
However when I am doing deposit, the ActionResult in the post is not been passed on the model.
public ActionResult Index()
{
SetViewBagAccounts();
var model = new ModelB {};
return View(model);
}
[HttpGet]
public ActionResult Deposit()
{
return View();
}
[HttpPost]
public ActionResult Deposit(ModelB model)
{
int acct = Convert.ToInt32(Request.Form["Accounts"]);
var comment = model.Deposit.Comment;// This causes **NullReferenceException**
}
The error I am getting in http post is NullReferenceExcption.
When I debug, the model being passed to the action method, Deposit, is all empty.
BELOW IS A SAMPLE OF THE VIEW
#model Login.Models.ModelB
#{
ViewBag.Title = "ATM";
}
<h2>ATM</h2>
<div id="leftpanel" style="position:absolute;left:0;width:33%;">
#using (Html.BeginForm("Deposit", "ATM", FormMethod.Post, new {}))
{
<div>
<fieldset>
<legend>Deposit Money</legend>
<div>#Html.LabelFor(u=>u.Deposit.AccountNumber1)</div>
<div>#Html.DropDownList("Accounts", "-- Select Account --")
</div>
<div>#Html.LabelFor(u=>u.Deposit.Amount)</div>
<div>#Html.TextBoxFor(u=>u.Deposit,new {style = "width:150px"})
#Html.ValidationMessageFor(u=>u.Deposit.Amount)
</div>
<div>#Html.LabelFor(u=>u.Deposit.Comment)</div>
<div>#Html.TextAreaFor(u=>u.Deposit.Comment,new {style = "width:250px"})
#Html.ValidationMessageFor(u=>u.Deposit.Comment)
</div>
<input type="submit" value ="Submit" style="width:31%;"/>
<input type="reset" value ="Clear" style="width:31%;"/>
</fieldset>
</div>
}
The div for deposit is pretty much copied again for withdrawal and transfer, only changed bit is :
#using (Html.BeginForm("Withdrawal", "ATM", FormMethod.Post, new {}))
#using (Html.BeginForm("Transfer", "ATM", FormMethod.Post, new {}))
i'm using razor's listboxfor for the first time, but my Model is always null.
after reading similar posts and tryouts it still won't work.
Person.cshtml
#model SampleApp.Web.ViewModel.PersonViewModel
#{
ViewBag.Title = "Welcome";
}
<article>
<p>
Welcome to example page.
</p>
<p>
<div class="container">
//Post data works as expected, controllers create method write to db successfully
#using (Html.BeginForm("Create", "Person", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Personen</legend>
<div class="editor-label">
#* #Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Age)
#Html.ValidationMessageFor(model => model.Age)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Surrname)
</div>
</fielset>
</div>
<p>
<input type="submit" value="Create" />
</p>
}
//binding to Model fails, Model is null. Not be able to debug anything in controller action, it stops when "loading" the page
#using (Html.BeginForm("GetListBoxData", "Person"))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#Html.ListBoxFor(model => model.ListboxData, Model.ListboxData);
}
</div>
PersonController.cs
[AcceptVerbs(HttpVerbs.Get)]
[ValidateAntiForgeryToken]
public ActionResult GetListBoxData()
{
var data = new List<PersonViewModel>();
data.Add(new PersonViewModel{Name = "Test", Surrname="testsurrname", Age=30});
var viewModel = new PersonViewModel()
{
ListboxData = data.AsEnumerable().Select(s=> new SelectListItem{Value=s.Name ,Text = s.Surrname}),
};
return View(viewModel);
}
[AcceptVerbs(HttpVerbs.Post)]
[ValidateAntiForgeryToken]
public ActionResult GetListBoxData(PersonViewModel persondata)
{
//TODO: handle values from View
return View(this);
}
[ValidateAntiForgeryToken]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Include = "Name, Surrname, Age")] PersonViewModel persondata)
{
try
{
PersonService personDataProvider = new PersonService();
personDataProvider.SavePerson(persondata);
return new RedirectResult("SomewhereToGo");
}
catch (DataException ex)
{
//TODO: Log
}
return View(this);
}
PersonViewModel
public class PersonViewModel
{
public int PersonId{ get; set; }
public int Age { get; set; }
public string Name { get; set; }
public string Surrname { get; set; }
public IEnumerable<SelectListItem> ListboxData { get; set; }
}
writing values from editFor to db works as expected without code for listboxfor.
after adding it to my html it should be filled from db on page loading, but I get a ReferenceNotSet Exception on page loading. Model.ListboxData is null, before GetListBoxData action is called.
Thanks a lot for your help!
Your form should submit the data via POST, not GET. And, you don't need to use enctype = "multipart/form-data", unless you want to upload files through your from.
You need two Index Actions in your Controller, one is for sending the data from your Controller to the View, and the other one is for getting the data back from the View, when the form is submitted (POST) to the server.
The first argument you pass to your ListBox (the expression) refers to the Property in your Model that the selected item from your ListBox will be stored in, which in this case is PersonId.
So, your View should look like this:
#model MVCApplication.Web.ViewModel.PersonViewModel
#using (Html.BeginForm("Index", "Person"))
{
#Html.ListBoxFor(model => model.PersonId, Model.ListBoxData)
<input type="submit" value="Save" />
}
Then, in your Controller, you'll have two Actions like this:
public ActionResult Index()
{
var viewModel = new PersonViewModel()
{
ListboxData = data.Select(s => new SelectListItem { Value = s.PersonId.ToString(), Text = s.PersonId.ToString() }).AsEnumerable();
};
return View(viewModel);
}
[HttpPost]
public ActionResult Index(PersonViewModel viewModel)
{
// code to save the data in the database or whatever you want to do with the data coming from the View
}
By the way, in your ViewModel, you don't have to define your ListBoxData property like that, just do this:
public class PersonViewModel
{
public int PersonId{ get; set; }
public IEnumerable<SelectListItem> ListBoxData { get; set; }
}