asp.net mvc problem in IValidatableObject method on validation - c#

I am creating a web app in asp.net mvc-5,
I am using IValidatableObject interface for validations,
here is how my model looks,
public class LicenseInfo : IValidatableObject
{
public int LicenseId { get; set; }
//other properties
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
//Validate class which will be called on submit
}
}
My view
#using (Ajax.BeginForm("_AddEditLicense", "User", new AjaxOptions { HttpMethod = "POST", UpdateTargetId = "dvLicenseContent", OnSuccess = "fnAddEditOnSuccess" }))
{
#Html.ValidationSummary(false)
#Html.DropDownListFor(m => m.LicenseId, new SelectList(Model.LicenseData, "Value", "Text"), "Select....", new { #class = "form-control" })
#*other html elements*#
<input type="submit" value="#ViewBag.Submit" id="btnaddLicense" class="btn btn-primary btn-block" />
}
My Controller
[HttpPost]
public ActionResult _AddEditLicense(LicenseInfo data)
{
if (ModelState.IsValid)
{
//execution
}
}
when my LicenseId = 0 then my validation is not working and the debugger on my controller is executing directly, but when LicenseId > 0 then my validation method is executing.

You need to manually add the validation inside your controller method.
[HttpPost]
public ActionResult _AddEditLicense(LicenseInfo data)
{
if (ModelState.IsValid)
{
// Execute code
}
// Not validated, return to the view
return View(data);
}
EDIT
Well, 0 is a valid value for an int even if does not represent anything in your drop down.
Try to change it to int?, then the default value would be null and it should be easier to catch it in model validation.

Related

MVC Partial View Validation Message not showing

