MVC dropdown update the form not working - asp.net-mvc

I know there are other posts for this, but I'm still not wrapping my head around it.
I would like write my MVC view to use a ViewModel that will allow text fields to change based on selections in a dropdownlist?
In the below example, I have a collection of Templates. I would like to see the dropdown list the Templates, then the user would select an item from the DropDownList, the code would populate the text fields, the user could edit those text fields, and finally submit the form with their changes.
What happens now is that every time the Submit code is called, it acts as if the dropdown was selected.
Model and ViewModel:
public class Template
{
[Display(Name = "Template")]
public int TemplateId {get; set;}
public string TemplateName {get; set;}
public string Subject {get; set;}
public string Message {get; set;}
}
public class TemplateViewModel
{
[Display(Name = "Template")]
public int LetterTemplateId {get; set;}
public ICollection<Template> Templates { get; set; }
}
View:
#model MyWebProject.ViewModels.TemplateViewModel
#using (Html.BeginForm("callforcontentproposaldetail","Project", FormMethod.Post, new {id = "frmProposalDetail"}))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Email Template</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.LetterTemplateId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-4">
#Html.DropDownListFor(m => m.LetterTemplateId,
new SelectList(Model.Templates as IEnumerable, "TemplateId", "TemplateName"),
new { #class = "form-control", onchange = "document.getElementById('frmProposalDetail').submit();", id = "ddlTemplate" })
#Html.ValidationMessageFor(model => model.LetterTemplateId, "", new { #class = "text-danger" })
</div>
</div>
<div id="letterTemplateEditArea">
<div class="form-group col-md-12">
<div class="row">
#Html.TextBoxFor(model => model.SelectedTemplate.TemplateSubject, new { #class = "form-control" })
#Html.LabelFor(model => model.SelectedTemplate.Message, new { #class = "control-label" })
#Html.TextAreaFor(model => model.SelectedTemplate.Message, new { #class = "form-control", #style = "max-width:100%;height:400px;font-size:11px;" })
</div>
</div>
<br />
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
Controller Code:
public ActionResult callforcontentproposaldetail(Guid id)
{
var proposal = db.CallForContentProposal.Find(id);
var model = Mapper.Map<CallForContentProposalViewModel>(proposal);
if (TempData["LetterTemplateId"] != null)
{
var emailTempId = 0;
if (int.TryParse((string)TempData["LetterTemplateId"], out emailTempId))
{
var template = model.Templates.FirstOrDefault(t => t.TemplateId == emailTempId);
model.SelectedTemplateId = emailTempId;
model.Subject = template.Subject;
model.Message = template.Body;
}
}
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult callforcontentproposaldetail(CallForContentProposalViewModel model, string SelectedTemplateId = "")
{
// Do some saving of the current data
// ...
db.SaveChanges();
// If this is the dropdownlist calling, redirect back to display the text fields populated
if (!string.IsNullOrEmpty(SelectedTemplateId))
{
TempData["LetterTemplateId"] = SelectedTemplateId;
return RedirectToAction("callforcontentproposaldetail", new { id = model.CallForContentProposalId });
}
// On Submit, do other tasks
return RedirectToAction("callforcontent", new {id = model.CallForContentId});
}

I figured it out. Thanks to your comments, I worked out a way to use Ajax to do this for our situation (I tried to make it as generic as possible to reuse elsewhere):
View:
<div class="form-group">
#Html.LabelFor(model => model.LetterTemplateId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-4">
#Html.DropDownListFor(m => m.LetterTemplateId,
Model.EmailTemplateSelect(),
new { #class = "form-control", onchange = "EmailTemplateLoad.call(this,this.options[this.selectedIndex].value,'LetterTemplateSubject','LetterTemplateBody');", id = "ddlReviewRole" })
#Html.ValidationMessageFor(model => model.LetterTemplateId, "", new { #class = "text-danger" })
</div>
</div>
Javascript:
EmailTemplateLoad = function(id, subjectTbName, messageTbName) {
debugger;
var ServiceUrl = "/Project/LoadTemplate?id=" + id;
var content = '';
$.support.cors = true;
$.ajax({
type: 'GET',
url: ServiceUrl,
async: true,
cache: false,
crossDomain: true,
contentType: "application/json; charset=utf-8",
dataType: 'json',
error: function (xhr, err) {
},
success: function (result, status) {
$('#' + subjectTbName).val(result[0].Subject);
$('#' + messageTbName).val(result[0].Message);
}
});
};
Controller:
public ActionResult LoadTemplate(int id)
{
var result = (from t in db.EmailBodyTemplate
where t.EmailTemplateId == id
select new
{
t.Subject,
Message = t.Body
});
return Json(result, JsonRequestBehavior.AllowGet);
}
I also followed this example, although it was incomplete:
CodeProject example

Related

LINQ To Entities SaveChanges() not working

I have a scenario where I have a form where I would like administrators to modify their exam / survey details, I am trying to update my database table with the following code.
However the code does not save my changes for the "if" part in my controller, and does not throw any error, and will just redirect me to the next page which is "EditExam2".
I am trying to update the "InformationSheetText" and "InformationConsentForm" fields.
I know the query works as the the "else" part of my code in my controller works when adding a new row into the database.
My View
#model
AppXamApplication.Models
InformationSheetViewModel
#{
ViewBag.Title = "InformationSheet";
}
<!DOCTYPE html>
<html>
<body>
<h2>InformationSheet</h2>
<h3>Survey ID: #ViewBag.CurrentExamID</h3>
#using (Html.BeginForm("InformationSheet", "ExamAdmin", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
<h4>Create Information and Consent Sheet.</h4>
<hr />
#Html.ValidationSummary("", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(m => m.ImageURL, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
<input type="file" name="ImageFile" />
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.InformationSheetText, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.EditorFor(m => m.InformationSheetText, new { #class = "form-control", #rows = 4, #style = "resize: none;" })
</div>
</div>
<div class="form-group">
<div class="col-md-10">
#Html.CheckBoxFor(m => m.Check_InformationSheet, new { #disabled = "disabled", #checked = true })
#Html.LabelFor(m => m.Check_InformationSheet, new { })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.InformationConsentForm, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.EditorFor(m => m.InformationConsentForm, new { #class = "form-control", #rows = 4, #style = "resize: none;" })
</div>
</div>
<div class="form-group">
<div class="col-md-10">
#Html.CheckBoxFor(m => m.Check_InformationConsentForm1, new { #disabled = "disabled", #checked = true })
#Html.LabelFor(m => m.Check_InformationConsentForm1, new { })
</div>
<div class="col-md-10">
#Html.CheckBoxFor(m => m.Check_InformationConsentForm2, new { #disabled = "disabled", #checked = true })
#Html.LabelFor(m => m.Check_InformationConsentForm2, new { })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" class="btn btn-default" value="Create Exam" />
</div>
</div>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
My model
public class InformationSheetViewModel
{
public string ExamID { get; set; }
[Display(Name = "Choose Image To Display")]
public string ImageURL { get; set; }
[Display(Name = "Enter your Information Sheet")]
public string InformationSheetText { get; set; }
[Display(Name = "Enter your Consent Form")]
public string InformationConsentForm { get; set; }
public HttpPostedFileBase ImageFile { get; set; }
[Display(Name = "I had read and understood the information sheet")]
public bool Check_InformationSheet { get; set; }
[Display(Name = "I consent and agree to the information consent form")]
public bool Check_InformationConsentForm1 { get; set; }
[Display(Name = "I have read, agree and consent to the information and conditions")]
public bool Check_InformationConsentForm2 { get; set; }
}
My Controller
[HttpGet]
public ActionResult InformationSheet(string id)
{
if (ModelState.IsValid)
{
ViewBag.CurrentExamID = id;
using (var ctx = new AppXamApplicationEntities())
{
var query = ctx.InformationConsentAndSheets.Where(x => x.ExamID.Equals(id)).Select(x => new InformationSheetViewModel()
{
ExamID = id,
InformationSheetText = x.InformationSheetText,
InformationConsentForm = x.InformationSheetText
}).FirstOrDefault();
return View(query);
}
}
return View();
}
[HttpPost]
[Authorize(Roles = "ExamAdmin")]
[ValidateAntiForgeryToken]
public ActionResult InformationSheet(string id, InformationSheetViewModel model)
{
using (var ctx = new AppXamApplicationEntities())
{
InformationConsentAndSheet query = ctx.InformationConsentAndSheets.Where(x => x.ExamID.Equals(id)).FirstOrDefault();
if (query != null)
{
//To insert picture into database as well as folder
string fileName = Path.GetFileNameWithoutExtension(model.ImageFile.FileName);
string extension = Path.GetExtension(model.ImageFile.FileName);
fileName = fileName + DateTime.Now.ToString("yymmssfff") + extension;
model.ImageURL = "~/Image/" + fileName;
fileName = Path.Combine(Server.MapPath("~/Image/"), fileName);
model.ImageFile.SaveAs(fileName);
query = new InformationConsentAndSheet()
{
ExamID = id,
ImageURL = model.ImageURL,
InformationSheetText = model.InformationSheetText,
InformationConsentForm = model.InformationConsentForm
};
ctx.SaveChanges();
}
else
{
//To insert picture into database as well as folder
string fileName = Path.GetFileNameWithoutExtension(model.ImageFile.FileName);
string extension = Path.GetExtension(model.ImageFile.FileName);
fileName = fileName + DateTime.Now.ToString("yymmssfff") + extension;
model.ImageURL = "~/Image/" + fileName;
fileName = Path.Combine(Server.MapPath("~/Image/"), fileName);
model.ImageFile.SaveAs(fileName);
query = new InformationConsentAndSheet()
{
ExamID = id,
ImageURL = model.ImageURL,
InformationConsentForm = model.InformationConsentForm,
InformationSheetText = model.InformationSheetText
};
ctx.InformationConsentAndSheets.Add(query);
ctx.SaveChanges();
}
return RedirectToAction("EditExam2");
}
}
I am very perplexed to what is wrong with my code, any form of help will be very appreciated as I am extremely new to MVC in general.
First of all you need to send your Id of survey when you want to edit. It can be done easly by route parameters in your form.
#using (Html.BeginForm("InformationSheet", "ExamAdmin", new { id = ViewBag.CurrentExamID }, FormMethod.Post, new { enctype = "multipart/form-data" }))
Your code for edit existing item is a little bit wrong. You need to, well, modify existing InformationConsentAndSheet and not creating new.
InformationConsentAndSheet query = ctx.InformationConsentAndSheets.Where(x => x.ExamID.Equals(id)).FirstOrDefault();
if (query != null)
{
// work with files
query.ExamID = id;
query.ImageURL = model.ImageURL;
query.InformationSheetText = model.InformationSheetText;
query.InformationConsentForm = model.InformationConsentForm;
ctx.Entry(query).State = EntityState.Modified;
ctx.SaveChanges();
}
Before submiting changes you need to specify that query have been edited and needs to be saved.
Hope its helps.

ASP.NET MVC - cascading drop down not being saved to database

I created a 2-dependent cascading dropdown list:
Model Classes
COUNTRIES
public int COUNTRY_ID { get; set; }
public string COUNTRY_NAME { get; set; }
STATES
public int STATE_ID { get; set; }
public Nullable<int> COUNTRY_ID { internal get; set; }
public string STATE_NAME { get; set; }
CITIES
public int CITY_ID { get; set; }
public Nullable<int> STATE_ID { internal get; set; }
public Nullable<int> COUNTRY_ID { internal get; set; }
public string CITY_NAME { get; set; }
The drop-down list worked as expected. But when clicked on save button, nothing is saved.
Controller: CITIES
// Json Call to get state
public JsonResult GetStates(string id)
{
List<SelectListItem> states = new List<SelectListItem>();
var stateList = this.Getstate(Convert.ToInt32(id));
var stateData = stateList.Select(m => new SelectListItem()
{
Text = m.STATE_NAME,
Value = m.STATE_ID.ToString(),
});
return Json(stateData, JsonRequestBehavior.AllowGet);
}
// Get State from DB by country ID
public IList<STATES> Getstate(int CountryId)
{
return _statesService.GetStates().Where(stat => stat.COUNTRY_ID == CountryId).ToList();
}
//
public ActionResult Create()
{
ViewBag.COUNTRIES = new SelectList(_countriesService.GetCountries(), "COUNTRY_ID", "COUNTRY_NAME");
ViewBag.STATES = new SelectList(_statesService.GetStates(), "STATE_ID", "state_NAME");
return View();
}
[HttpPost]
public ActionResult Create(CITIES cities)
{
try
{
IEnumerable<COUNTRIES> lstCountries = _countriesService.GetCountries();
if (ModelState.IsValid)
{
cities.ACTION_STATUS = 0;
cities.CREATED_DATE = DateTime.Now;
_citiesService.AddCity(cities);
return RedirectToAction("Index");
}
}
catch
{
ModelState.AddModelError("", "We cannot add this Cities. Verify your data entries !");
}
ViewBag.COUNTRIES = new SelectList(_countriesService.GetCountries(), "COUNTRY_ID", "COUNTRY_NAME", cities.COUNTRY_ID);
ViewBag.STATES = new SelectList(_statesService.GetStates(), "STATE_ID", "STATE_NAME", cities.STATE_ID);
return View(cities);
}
View : Cities
<div class=" box box-body box-primary">
#*#using (Html.BeginForm())*#
#using (Html.BeginForm("Create", "Cities", FormMethod.Post, new { #class = "form-horizontal", #enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true, null, new { #class = "text-danger" })
<div class="row .col">
<div style="margin-top:20px" class="mainbox col-md-12 col-md-offset-0 col-sm-8 col-sm-offset-2">
<div class="panel panel-info">
<div class="panel-heading">
<div class="panel-title">Create City</div>
</div>
<div class="panel-body">
<div class="col-md-4">
<div>
#Html.LabelFor(model => model.COUNTRY_ID, "Country Name", new { #class = "control-label" })
#Html.DropDownList("COUNTRIES", ViewBag.COUNTRIES as SelectList, "-- Please Select a Country --", new { style = "width:250px" })
#Html.ValidationMessageFor(model => model.COUNTRY_ID, null, new { #class = "text-danger" })
</div>
</div>
<div class="col-md-4">
<div>
#Html.LabelFor(model => model.STATE_ID, "State Name", new { #class = "control-label" })
#Html.DropDownList("STATES", new SelectList(string.Empty, "Value", "Text"), "-- Please select a State --", new { style = "width:250px", #class = "dropdown1" })
#Html.ValidationMessageFor(model => model.STATE_ID, null, new { #class = "text-danger" })
</div>
</div>
<div class="col-md-4">
<div>
#Html.LabelFor(model => model.CITY_NAME, "City Name", new { #class = "control-label" })
#Html.TextBoxFor(model => model.CITY_NAME, new { #style = "border-radius:3px;", #type = "text", #class = "form-control", #placeholder = Html.DisplayNameFor(m => m.CITY_NAME), #autocomplete = "on" })
#Html.ValidationMessageFor(model => model.CITY_NAME, null, new { #class = "text-danger" })
</div>
</div>
</div>
<div class="panel-footer">
<div class="panel-title">
<div class="form-actions no-color">
<input type="submit" value="Create" class="btn btn-success" />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
<script type="text/javascript">
$(document).ready(function () {
//Country Dropdown Selectedchange event
$("#COUNTRIES").change(function () {
$("#STATES").empty();
$.ajax({
type: 'POST',
url: '#Url.Action("GetStates")', // Calling json method
dataType: 'json',
data: { id: $("#COUNTRIES").val() },
// Get Selected Country ID.
success: function (states) {
$.each(states, function (i, state) {
$("#STATES").append('<option value="' + state.Value + '">' +
state.Text + '</option>');
});
},
error: function (ex) {
alert('Failed to retrieve states.' + ex);
}
});
return false;
})
});
</script>
When I clicked on Countries Drop-down list, it populates and the respective states are loaded as expected. When I clicked on Save button, nothing is being saved. Please what is the problem

ASP.NET MVC - Null Object in ViewModel on POST

Upon POST of an ActionController I am receiving the great ole' object reference not set to an instance of an object error.
Basically I need the ID of the userRequest to be saved WITH the requestResponse. (Foreign Key here)
Here is the code.
ViewModel:
public class RequestResponseViewModel
{
public Models.Request userRequest { get; set; }
public Models.RequestResponse requestResponse { get; set; }
}
View: In debug here there is value in model.userRequest.ID
#model UserRequests.ViewModels.RequestResponseViewModel
#{
ViewBag.Title = "Create";
}
<h2>Admin Response to Request</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.requestResponse.Response,
htmlAttributes: new { #class = "control-label col-md-1" })
<div class="col-md-10">
#Html.TextAreaFor(model => model.requestResponse.Response, new {
#class = "form-control", #rows = 5 })
#Html.ValidationMessageFor(model =>
model.requestResponse.Response, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.userRequest.ID, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-2">
#Html.DisplayFor(model => model.userRequest.ID)
#Html.ValidationMessageFor(model => model.userRequest.ID, "", new { #class = "text-danger" })
</div>
#Html.LabelFor(model => model.requestResponse.Author, htmlAttributes: new { #class = "control-label col-md-1" })
<div class="col-md-3">
#Html.EditorFor(model => model.requestResponse.Author, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.requestResponse.Author, "", new { #class = "text-danger" })
</div>
#Html.LabelFor(model => model.requestResponse.CreateDate, htmlAttributes: new { #class = "control-label col-md-1" })
<div class="col-md-3">
<h5>#DateTime.Now</h5>
#Html.ValidationMessageFor(model => model.requestResponse.CreateDate, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-1">
<button type="reset" class="btn btn-default">Cancel</button>
<input type="submit" value="Create" class="btn btn-success" />
</div>
</div>
</div>
<hr />
<h3 class="text-success">Original Request</h3>
<div class="row">
<div class="col-md-10">
<h4>#Html.DisplayFor(model => model.userRequest.Title)</h4>
</div>
</div>
<div class="row">
<div class="col-md-10">
<h4>#Html.DisplayFor(model => model.userRequest.Description)</h4>
</div>
</div>
}
<div>
#Html.ActionLink("Back to Browse", "Browse","Change")
</div>
Get ActionResult:
public ActionResult Create(int id)
{
UserRequestContextDataContext db = new UserRequestContextDataContext();
var request = (from m in db.Requests
where m.ID == id
select new Models.Request()
{
ID = m.ID,
Title = m.Title,
Description = m.Description,
BusinessUnit = m.BusinessUnit,
Author = m.Author,
ModuleName = m.MenuItem,
RequestStatus = 2,
SubmitDate = m.SubmitDate,
Type = m.Type,
UrgencyNum = m.UrgencyLevel
}).FirstOrDefault();
var reqResponse = new Models.RequestResponse();
var viewModel = new RequestResponseViewModel
{
userRequest = request,
requestResponse = reqResponse
};
return View(viewModel);
}
The "viewModel" here has everything I need. It's lost somewhere between the ActionResults..
And Finally the Post ActionResult:
[HttpPost]
public ActionResult Create(RequestResponseViewModel _requestResponseViewModel)
{
try
{
if (ModelState.IsValid)
{
using (UserRequestContextDataContext db = new UserRequestContextDataContext())
{
RequestResponse reqRes = new RequestResponse();
reqRes.Response = _requestResponseViewModel.requestResponse.Response.ToString();
reqRes.RequestID = _requestResponseViewModel.userRequest.ID;
reqRes.Author = _requestResponseViewModel.requestResponse.Author.ToString();
reqRes.CreateDate = DateTime.Now;
db.RequestResponses.InsertOnSubmit(reqRes);
db.SubmitChanges();
}
}
return RedirectToAction("Browse","Change");
}
catch (Exception ex)
{
return View("Error", new HandleErrorInfo(ex, "Change", "Create"));
}
}
Using debug mode the userRequest object is NULL in the view model parameter of the POST method but requestResponse is FINE and populated as should.
Searching on this, it seemed most had issues with the naming convention in the view model but I've made sure there are no discrepancies there.
If there is a more clear way to do this workflow please mention.
#Html.DisplayFor does not create an HTML input element, but a simple string literal (for most types, some exceptions are listed in the docs: https://msdn.microsoft.com/en-us/library/ee407420(v=vs.118).aspx#Anchor_1).
So when you press submit, your browser will not send the ID back to the server because it sends only form data (e.g. data from input, textare, select fields). Using your browsers developer tools (F12) you can examine what is actually send to the server.
You can add a hidden input field using #Html.HiddenFor(model => model.userRequest.ID) or use a custom display template for the ID to automatically add a hidden input field. You could further use UIHint attributes to automatically select a display template. Both approaches are thoroughly documented (e.g. http://www.codeguru.com/csharp/.net/net_asp/mvc/using-display-templates-and-editor-templates-in-asp.net-mvc.htm).
Another reason the object could be NULL in the POST is due to forgetting to add the setters { get; set; } in your view model:
public Orders orders; --> missing { get; set; }
public class OrderViewModel
{
public Orders orders { get; set; }
public List<VendorJobTitleView> Jobs { get; set; }
public List<ManagerView> Managers { get; set; }
}

Passing data from view to method Create in controller [duplicate]

This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 5 years ago.
I have the following problem with passing data to metod create in controller.
First I display view for method create (GET)
public ActionResult Create(string id)
{
RolesUser RolesUser = new RolesUser(id,repository.GetUserById(id).Roles, repository.GetRoles().ToList());
return View(RolesUser);
}
I use the following ViewModel
public class RolesUser
{
public RolesUser()
{
}
public RolesUser(string id,ICollection<IdentityUserRole> userRoles,List<IdentityRole> Roles)
{
userId = id;
this.Roles = GetNewRolesForUser(userRoles.ToList(), Roles.ToDictionary(x => x.Id, x => x.Name)).ConvertAll(
role =>
{
return new SelectListItem()
{
Text = role.ToString(),
Value = role.ToString(),
Selected = false
};
});
}
public IEnumerable<SelectListItem> Roles { get; set; }
public string userId { get; set; }
private List<string> GetNewRolesForUser(List<IdentityUserRole> UserRoles,Dictionary<string,string> Roles)
{
List<string> AvaiableRoles = new List<string>();
List<string> IdUserRoles = new List<string>();
UserRoles.ForEach(item => IdUserRoles.Add(item.RoleId));
foreach(KeyValuePair<string,string> Role in Roles)
{
if (!IdUserRoles.Contains(Role.Key))
{
AvaiableRoles.Add(Role.Value);
}
}
return AvaiableRoles;
}
}
It displays me essential information on my view in Create.cshtml, when I execute Submit it shows me following error
Object reference not set to an instance of an object.
The metod create (POST) looks like this
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize(Roles ="Admin")]
public ActionResult Create([Bind(Include ="userId")] RolesUser user)
{
if (ModelState.IsValid)
{
try
{
repository.AssignToRole(user.userId, "Klient");
repository.SaveChanges();
}
catch
{
ViewBag.exception = true;
return View();
}
}
ViewBag.exception = false;
return RedirectToAction("Index");
}
Code for Create.cshtml
#model Repository.ViewModels.RolesUser
#{
ViewBag.Title = "Create";
}
<h2>Dodaj poziom dostępu</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>#Model.userId</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.userId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DisplayFor(model => model.userId, new { htmlAttributes = new { #class = "form-control" } })
</div>
</div>
<div class="form-group">
#Html.Label("Poziomy dostępu", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(m => m.userId, Model.Roles)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Dodaj" class="btn btn-default" />
</div>
</div>
</div>
}
It looks like viewmodel is not passing to method, so I have null exception. But I don't understand why, if GET metod render properly view.
The NullReferenceException is likely hit inside the repository.AssignToRole() method because user.userId is null. You should add a hidden input for the userId somewhere inside the form so the userId value is posted to the parameter object on the Create action. If you add it after the #Html.AntiForgeryToken() the start of the form would look like
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.HiddenFor(model => model.userId)
...

