ASP.NET MVC Postbacks and HtmlHelper Controls is not reflecting Model Changes - asp.net-mvc

I'm facing problems with a MVC5 Razor web application. I have an authentication page (cshtml) that has an Id and password helper controls:
#model NetInfinity.Middleware.VistaModelos.LoginVistaModelo
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<h1>#Login.Acceso</h1>
<p>
#Html.TextBoxFor(c => c.Id, new { #placeholder = #Login.Usuario, autofocus = "", autocomplete = "off", maxlength = "15", size = "15" })
</p>
<p class="p1">
#Html.PasswordFor(c => c.Clave, new { #placeholder = #Login.Contraseña, maxlength = "20", size = "20" })
#Html.ActionLink(".", "Cambiopwd", null, new { #class = "login-cambiarpwd", id = "Cambiopwd" })
</p>
<p class="login-recordarpwd">#Html.ActionLink(#Login.RecordarPwd, "Recordatoriopwd")</p>
<button type="button" class="login-submit" id="login-submit">#Login.LoginSubmit</button>
}
And the respective Model:
public class LoginVistaModelo
{
public string Id
{
get;
set;
}
[DataType(DataType.Password)]
public string Clave
{
get;
set;
}
public string MensajeError
{
get;
set;
}
}
And Controller Action that validates user is:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginVistaModelo vmUsuario)
{
if (ModelState.IsValid)
{
EntidadesBD backend;
var cache = MemoryCache.Default;
backend = (EntidadesBD)cache.Get("backend");
if (backend == null)
{
backend = new EntidadesBD();
var politica = new CacheItemPolicy { Priority = CacheItemPriority.NotRemovable };
cache.Set("backend", backend, politica);
}
Usuario usuario = vmUsuario.ValidaUsuario();
if (usuario == null)
{
vmUsuario.MensajeError = "error2";
vmUsuario.Id = vmUsuario.Clave = String.Empty; // <--- This not works
ModelState.Clear(); // <-- This not works
}
else
{
}
}
return View(vmUsuario);
}
When Login Action is triggered to validate user and password and error is thrown, I need to clear TextBoxFor value and PasswordFor value, and to achieve this I set model properties Id and Clave to string.empty in Controller, however when page (cshtml) is rendered again, controls keep old values ignoring model changes, not even if ModelState.Clear(). I've heard that HtmlHelpers controls (like .TextBoxFor() etc.) don't bind to model values on Postback, but rather get their value directly out of the POST buffer from ModelState. Please, ¿How can I do to update controls value when they are changed in Model properties?
Thanks

try making the value of model null before returning it to view,
like vmUsuario.id = null, vmUsuario.clave= null ; and thn return the empty model to view

A better approach for this type of problem would be to redirect the user, rather than returning the view. Otherwise you run into the problem that if they press F5 it reposts the data. So simply redirect the user, and use TempData to include your error message. In your Get method, check if TempData contains an error message and display it if it does.

Related

Multiple Checkbox with Model returns null