I have this AjaxForm in my partial view:
#using (Ajax.BeginForm("CreateStarter", "Player", new AjaxOptions { HttpMethod = "POST"}))
{
#Html.HiddenFor(m => m.OwnerID)
#Html.HiddenFor(m => m.Species)
#Html.HiddenFor(m => m.Gender)
#Html.ValidationSummary(true)
<div class="editor-label">#Html.LabelFor(m => m.Nickname)</div>
<div class="editor-field">
#Html.EditorFor(m => m.Nickname)
#Html.ValidationMessageFor(m => m.Nickname,"", new { #class = "text-danger" })
</div>
<input type="submit" value="Choose my pokemon">
}
In my controller post action i verify whether or not the model is valid. If it is not i return the partial view. If the model is not valid, the partial view is returned, but the validation message is not shown. Am I missing something?
This is my action:
[HttpPost]
public ActionResult CreateStarter(PokemonViewModel pokemonViewModel)
{
if (ModelState.IsValid)
{
Pokemon pokemonEntity = PokemonMapper.GetPokemonEntity(pokemonViewModel);
_userService.AddStarterPokemonToPlayer(pokemonViewModel.OwnerID, pokemonEntity);
return RedirectToAction("PlayerProfile");
}
else
{
return PartialView("_CreateStarter", pokemonViewModel);
}
}
And this is my model:
public class PokemonViewModel
{
public int ID { get; set; }
public int Species { get; set; }
public string Gender { get; set; }
[Required]
public string Nickname { get; set; }
public int OwnerID { get; set; }
}
Dealing with partial views and ajax is not straight forward but it is not hard either. You need to do a few things:
Create a container (<div>) in your main page wherein you house you
partial view.
In Ajax.BeginForm, specify what to do in:
InsertionMode
UpdateTargetId
OnFailure
OnBegin
In your controller you cannot simply return the view if the model is not valid, because that will send an HTTP 200 OK status indicating the request succeeded. You need to inform the client that something is not right.
Step 1 and Step 2
Imagine you have a main page and within that you will need to create a container for your partial and put your partial there. Also note the OnXxx function handlers.
<html>
<!-- Your main page -->
<div id="your-partial-form-id-here>
#using (Ajax.BeginForm( /* other arguments */
new AjaxOptions
{
HttpMethod = "POST",
OnBegin = "DoBeforeUpdatingPage",
OnFailure = "DoWhenThereIsAnIssue",
OnSuccess = "DoOnSuccess",
UpdateTargetId = "id-of-html-element-to-update-on-success",
}))
{
//...code
}
</div>
</html>
All the OnXxx handlers are javascript method names which will handle each scenario. Here is what you may do in each:
<script type="text/javascript">
function DoBeforeUpdatingPage() {
// Maybe nothing or maybe form validation
}
function DoWhenThereIsAnIssue(responseFromServer) {
// In your case the responseFromServer is the partial view
// so you just need to inject the html into the container
// you have created earlier.
$('your-partial-form-id-here').html(responseFromServer);
// Also since there was an issue, you may want to clear
// the thing which is updated when it is success
$('#id-of-html-element-to-update-on-success').empty();
}
function DoOnSuccess(responseFromServer) { // whatever... }
</script>
Step 3
Return BadRequest to the client so the javascript OnFailure handler is invoked; in our case the DoWhenThereIsAnIssue will be invoked.
public ActionResult SomeAction(SomeModel model)
{
if (!ModelState.IsValid)
{
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return PartialView("_NameOfPartial", model);
}
}

How to pass the ID of an object from a form to an action in ASP.NET MVC

I have an ASP.NET MVC project with entities based on EF6 (model first). So my entities are all auto-generated for me. I have an entity, Site and I just want the user to select a Site before proceeding. I have tried a couple of ways, all of them work, but they seem very messy and unnecessary.
I was curious about the cleanest way to create a DropdownList of Sites, then get the selected site when the form is submitted (by Id or whatever other mechanism is better).
Currently I have:
The index where the user is asked to select a site:
public ActionResult Index()
{
ViewBag.Sites = new SelectList(db.Sites.ToList(), "Id", "Name");
return View();
}
The view:
#using (Html.BeginForm("SetSite", "Home"))
{
#Html.Label("sites", "Site:");
#Html.DropDownList("Sites", null, new { #class = "selectpicker" });
<div style="width:100%;height:25px"></div>
<button class="btn btn-default" style="width:100%">Go</button>
}
And the SetSite action, where the form is submitted
[HttpPost]
public ActionResult SetSite()
{
if (Request.Form["Sites"] != null)
{
Session["Site"] = db.Sites.Find(Request.Form["Sites"]);
return RedirectToAction("Home");
}
else
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
}
A few problems arise from this method. First, I really wanted to take advantage of the #model functionality in razor and point it towards my Site class, but since it's auto-generated, I don't want to go poking around and adding a whole bunch of view properties. (beggars can't be choosers?)
Second, the Request.Form['Sites'] returns a string, and converting it to and int is ugly as hell.
As I mentioned, I'd like to use the #model functionality with Html.DropDownListFor. Is that possible when working with a list of Sites from the DB?
How can I get this done cleanly?
Solution 1:-
Controller:-
private List<Country> GetCountries()
{
var list = new Entity.Result<List<Entity.Country>>();
list = _context.Countries.Select(tc => new Entity.Country
{
Id = tc.Id,
Name = tc.Name,
IsdCode = tc.Code,
}).ToList();
return list.Data.Select(x => new Country
{
id = x.Id,
Name = x.Name,
}).ToList();
}
HttpGet Method:-
public ActionResult Add(int id)
{
try
{
}
catch (Exception ex)
{
}
finally
{
ViewBag.countryList = GetCountries();
}
return View()
}
View Method:-
#Html.DropDownListFor(x => x.countryId, new SelectList(ViewBag.countryList, "id", "Name"), KahandasDhanji.Common.Localization.Application.Resources.ddlCountry,
new { #id = "ddlCountry", #rows = 1 })
In Http Post Form Submitimg u handle that model value in HTTPPOST Method.
Solution 2:-
FormCollection class we can capture the form's values within the controller.
[HttpPost]
public ActionResult Add(FormCollection form)
{
string strDDLValue = form["Sites"].ToString();
return View(MV);
}
Hope Its Work !!!
You can use a ViewModel to avoid converting the string value from Request.Form. Below is how your ViewModel class should look like
public class MyViewModel
{
public MyViewModel()
{
this.DropdownItems = new List<SelectListItem>();
}
public int SelectedSiteId { get; set; }
public List<SelectListItem> DropdownItems { get; set; }
}
Change the get action method in your controller as below
public ActionResult Index()
{
List<Site> sites = db.Sites.ToList();
MyViewModel model = new MyViewModel();
foreach(var site in sites)
{
model.DropdownItems.Add(new SelectListItem() { Text = site.Name, Value = site.ID.ToString() });
}
return View(model);
}
Add #model MyViewModel at the first line in your view code and use Html.DropDownListFor method to generate the dropdownlist
#model MyViewModel
#using (Html.BeginForm("SetSite", "Home"))
{
#Html.Label("SelectedSiteId", "Site:");
#Html.DropDownListFor(m => m.SelectedSiteId, Model.DropdownItems, new { #class = "selectpicker" })
<div style="width:100%;height:25px"></div>
<button class="btn btn-default" style="width:100%">Go</button>
}
The post action method in your controller should look like below. model.SelectedSiteId will be the selected value of the dropdownlist and the type is int so you won't have to do any conversion such as Convert.ToInt32(Request.Form['Sites']).
[HttpPost]
public ActionResult SetSite(MyViewModel model)
{
Session["Site"] = model.SelectedSiteId;
return RedirectToAction("Home");
}

Send check box status (checked or not) through Hidden field

*.cshtml page
#{
var companyLoginFormViewModel = TempData["CompanyLoginFormViewModel"] as CompanyLoginFormViewModel;
}
<form class="login-form" action="#Url.Action("Login")" name="companyLoginForm" method="post">
<input type="hidden" name="rememberMe" value="#companyLoginFormViewModel.LoginViewModel.RememberMe" />
<button type="submit" class="btn btn-success uppercase">#L("LogIn")</button>
</form>
Note : This value (#companyLoginFormViewModel.LoginViewModel.RememberMe) is always true or false
VMs
public class CompanyLoginFormViewModel
{
public LoginViewModel LoginViewModel { get; set; }
}
public class LoginViewModel
{
public bool RememberMe { get; set; }
}
Action Method :
[HttpPost]
public virtual async Task<JsonResult> Login(LoginViewModel loginModel)
{
if (!ModelState.IsValid) //here it shows validation error
{
throw new UserFriendlyException(L("FormIsNotValidMessage"));
}
}
Question : When I pass a true or false through hiden field,it always says The RememberMe field is required. validation error on the server side.But that property is not a required field ? Could you tell me why this is happening ? Thanks.
You can add class="ignore" in you hidden field, and put this in your javascript code to ignore in client side:
$.validator.setDefaults({
ignore: ".ignore"
});
server side:
#Html.Hiddenfor(model => model.rememberMe,
new Dictionary<string, object> { { "data-val", false }})
you are using MVC ,Please use the facilities it:
#model CompanyLoginFormViewModel
#{
}
#using (Html.BeginForm("Your Action","your controller",FormMethod.Post))
{
#Html.Hiddenfor(model => model.rememberMe,
new Dictionary<string, object> { { "data-val", false } })
<button type="submit" class="btn btn-success uppercase">#L("LogIn")</button>
}