MVC Unobtrusive custom rule only works in one form on client

I built a custom validation rule for one particular field on my MVC 5 app. It works great on the edit form, but when validating that same field on the "create' form, the client side validation does not fire - The client side validation is triggered, but on the create form it shows as valid, even though I can see it is not. So no message is shown.
Both forms use the same model.
The scripts are added in the _layout page so both views have all the scripts.
Both views have the exact same razor code including the ValidationMessageFor()
When the form gets to the controller, the model is not valid due to the custom error. So the validation is working on the server side, but not the client. I can't find anything that would make it work in one form but not the other.
Here is my code:
Custom attribute:
public class AtLeastOneRequiredAttribute : ValidationAttribute, IClientValidatable
{
public string OtherPropertyNames;
public AtLeastOneRequiredAttribute(string otherPropertyNames)
{
OtherPropertyNames = otherPropertyNames;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
string[] propertyNames = OtherPropertyNames.Split(',');
bool IsAllNull = true;
foreach(var i in propertyNames)
{
var p = validationContext.ObjectType.GetProperty(i);
var val = p.GetValue(validationContext.ObjectInstance, null);
if(val != null && val.ToString().Trim() != "")
{
IsAllNull = false;
break;
}
}
if(IsAllNull)
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
else
{
return null;
}
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rules = new ModelClientValidationRule()
{
ErrorMessage = FormatErrorMessage(metadata.DisplayName),
ValidationType = "atleastonerequired"
};
rules.ValidationParameters["otherpropertynames"] = OtherPropertyNames;
yield return rules;
}
}
Client code:
$(function() {
$.validator.unobtrusive.adapters.addSingleVal("atleastonerequired", "otherpropertynames");
$.validator.addMethod("atleastonerequired", function (value, element, params) {
var param = params.toString().split(',');
var IsAllNull = true;
$.each(param, function (i, val) {
var valueOfItem = $('#Activity_' + val).val().trim();
if (valueOfItem != '') {
IsAllNull = false;
return false;
}
});
if (IsAllNull) {
return false;
}
else {
return true;
}
})
})
View - Edit & Create Forms are identical:
#using (Html.BeginForm("Edit", "Activity", FormMethod.Post, new { #id = "editActivityForm" }))
{
#Html.AntiForgeryToken()
<div class="form activity-form">
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.Activity.RecordId)
<div class="form-group">
#Html.LabelFor(model => model.Activity.Acres, htmlAttributes: new { #class = "control-label" })
#Html.EditorFor(model => model.Activity.Acres, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Activity.Acres, "", new { #class = "text-danger" })
</div>
<div class="form-group">
#Html.LabelFor(model => model.Activity.Volume, htmlAttributes: new { #class = "control-label" })
#Html.EditorFor(model => model.Activity.Volume, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Activity.Volume, "", new { #class = "text-danger" })
</div>
<div class="form-group">
#Html.LabelFor(model => model.Activity.Feet, htmlAttributes: new { #class = "control-label" })
#Html.EditorFor(model => model.Activity.Feet, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Activity.Feet, "", new { #class = "text-danger" })
</div>
<div class="form-group">
#Html.LabelFor(model => model.Activity.Hours, htmlAttributes: new { #class = "control-label" })
#Html.EditorFor(model => model.Activity.Hours, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Activity.Hours, "", new { #class = "text-danger" })
</div>
<div class="form-group">
#Html.LabelFor(model => model.Activity.Comment, htmlAttributes: new { #class = "control-label" })
#Html.EditorFor(model => model.Activity.Comment, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Activity.Comment, "", new { #class = "text-danger" })
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" onclick="$.validator.unobtrusive.parse($('#editActivityForm'));" />
</div>
</div>
}
Model with attirbute added:
[AtLeastOneRequired("Acres,Volume,Feet,Hours", ErrorMessage = "Activity requires at least one measure - Acres, Volume, Feet or Hours.")]
public Nullable<int> Acres { get; set; }
public Nullable<int> Volume { get; set; }
public Nullable<int> Feet { get; set; }
public Nullable<int> Hours { get; set; }
The problem was with the client code. I finally found the client code was not being hit. I eventually found it was because the validation add was inside (function() {}). I deleted that 'ready' piece and now it works every time.

Resources