after I read data from my database, I try to show those datas in Html.Helper checkbox and I do that. But later when I try to get checked values back to the controller, model always returns null. Here's my controller part:
[HttpGet]
public ActionResult NewClient()
{
HizmetModel hizmetModel = new HizmetModel();
hizmetModel.hizmet = db.Hizmet.ToList<Hizmet>();
return View(hizmetModel);
}
[HttpPost]
public ActionResult NewClientPost(string name, string lastname, string telephone, string plate, HizmetModel hizmet)
{
Musteri musteri = new Musteri();
if (!db.Musteri.Where(x => x.plaka == plate).Any())
{
musteri.isim = name;
musteri.soyisim = lastname;
musteri.telefon = telephone;
musteri.plaka = plate;
db.Musteri.Add(musteri);
db.SaveChanges();
}
Islem islem = new Islem();
IslemHizmet islemhizmet = new IslemHizmet();
islem.giristarihi = DateTime.Now;
islem.plaka = plate;
var selectedHizmet = hizmet.hizmet.Where(x => x.isChecked == true).ToList<Hizmet>();
db.Islem.Add(islem);
db.SaveChanges();
var onprocessplate = db.Islem.Where(x => x.plaka == plate).FirstOrDefault();
foreach (var item in selectedHizmet)
{
islemhizmet.islem_id = onprocessplate.islem_id;
islemhizmet.hizmet_id = item.hizmet_id;
db.IslemHizmet.Add(islemhizmet);
db.SaveChanges();
islemhizmet = new IslemHizmet();
}
TempData["Success"] = "Müşteri başarıyla eklendi...";
return RedirectToAction("CurrentClients", "Admin");
}
This is my model for the list:
public class HizmetModel
{
public List<Hizmet> hizmet { get; set; }
}
I use this model in the cshtml file:
#model otoyikama.Models.Model.HizmetModel
And this is the loop for displaying checkboxes
#for (int i = 0; i < Model.hizmet.Count; i++)
{
<li>
<label>#Model.hizmet[i].hizmetisim</label>
#Html.CheckBoxFor(m => m.hizmet[i].isChecked)
#Html.HiddenFor(m => m.hizmet[i].hizmet_id)
#Html.HiddenFor(m => m.hizmet[i].hizmetisim)
</li>
}
I couldn't figure what's the problem here, my get action works fine, I can see all the data from database but I can't pass them back to controller.
As a first think , u need to create a object in your controller parameters such as like List<int> serviceIDs or List<Service> services so you can keep more data than one.
The html part:
#foreach(item in Model.Service)
{
<label>#item.ServiceName</label><br>
<input type="checkbox" name="services" value="#item.ServiceID">
<input type="hidden" name="services" value="#item.ServiceName">
}
The backend part:
[HttpPost]
public ActionResult NewClientPost(string name, string lastname, string telephone, string plate, List<Service> services)
{
}
when u do in that way, u will able to hold more services than to one and i think u can pass them the controller more easly.
when i face with that stuation, im taking those services ID's and calling them on the controller side like bellow;
The html part:
#foreach(item in Model.Service)
{
<label>#item.ServiceName</label><br>
<input type="checkbox" name="serviceIDs" value="#item.ServiceID">
}
The backend part:
[HttpPost]
public ActionResult NewClientPost(string name, string lastname, string telephone, string plate, List<int> serviceIDs)
{
List<Service> services= new List<Service>();
foreach(var item in serviceIDs)
{
var service=db.Service.Where(x => x.ServiceID == item).Any()
if(service.Count!=0)
{
services.Add(service);
}
}
}

asp.net/MVC custom model validation attribute not working