Passing an Object to a controller using BeginForm()

I have a strongly typed view and I'm trying to pass the input from a textbox upon a button click to an action using BeginForm. My code keeps passing a null object to the action method in the controller. How do I pass the object to the controller via the form ?
#using (#Html.BeginForm("GetQueueInfoWorkorder","Home", FormMethod.Post, new { id = Model}))
{
#Html.TextBoxFor(x=> x.ID);
<input type="Submit" value ="Search" class="ui-button-icon-secondary"/>
}
Here is the actionmethod :
[HttpPost]
public ActionResult GetQueueInfoWorkorder(UserResponse id)
{
//Check queue complete
int woNumber = Convert.ToInt32(id);
tb_QueueCompleted completed = db.tb_QueueCompleted.SingleOrDefault(x => x.WorkOrderNumber == woNumber);
if (completed != null)
{
var u = new UserResponse { ID = completed.QueueId.ToString() };
GetLogInfoCompleted(u);
return View("GetLogInfo");
}
//check queue pending
return View();
}
I think you're fairly close, but make these changes & it should work as expected:
Model:
public class UserResponse
{
public int ID { get; set; }
}
View:
#model UserResponse
#using (Html.BeginForm("GetQueueInfoWorkorder", "Home"))
{
#Html.TextBoxFor(x => x.ID);
<input type="Submit" value ="Search" class="ui-button-icon-secondary"/>
}
Action method:
public ActionResult GetQueueInfoWorkorder(UserResponse model)
{
int woNumber = model.ID;
//...
}
if the #model of your view is UserResponse , then on submission of this page the model (UserResponse) will automatically get submitted to the controller. Where have you declared the #model for the view.