(I've made some progress, but still not working, updates below...)
I am trying to implement ye olde start date is not greater than end date validation. This is the first time I've attempted to write a custom validation attribute. Based on what I've been reading out here, this is what I've come up with...
custom validation attribute:
public class DateGreaterThanAttribute : ValidationAttribute
{
private string _startDatePropertyName;
public DateGreaterThanAttribute(string startDatePropertyName)
{
_startDatePropertyName = startDatePropertyName;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var propertyInfo = validationContext.ObjectType.GetProperty(_startDatePropertyName);
if (propertyInfo == null)
{
return new ValidationResult(string.Format("Unknown property {0}", _startDatePropertyName));
}
var propertyValue = propertyInfo.GetValue(validationContext.ObjectInstance, null);
if ((DateTime)value > (DateTime)propertyValue)
{
return ValidationResult.Success;
}
else
{
var startDateDisplayName = propertyInfo
.GetCustomAttributes(typeof(DisplayNameAttribute), true)
.Cast<DisplayNameAttribute>()
.Single()
.DisplayName;
return new ValidationResult(validationContext.DisplayName + " must be later than " + startDateDisplayName + ".");
}
}
}
view model:
public class AddTranscriptViewModel : IValidatableObject
{
...
[DisplayName("Class Start"), Required]
[DataType(DataType.Date)]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:MM/dd/yyyy}")]
[RegularExpression(#"^(1[012]|0?[1-9])[/]([12][0-9]|3[01]|0?[1-9])[/](19|20)\d\d.*", ErrorMessage = "Date out of range.")]
public DateTime? ClassStart { get; set; }
[DisplayName("Class End"), Required]
[DataType(DataType.Date)]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:MM/dd/yyyy}")]
[RegularExpression(#"^(1[012]|0?[1-9])[/]([12][0-9]|3[01]|0?[1-9])[/](19|20)\d\d.*", ErrorMessage = "Date out of range.")]
[DateGreaterThan("ClassStart")]
public DateTime? ClassEnd { get; set; }
...
}
Relevant portions of the front-end:
#using (Html.BeginForm("AddManualTranscript", "StudentManagement", FormMethod.Post, new { id = "studentManagementForm", #class = "container form-horizontal" }))
{
...
<div class="col-md-4" id="divUpdateStudent">#Html.Button("Save Transcript Information", "verify()", false, "button")</div>
...
<div class="col-md-2">
<div id="divClassStart">
<div>#Html.LabelFor(d => d.ClassStart, new { #class = "control-label" })</div>
<div>#Html.EditorFor(d => d.ClassStart, new { #class = "form-control" }) </div>
<div>#Html.ValidationMessageFor(d => d.ClassStart)</div>
</div>
</div>
<div class="col-md-2">
<div id="divClassEnd">
<div>#Html.LabelFor(d => d.ClassEnd, new { #class = "control-label" })</div>
<div>#Html.EditorFor(d => d.ClassEnd, new { #class = "form-control" }) </div>
<div>#Html.ValidationMessageFor(d => d.ClassEnd)</div>
</div>
</div>
...
}
<script type="text/javascript">
...
function verify() {
if ($("#StudentGrades").data("tGrid").total == 0) {
alert("Please enter at least one Functional Area for the transcript grades.");
}
else {
$('#studentManagementForm').trigger(jQuery.Event("submit"));
}
}
...
</script>
The behavior I'm seeing is that all other validations on all other fields on the form, which are all standard validations like Required, StringLength, and RegularExpression, etc., are working as expected: when I click the "save" button, the red text appears for those fields that don't pass. I have put a breakpoint in my IsValid code, and it doesn't hit unless all the other validations are passed. And even then, if the validation check fails, it doesn't stop the post.
Further reading led me to add the following to Global.asax.cs:
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(DateGreaterThanAttribute), typeof(DataAnnotationsModelValidator));
But that made no difference. I also tested ModelState.IsValid in the postback function, and it was false. But for the other validators if never gets that far. I even noticed in the markup that it seems like a lot of markup gets created on those fields that have validation attributes when the page is generated. Where does that magic occur and why is my custom validator out of the loop?
There's a lot of variation out there, but what I have here seems to generally line up with what I'm seeing. I've also read some about registering validators on the client side, but that seems to only apply to client-side validation, not model validation at submit/post. I won't be embarrassed if the answer is some silly oversight on my part. After about a day on this, I simply need it to work.
Update:
Rob's answer led me to the link referenced in my comment below, which then led me here client-side validation in custom validation attribute - asp.net mvc 4 which led me here https://thewayofcode.wordpress.com/tag/custom-unobtrusive-validation/
What I read there jived with what I had observed, that something was missing in the markup, and it looked like the author outlined how to get it in there. So I added the following to my validation attribute class:
public class DateGreaterThanAttribute : ValidationAttribute, IClientValidatable // IClientValidatable added here
...
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
//string errorMessage = this.FormatErrorMessage(metadata.DisplayName);
string errorMessage = ErrorMessageString;
// The value we set here are needed by the jQuery adapter
ModelClientValidationRule dateGreaterThanRule = new ModelClientValidationRule
{
ErrorMessage = errorMessage,
ValidationType = "dategreaterthan" // This is the name the jQuery adapter will use, "startdatepropertyname" is the name of the jQuery parameter for the adapter, must be LOWERCASE!
};
dateGreaterThanRule.ValidationParameters.Add("startdatepropertyname", _startDatePropertyName);
yield return dateGreaterThanRule;
}
And created this JavaScript file:
(function ($) {
$.validator.addMethod("dategreaterthan", function (value, element, params) {
console.log("method");
return Date.parse(value) > Date.parse($(params).val());
});
$.validator.unobtrusive.adapters.add("dategreaterthan", ["startdatepropertyname"], function (options) {
console.log("adaptor");
options.rules["dategreaterthan"] = "#" + options.params.startdatepropertyname;
options.messages["dategreaterthan"] = options.message;
});
})(jQuery);
(Notice the console.log hits... I never see those.)
After this, I'm now getting hits when I browse to the page in the DataGreaterThanAttribute constructor and the GetClientValidationRules. As well, the ClassEnd input tag now has the following markup in it:
data-val-dategreaterthan="The field {0} is invalid." data-val-dategreaterthan-startdatepropertyname="ClassStart"
So I'm getting closer. The problem is, the addMethod and adapater.add don't seem to be doing their jobs. When I inspect these objects in the console using the following:
$.validator.methods
$.validator.unobtrusive.adapters
...my added method and adapter are not there. If I run the code from my JavaScript file in the console, they do get added and are there. I also noticed that if I generally inspect the unobtrusive validation object with...
$("#studentManagementForm").data('unobtrusiveValidation')
...there is no evidence of my custom validation.
As I alluded earlier, there are many examples out here, and they all seem to do things just a little differently, so I'm still trying some different things. But I'm really hoping someone who has beaten this into submission before will come along and share that hammer with me.
If I can't get this to work, I'll be putting on the hard-hat and writing some hacky JavaScript to spoof the same functionality.
I think you need IEnumerable<ValidationResult> on your model.
I had to do something similar around 4 years ago and still have the snippet to hand if this helps:
public class ResultsModel : IValidatableObject
{
[Required(ErrorMessage = "Please select the from date")]
public DateTime? FromDate { get; set; }
[Required(ErrorMessage = "Please select the to date")]
public DateTime? ToDate { get; set; }
IEnumerable<ValidationResult> IValidatableObject.Validate(ValidationContext validationContext)
{
var result = new List<ValidationResult>();
if (ToDate < FromDate)
{
var vr = new ValidationResult("The to date cannot be before the from date");
result.Add(vr);
}
return result;
}
}

Reference DropDownList selected value from enclosing Form

I'm just getting started with MVC5 (from WebForms), and dropdownlist bindings are giving me some fits.
I'd like to get this working using a GET request back to the page, with a selected value parameter. I'm hopeful that I can specify the route arguments in the form itself, so I'd like to reference the DDL's SelectedValue.
<p>
#using (Html.BeginForm("Index", "Profile", FormMethod.Get, new { id = WHATDOIPUTHERE} )) {
#Html.AntiForgeryToken()
#Html.DropDownList("ApplicationID", new SelectList(ViewBag.ApplicationList, "ApplicationID", "ApplicationName", ViewBag.SelectedApplicationId), new {onchange = "this.form.submit();"})
}
</p>
I can make it work with a POST form, but that requires a second controller method so I end up with
public ActionResult Index(long? id) {
ConfigManager config = new ConfigManager();
//handle application. default to the first application returned if none is supplied.
ViewBag.ApplicationList = config.GetApplications().ToList();
if (id != null) {
ViewBag.SelectedApplicationId = (long)id;
}
else {
ViewBag.SelectedApplicationId = ViewBag.ApplicationList[0].ApplicationID; //just a safe default, if no param provided.
}
//handle profile list.
List<ProfileViewModel> ps = new List<ProfileViewModel>();
ps = (from p in config.GetProfilesByApp((long)ViewBag.SelectedApplicationId) select new ProfileViewModel(p)).ToList();
return View(ps);
}
//POST: Profile
//read the form post result, and recall Index, passing in the ID.
[HttpPost]
public ActionResult index(FormCollection collection) {
return RedirectToAction("Index", "Profile", new {id = collection["ApplicationId"]});
}
It would be really nice to get rid of the POST method, since this View only ever lists child entities.
What do you think?
You can update your GET action method parameter name to be same as your dropdown name.
I also made some small changes to avoid possible null reference exceptions.
public ActionResult Index(long? ApplicationID) {
var config = new ConfigManager();
var applicationList = config.GetApplications().ToList();
ViewBag.ApplicationList = applicationList ;
if (ApplicationID!= null) {
ViewBag.SelectedApplicationId = ApplicationID.Value;
}
else
{
if(applicationList.Any())
{
ViewBag.SelectedApplicationId = applicationList[0].ApplicationID;
}
}
var ps = new List<ProfileViewModel>();
ps = (from p in config.GetProfilesByApp((long)ViewBag.SelectedApplicationId)
select new ProfileViewModel(p)).ToList();
return View(ps);
}