Client-side custom data annotation validation

I've create a custom data annotation to do some validation on my view model. The problem is that it doesn't validate on the client-side. Here's my model:
public class MemberViewModel
{
[ScaffoldColumn(false)]
public int MemberId { get; set; }
[Required(ErrorMessage = "Name is required")]
public string Name { get; set; }
//My custom data annotation
[EnforceTrue(ErrorMessage = "You must agree to the Terms and Conditions")]
public bool AgreeTerms { get; set; }
}
My data annotation validation code:
public class EnforceTrueAttribute : ValidationAttribute, IClientValidatable
{
public EnforceTrueAttribute() { }
public override bool IsValid(object value)
{
return value != null && (bool)value == true;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
yield return new ModelClientValidationRule() { ValidationType = "enforcetrue", ErrorMessage = this.ErrorMessageString };
}
}
My controller method:
[HttpPost]
public ActionResult Index(MemberViewModel viewModel)
{
Member member = new Member();
TryUpdateModel(member);
if (ModelState.IsValid)
{
_membersRepository.SaveMember(member);
return RedirectToAction("Index", "Home");
}
return View(viewModel); // validation error, so redisplay same view
}
And my view:
#using (Html.BeginForm("Index", "Members", FormMethod.Post)) {
#Html.HiddenFor(m => m.MemberId)
<div class="editor-label">#Html.LabelFor(model => model.Name)</div>
<div class="editor-field">#Html.TextBoxFor(model => model.Name)</div>
<div class="editor-field">#Html.CheckBoxFor(model => model.AgreeTerms) <label for="AgreeTerms">I agree to the Terms and Conditions</label></div>
<p>
<input type="submit" value="Submit" />
</p>
#Html.ValidationSummary()
}
So all my other error messages get displayed in the validation summary with client-side validation. But for my custom data annotation, the error message doesn't show until the rest of the model is valid, and after you submit the form and page reloads, that's when the error is displayed in the summary.
Is there something else I need to do here to get it to show up in the summary with the other errors?
I'm using C# and ASP.NET MVC 3
Had same issue recently. You can write:
$.validator.addMethod('enforcetrue', function (value, element) {
return $(element).is(":checked");
});
$.validator.unobtrusive.adapters.add('enforcetrue', [], function (options) {
options.messages['enforcetrue'] = options.message;
options.rules['enforcetrue'] = options.params;
});
Similar question here ASP.NET MVC 3 client-side validation
Implementing Iclientvalidatable only adds unobtrusive attributes to generated html inputs. To enable validation on client side you must write validators that use these unobtrusive attributes to validate the inputs. Here you can find very good explanation of client and server validation in asp.net mvc 3
A Remote Validator is what you need here is the link
http://www.devtrends.co.uk/blog/the-complete-guide-to-validation-in-asp.net-mvc-3-part-1

Categories

Resources