How to set the default value for Html.DropDownListFor in MVC

i have following code :
controller method:
public ActionResult Register(int? registrationTypeId)
{
IEnumerable<AccountType> accountTypes = new List<AccountType>
{
new AccountType
{
AccountTypeId = 1,
AccountTypeName = "Red"
},
new AccountType
{
AccountTypeId = 2,
AccountTypeName = "Blue"
}
};
// I want to select account type on registrationTypeId
ViewBag.AccountTypes = accountTypes;
return View();
}
View
<div class="col-md-10">
#Html.DropDownListFor(n => n.AccountType,
new SelectList(ViewBag.AccountTypes, "AccountTypeId", "AccountTypeName"), new { #class = "form-control" })
</div>
Model
public class RegisterViewModel
{
[Required]
[Display(Name = "Account Type")]
public int AccountType { get; set;
}
As you can see registrationTypeId in controller , i want to set the type on its bases if it is not null ,otherwise set to red. I have tried a alot but nothing worked for me. Any help will be appreciated !
I would highly recommend that you don't pass your list through the view bag. have seen too many questions where that has caused major issues. add this to your model
public List<SelectListItem> AccountTypes { get; set; }
in your controller in the get method set your default and set your list
Model.AccountType = 1; // change the one to your default value
Model.AccountTypes = accountTypes; //instead of ViewBag.AccountTypes = accountTypes;
then on your view
#Html.DropDownListFor(x => x.AccountType, Model.AccountTypes)
setting AccountType before passing the model to the view will set the default and the selected value on the view will be passed back in that same value.
The Wrong Way To Do This
var accountTypes = new SelectList(accountTypes, "AccountTypeId", "AccountTypeName");
foreach(var item in accountList)
if (item.AccountTypeId == registrationTypeId)
item.Selected = true;
ViewBag.AccountTypes = accountTypes;
In view,
#Html.DropDownListFor(n => n.AccountType, (SelectList)ViewBag.AccountTypes)

Get the dropdownlist data in controller action method

I have a drop down list in my view I want to get the value user selects in my controller action method.
View
Specialty Name Active:
<td>
<select name="Specialty" id="Specialty">
<option>--Select--</option>
<option value="1">True</option>
<option value="0">False</option>
</select>
</td>
Model:
public class GazelleInfoModel
{
public GazelleInfo gazelleInfo { get; set; }
public IList<WCG.Data.EntityObjects.GazelleInfo> ReportModel { get; set; }
}
Controller:
public ActionResult CreateNewGazelleInfo(GazelleInfoModel gazelleinfoModel, string hdnId, )
{
if (!isActive)
return LogOut();
ViewData["CurrentPage"] = "Create New GazelleInfo";
GazelleInfo gaz = null;
if (gaz == null)
{
gaz = new GazelleInfo();
}
gaz.SpecialtyName = gazelleinfoModel.gazelleInfo.SpecialtyName;
gaz.SpecialtyNameActive=
gaz.PreferredLanguage = gazelleinfoModel.gazelleInfo.PreferredLanguage;
gaz.PreferredLanguageActive = gazelleinfoModel.gazelleInfo.PreferredLanguageActive;
gaz.Race = gazelleinfoModel.gazelleInfo.Race;
gaz.RaceActive = gazelleinfoModel.gazelleInfo.RaceActive;
gaz.Ethnicity = gazelleinfoModel.gazelleInfo.Ethnicity;
gaz.EthnicityActive = gazelleinfoModel.gazelleInfo.EthnicityActive;
gaz.HolidayName = gazelleinfoModel.gazelleInfo.HolidayName;
gaz.HolidayNameActive = gazelleinfoModel.gazelleInfo.HolidayNameActive;
GazelleInfoBo.SaveOrUpdate(gaz);
What I need is storing the values selected in the drop downlist in the gaz object which at a later point of time i will store in the database.
Option 1
var Specialty = Request.Form["Specialty"];// here request.form is used to get Specialty form variable.
Option 2 : I would suggest you to use MVC ModelBinding and Html Helpers.
Model - Add new property Specialty
Controller - get all relevant Specialities
ViewBag.Specialities= new SelectList(Specialities, "Value", "Text");
View - User strongly type html helper
#Html.DropDownListFor(x => x.Specialty , new SelectList(ViewBag.Specialities, "Value", "Text", Model.Specialty ))
Option 3 - Best practice to have radiobuttons(where possible) to implement true/false logic.
#Html.RadioButtonFor(x=>x.Specialty, true)True
#Html.RadioButtonFor(x=>x.Specialty, false)False

Resources