To put simply, I am scanning a drivers license with a magnetic strip card reader, using javascript RegEx to get the data I need, sending that data to the server as JSON via an ajax call, and then sending back a partial view with an Ajax.BeginForm with the data filled out.
In fact, all of this is working great. The problem is that I'm trying to alter the data on the server in C# before sending it to the view, but no matter what I do the original non-formatted data is showing up on the page still. I even stepped through the entire process, and looking at the Model data for the view shows that it received the correct data. Yet when the view is displayed, the non-formatted data (which does not actually exist in the Model object when I browse it) is what shows up in the text boxes.
function LoadVisitorCreatePartial(CardData) {
$.post(webroot + "Visitor/GetVisitorLogCreateVisitorForm", CardData, function (data) {
$('#visitor-info').append(data);
});
}
Above is the relevant javascript I am using for the call. I am using Carl Raymond's jquery card swipe to pull the data and I implemented some of my own RegEx to get the data I need for "CardData", which is JSON.
[HttpPost]
public ActionResult GetVisitorLogCreateVisitorForm(DriversLicenseForm CardData)
{
var form = visitorService.GetForm(CardData);
return PartialView("Form", form);
}
Above is the controller function that is being called. DriversLicenseForm is essentially the JSON object, as a C# class.
public VisitorForm GetForm(DriversLicenseForm CardData)
{
var textInfo = CultureInfo.InvariantCulture.TextInfo;
var DateOfBirth = DriversLicense.ParseDriversLicenseDate(CardData.DateOfBirthString);
CardData.FirstName = ConvertToTitleCase("i'm ron burgundy?");
CardData.LastName = textInfo.ToTitleCase(CardData.LastName);
var form = new VisitorForm();
if (DateOfBirth != null)
{
CardData.DateOfBirth = Convert.ToDateTime(DateOfBirth);
}
form = Mapper.Map<VisitorForm>(CardData);
form.ID = -1;
form = SetFormProperties(form);
return form;
}
In the above code you can see that I was getting annoyed. I decided to manually set the name and despite me setting CardData.FirstName to something completely different (verified when stepping through in debug mode) when the form comes back, it just shows my name from drivers license. I also have two title case functions because initially I thought that was the problem and found a non-textinfo solution, though that changed nothing and after debug stepping through, the data in CardData is being changed and updated properly. I feel it is worth noting one more time that the data is changed in debug mode in CardData and in the form after AutoMapper maps it out, my code appears to be working fine, except the actual view displays the original data despite there being no reference or reason for it to even know that data exists.
I literally have no idea what to do at this point.
EDIT: Here is the view code
#using (Ajax.BeginForm("PostForm", "Visitor", new AjaxOptions { HttpMethod = "POST", UpdateTargetId = "visitor-info", OnSuccess = "VisitorPostback" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.ID)
<div class="editor-label">
#Html.LabelFor(model => model.LastName)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.LastName, new { #class = "focus" })
#Html.ValidationMessageFor(model => model.LastName)
</div>
<p>
<input type="submit" class="btn btn-primary" value="Save" />
</p>
}
I cut out a lot of the actual textboxes, but left one as an example. They are all done the same way.
When sending data to a MVC Post action method, the ModelState gets populated with the data that you send. This is for instances when a form is POSTed that has validation errors, you can simply return the view back and all of the users form inputs are shown as well along with the validation errors.
Typically after a POST, you would redirect (PRG -> Post Redirect Get) to another action method, but it doesn't look like you would be able to do that here easily.
It looks like those ModelState values are being held onto the return of the "Form" view and is binding those to the matching form elements. Try adding ModelState.Clear() to your POST action method to see if that is what is causing the issue.
[HttpPost]
public ActionResult GetVisitorLogCreateVisitorForm(DriversLicenseForm CardData)
{
ModelState.Clear();
var form = visitorService.GetForm(CardData);
return PartialView("Form", form);
}
Related
first post. Trying so solve an issue I am seeing here between my Razor view and model. I have a popup window that is being fed a partial view and model.
public IActionResult ClickedCovid19Question(int id)
{
var existingQ = db.CustomerInfoItems.Find(id);
var suffix = existingQ.Suffix;
if (suffix.Length == 2)
suffix = suffix.Insert(0, "0");
var salesman = SalesmanHelper.GetSalesmanNum();
var par = db.Pars.Where(p => p.AccountNo == existingQ.CustNum).Where(p => p.Suffix == suffix)
.Where(p => p.SalesmanNumber == salesman).FirstOrDefault();
var clickedCovid19Model = new ClickedCovidQuestionModel
{ //insert model data here }
clickedCovid19Model.Machines = db.MachinePopulationItems
.Where(m => m.CustNum == existingQ.CustNum)
.ToList();
return View("~/Views/Covid19/_ClickedCovid19Question.cshtml", clickedCovid19Model);
}
This works great on the first page render. I see the data fill my UI elements that are called from the #model on the razor page. When my user updates a field here and submits, I use this function
function SaveClickedCovid19Question(idJS) {
C19ParChanged('#Model.Par');
var jsonJS = SerializeForm("#c19QuestionForm");
$.ajax({
url: '/Covid19/SaveClickedCovid19Question',
type: 'GET',
data:
{
id: idJS,
json: jsonJS
},
success: function (data) {
Alert(data);
RefreshLV("CovidQuestions");
HideWindow("#Covid19Question");
},
error: function (data, error, e2) {
debugger;
}
});
}
However, after the popup window is closed, and another popup is opened, we call that same ClickedCovid19Question IActionResult to populate our form again. It populates the UI fine with the new model it generated, debugging shows it creates a new model with all the correct data.
The issue arrises when a user submits this form again, the model on the Razor view seems to think it is still the model of the very first submission. An example of this is the first line of the javascript function. When the razor view was created, it had the correct #Model.Par data, as I could see when I created a few elements to display it. However, when trying to capture that data using #Model.Par, it captures the original Model.Par data.
Long story short, subsequent popups don't overwrite the Model data from the very first one. I am stumped, because this system works in so many other areas of our codebase.
I can fix this buy creating hidden elements that will store the data I need to send in any requests, but I feel like there has to be a better answer than that.
Edit: Below is an example I was using to test. The view part below will always display the correct par data in the id=parID input box. However, on the second popup and everytime after, if I was to run the simple javascript function below to find the data held by model, it will ALWAYS update to the data from the first model the page ever rendered, which seems inconsistent with other areas of my code that do work normally.
function updateParID() {
$("#c19QuestionForm").find("#parID").val('#Model.Par');
}
<input id="parID" type="text" disabled="disabled" value="#Model.Par"/>
<partial name="Forms/_FormDropDown" model=#(new FormDropDownModel { Name = "Par", Values = Lists.ParStates(), Value = Model.CallsPerYear}) />
<button type="submit" onclick="RefreshWindow('#Model.ID')">Refresh</button>
<button type="submit" onclick="updateParID()">Update Par ID</button>
That #Model.Par (or whatever prop you have in #Model) renders before anything shows up in browser.
Razor page will render your view and then pass it to browser.
so if you want to fetch data using ajax you should manually put data received from ajax into you html controls.
Have a nice coding day :)
So, I actually ended up solving this. My issue was, trying to bind the data from the #Model.Par or any model data INSIDE a javascript function will ALWAYS bind using the initial model. Every subsequent call to this function will ALWAYS only use that very first model.
My solution that I overlooked was to actually send the data to the function from the model as a parameter first, and not try to bind it inside the javascript function.
Example: My edit has these lines
function updateParID() {
$("#c19QuestionForm").find("#parID").val('#Model.Par');
}
<button type="submit" onclick="updateParID()">Update Par ID</button>
When I change the order of how I capture that model to this:
function updateParID(parID) {
$("#c19QuestionForm").find("#parID").val(parID);
}
<button type="submit" onclick="updateParID('#Model.Par')">Update Par ID</button>
This now correctly captures the model data that is present. I am sure there is some reason javascript works this way, but it is unknown to me, if anyone can shed light on it. Forgive me if it is a simple answer, I am new to web programming. Thank you all for helping out!
I am at a loss on how to do this. I've tried every search I can think of. Any help/direction would be greatly appreciated.
The customer wanted to be able to load a partial view dynamically from a wysiwyg editor. So, I made a method in the controller that searches for a special tag that gets the name of the partial view. Got that to work fine. Now the customer wants to load and post data to/from the partial view.
I was able to get it loaded by doing this - finalhtml += PartialView("~/Views/Global/Partials/" + commandContents + ".cshtml").RenderPartialViewToString();
If I name the controller the exact name of the partial view plus controller ie: SamplePartialController, I can get the view post back to the controller, BUT I can't figure out how to load an initial index method on the partial no matter what I try.
It would be nice if I could define the initial index method and controller name programmatically, just not seeing a way.
I am fairly new to mvc, been doing asp.net for over a decade, and this may be very obvious, but I'm just not finding a way. Thanks!
Not sure I understand clearly the issue. I also had a lot of troubles working with partial views (to post data, to load them back if something wrong happened in the post action...).
It seems like you want to fill your partial view if some parameters are here when loading the index.
In the Controller.Index (or where you get your parameters) you can assign some ViewModel values, or use the ViewBag/ViewData.
In the index view, you can call where you need the RenderPartialAsync() based on your parameters setted previously
this.ViewData["error"] = Model.Error;
this.ViewData["partialGuid"] = Guid.NewGuid();
this.ViewData["foo"] = bar;
await Html.RenderPartialAsync("_PartialViewName", Model.PartialViewModel, this.ViewData);
And use your partial view the same way you do from an ajax call.
If you need to post data from your partial view, I struggled a lot with some things:
A FormContext is required in the partial view
#if (this.ViewContext.FormContext == null)
{
this.ViewContext.FormContext = new FormContext();
}
If you allow to push multiple times your partial view and post them, making a custom collectionId helps a lot when in the controller post action
#{
var collectionId = $"PartialItems[{this.ViewData["partialGuid"]}].{{0}}";
}
// then later...
<input class="form-control"
data-val="true"
data-val-required="#(string.Format(ApplicationResources.Error_Required, ApplicationResources.FieldFirstName))"
id="#(string.Format(collectionId, "FirstName"))"
name="#(string.Format(collectionId, "FirstName"))"
type="text"
value="#(string.IsNullOrWhiteSpace(Model.FirstName) ? string.Empty : Model.FirstName)" />
<span class="field-validation-valid text-danger"
data-valmsg-for="#(string.Format(collectionId, "FirstName"))"
data-valmsg-replace="true"></span>
// And in your Index ViewModel, something like that to get the collection in the controller post action
public IDictionary<string, PartialViewModel> PartialItems { get; set; }
If you call the new partial view with Ajax, after the first render of the view, reset the jquery validator if needed
$.ajax({
// ...
success: function (res) {
// do your stuff
$('#parentId').prepend(res);
// reinit the validation
$("form").each(function () { $.data($(this)[0], 'validator', false); });
$.validator.unobtrusive.parse("form");
}
});
It is almost everything I can think of that bugged me when dealing the first time with all this partial view things. Hope something here can help you.
I am trying very hard to rewrite this question better than my previous effort which received no responses. Even though I’m nearly done with this application, I am still a relative newbie at programming and it seems like one challenge just leads to another. I have looked at many posts related to the problem of passing a parameter to several Partial Views in a single view page. So let’s take this in order from the AlertPick.cshtml page where the user chooses one of three Alert_Identifier/SelectedAlertIndex parameters from the application database. I’m only showing the #model and Select Tag Form.
#model edxl_cap_v1_2.Models.ContentViewModels.EdxlCapMessageViewModel
#{
<h4>#Model.Alerts.Count Alerts</h4>
<form asp-controller="Alerts" asp-action="PickAlert" method="post">
<select class="cap_select" id="cap_select" style="width:100%;max-width:95%;"
asp-for="SelectedAlertIndex" asp-items="Model.Alert_Identifiers">
<option>Select one</option>
</select>
<br />
<input type="submit" name="PickAlert" value="Pick Alert to Assemble EDXL-Cap Message" />
</form>
}
This takes the user to thePickAlert.cshtml page, a table of five rows where the first four rows are the Data Categories of the application: Alert, Info, Area and Resource each with the Alert_Identifier repeated as a reminder in a text box followed by its own submit button named Check Alert, Check Info, Check Area, and Check Resource, respectively. These submit buttons take the user to a _DetailsAlert.cshtml, _DetailsInfo.cshtml, _DetailsArea.cshtml, and _DetailsResource.cshtml pages and they work correctly, with the data item names and values from the record that matches the Alert_Identifier. The fifth row repeats the Identifier and its button reads Add All, to assemble the whole set together for review and takes the user to the_Assemble.cshtml page below, where the individual data categories are correctly assembled with the data item names, but lack the correct data values that match the record that corresponds to the Alert_Identifier. I’m thinking that I need to add a third parameter for the SelectedAlertIndex or Alert_Identifier to each of the #Html.Partial(...) Views, but I haven’t found the correct form/syntax for that, and If someone could supply that or point me to an example similar enough to this, I would deeply appreciate it.
#model edxl_cap_v1_2.Models.ContentViewModels.EdxlCapMessageViewModel
<!DOCTYPE html>
<head>
<meta name="viewport" content="width=device-width" />
<link rel="stylesheet" href="~/css/capv1_2_refimp.css" />
<title>Assembled EDXL-CAP Message</title>
</head>
<h4>Assemble EDXL-CAP Message</h4>
<!-- DetailsAlert -->
<div class="content-wrapper">
#Html.Partial("_DetailsAlert", Model.Alert)
</div>
<!-- End of DetailsAlert -->
<!-- DetailsInfo -->
<div class="content-wrapper">
#Html.Partial("_DetailsInfo", Model.Info)
</div>
<!-- End of DetailsInfo -->
<!-- DetailsArea -->
<div class="content-wrapper">
#Html.Partial("_DetailsArea", Model.Area)
</div>
<!-- End of DetailsArea -->
<!-- DetailsResource -->
<div class="content-wrapper">
#Html.Partial("_DetailsResource", Model.Resource)
</div>
<!-- End of DetailsResource -->
Responding to first comment below, I'm showing the InfosController.cs code for _DetailsInfo(int? id) the controller action for the Info Data Category. It is virtually identical for each of the data categories except that the line ... .SingleOrDefaultAsync(m => m.InfoIndex == id); becomes ....SingleOrDefaultAsync(m => m.AlertIndex == id); and the method itself becomes_DetailsAlert(int? id).
// GET: Infos/Details/5
public async Task<IActionResult> _DetailsInfo(int? id)
{
if (id == null)
{
return NotFound();
}
var info = await _context.Info
//.Include(e => e.Elements)
// .ThenInclude(d => d.DataCategory)
.AsNoTracking()
.SingleOrDefaultAsync(m => m.InfoIndex == id);
if (info == null)
{
return NotFound();
}
return View(info);
}
PickAlert method from AlertsController follows:
public IActionResult PickAlert(Alert obj, int? SelectedAlertIndex)
{
if (SelectedAlertIndex.HasValue)
{
ViewBag.Message = "Alert loaded successfully";
}
return View(_context.Alert.Where(x => x.AlertIndex == SelectedAlertIndex));
}
I am not sure if I got the requirement correctly, but I think you have to create another model for all 4 partial views, e.g. for Alert, create a new model
class AlertModel:EdxlCapMessageViewModel
{
int SelectedAlertIndex {get;set;}
}
And then your view would look like:
<!-- DetailsAlert -->
<div class="content-wrapper">
#Html.Partial("_DetailsAlert",new AlertModel { Alert = Model.Alert,
SelectedAlertIndex = <ID SOMEHOW>
});
</div>
In .net core when I need to pass around a lot of data across the views, I usually find it cleanest to use services and DI. First, you can create a class that could store a set of data:
class MyDataForViews {
// the following is an example. You can have any properties
public string Info { get; set; }
}
You now have to add this class as a service. To do so go to your startup class and add the following within the services function:
services.AddScoped<MyDataForViews>();
Scoped means that the framework will create a new object of MyDataForViews for each HTTP request. No matter how many places you "inject" an object of MyDataForViews, it would use the same object across the current HTTP request. You can also replace the function with AddSingleton if you want to use the same object throughout the web app. The following is how you inject an object into your controller:
public class MyController : Controller
{
MyDataForViews myData;
// in controllers injection is done using the constructor
public MyController(MyDataForViews MyData) => myData = MyData;
public IActionResult Index()
{
myData = .... // assign all required data here
View();
}
}
Once this is done, instead of passing models to each view, you can inject the data into views using the following:
#inject MyDataForViews MyData;
Once you use this line on the top of any view, you can use the MyData object and there is no need to pass models to each partial view.
Here's a bit more detailed answer, since you've said at softwareengineering.stackexchange.com site that you still need help with this.
Let's first make sure you understand the basics correctly.
When it comes to passing data to the view, each controller in ASP.NET MVC has a property named ViewData, which is essentially a dictionary of key-value pairs. The ViewData itself has a property called Model, and this is what you access in the page using the Razor syntax #Model. You can use this property to pass a model that is strongly-typed, to avoid using magic strings for the keys of ViewData.
Note: ViewBag is a dynamic wrapper around the ViewData, so it's essentially the same thing (ViewBag.SomeProperty is the same as ViewData['SomeProperty']); the use of ViewBag is discouraged, though.
In a controller action when you do something like return View(), ASP.NET uses the cshtml page as a template to create actual HTML, and return it as the response to the client (this is all server-side).
There are a few ways to pass data to the view which are equivalent, for example:
ViewData.Model = someObject;
return View();
is the same as:
return View(someObject); // the View method can accept a model object
When it comes to partial views, by default, they get passed a copy of the parent page ViewData (this includes the reference to the Model), so you don't have to do anything special to pass this data to a partial view (but you can pass data of your choice if you want to).
The select tag helper renders (generates HTML) for the select element with the options specified. This is then sent as HTML to the client. On the client side, when the user clicks the submit button, a POST request is sent to the server, which is ultimately handled by the method PickAlert method on the AlertsController. If everything is setup correctly, you should get the SelectedAlertIndex as the parameter. Note that this is happening back at the server side, and that you now need to again return a page as the response.
You can pick the corresponding Alert object from your _context. Use the FirstOrDefault method for this instead of Where, as you only need a single item (convert types for comparison if necessary - e.g., if you have a string, but you are comparing to an int, or something along those lines).
var selectedAlert = _context.Alert.FirstOrDefault(x => x.AlertIndex == SelectedAlertIndex);
Now, all you need to do is set this selectedAlert and any other data that you need as a property on your model object (or under some key in ViewData), and render the correct view.
Note that if you just return View(model) without specifying the name of the view, the system will look for a view with the same name as your action method (here, PickAlert.cshtml), so use return View("ViewName", model) to change that if necessary.
For example, based on the code you've posted in your question, you could do something like this:
[HttpPost]
public IActionResult PickAlert(int? SelectedAlertIndex)
{
var model = new EdxlCapMessageViewModel(/* ... params, if any */);
if (SelectedAlertIndex.HasValue)
{
ViewBag.Message = "Alert loaded successfully";
var selectedAlert = _context.Alert.FirstOrDefault(x => x.AlertIndex == SelectedAlertIndex);
// I added a property to your model to store the alert;
// if you already have one, just use that one instead.
model.SelectedAlert = selectedAlert;
}
return View("YourViewName", model);
}
The YourViewName should be the parent view that has the partial views in it (the "Assembled EDXL-CAP Message" view, I presume).
BTW, I know that the way the system is passing the parameters to the action methods in a controller may seem a bit like magic, but it's convention-based. In the example above, it works because the parameter is named SelectedAlertIndex, and the model object has a property with the same name (and because you've specified that property in the select tag helper using asp-for="SelectedAlertIndex"). You can also modify the method signature so that it receives the entire model object (assuming that the model class is not too complicated - you can read more about how parameter binding works here):
[HttpPost]
public IActionResult PickAlert(EdxlCapMessageViewModel model)
{
// extract the index from model.SelectedAlertIndex
// you can also pass this same model object to the view
// (set some properties first if necessary)
// ...
}
Now for the partial views. Assuming that you are relying on the default mechanism which passes the parent ViewData to each partial view, you need to modify each partial view so that the code is written under the assumption that you can access the selected alert using #Model.SelectedAlert (the property you've set in the PickAlert action).
For example, a here's a simple partial view:
<div style="border: solid 1px #000000; padding: 30px; margin: 2px 2px 10px 2px;">
<p>The selected index is: #Model.SelectedAlert.AlertIndex</p>
</div>
Note that I'm just using the same model as in the parent view to access the SelectedAlert object: #Model.SelectedAlert.AlertIndex.
Again, when rendering the partial views, if you pass no additional parameters, they'll get a copy of the ViewData dictionary, and the same Model:
#Html.Partial("_DetailsAlert");
If you pass something else as the model, e.g., only the selected alert, then you need to change the partial view code accordingly:
#Html.Partial("_DetailsAlert", Model.SelectedAlert);
<div style="border: solid 1px #000000; padding: 30px; margin: 2px 2px 10px 2px;">
<p>The selected index is: #Model.AlertIndex</p>
</div>
Note that now, in the partial view, the local #Model refers to what was #Model.SelectedAlert in the parent view. (In other words, here #Model is of type Alert.) This only affects the ViewData.Model property; the key-value pairs stored in ViewData are still the same as those in the parent view.
I am using a razor file for my view with this post form:
#using (Html.BeginForm("Save", "MyEventController", FormMethod.Post))
{
<md-input-container class="md-block" flex-gt-xs="">
<label>Title</label>
<input type="text" name="title" />
</md-input-container>
<md-input-container class="md-block">
<label>Address</label>
<input type="text" name="address" id="txtAddress">
</md-input-container>
<md-button class="md-raised">
<input type="submit" value="Save" />
</md-button>
}
I want to send my input to my controller in Controllers/EventController.cs as show below:
public class MyEventController : Controller
{
[HttpPost]
public void Save(FormCollection collection)
{
InputEvent y = new InputEvent();
y.title = collection["title"];
y.address = collection["address"];
}
}
The Save() methods seems to never be called. how do i submit to Save()?
I am developing in Visual Studio 2015
Remove the text "Controller" when you specify the controller. Change it to,
Html.BeginForm("Save", "MyEvent", FormMethod.Post)
Remove the word "Controller" from your second parameter "MyEventController". So it should be "MyEvent".
Furthermore, I would create a Model (M from MVC) and when the form is posted, instead of receiving FormCollection, it will receive a nice model instead. ASP MVC will take care of binding for you.
Also, as others have pointed out, you may want to eventually return something back to the client to specify if the form submission and processing was successful. Therefore, your action should not return void. Most of the time in cases such as this, you should use the PRG pattern-it is very important to use this pattern.
Try ActionResult instead of void.
From MSDN:
Writes an opening tag to the response. When the user submits the form, the request will be processed by an action method.
I will show some minimum code, since I don't think my company wants me showing a whole lot, although we are currently just doing research.
We are using POCO in Entity Framework 4 to save data back to a database. We are using data annotations to try to reduce the amount of duplicate validation we have to do (which was a problem with our old classic ASP solution, where we had the SAME validation occurring on three different levels of the application).
We want to include business rules in our model, which includes making sure that fields that are validated against other tables are valid (we don't use dropdowns in our model, so users can type in anything). For example, we are storing room information in a building. The room has a field called "Room Type". Valid room types are defined in a different table.
We also want to have immediate client-side validation. For example, we have number fields that have to be between 0 and 32767. If the user types in -1, we use client side validation to immediately respond to the user to let them know -1 is invalid. We do that by turning on client side validation and using data annotations.
Web Config:
<appSettings>
<add key="webpages:Version" value="1.0.0.0" />
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>
Model:
public class Room : IValidatableObject
{
// I'm only showing the relevant properties...
// this is a field that's validated based on another table in the database.
// (In the model we are using autocomplete instead of a dropdown -- it's a long
// story --, so potentially invalid data can be passed through the form...
[DisplayName("Room Type"), MaxLength(5)]
public String RoomType { get; set; }
// A number field, with a range.
[Range(0, 32767), DisplayName("Maximum Seats")]
public Int16? MaxStudents { get; set; }
// do server side validation for this entity.
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var validateErrors = new List<ValidationResult>();
// make sure room type is a valid type
if(!String.IsNullOrEmpty(RoomType)) {
// hit database, which is a little weird since we need to create a context,
// and I know that might make some people's brains explode
}
// return all of the errors
return validateErrors;
}
}
Controller:
// I didn't include all the actions, just edit
public ActionResult Edit(Int32 Building, String RoomID)
{
// return a single room and show the detail information in edit mode.
Room room = findRoom(Building, RoomID);
return View(room);
}
[HttpPost]
public ActionResult Edit(Int32 Building, String RoomID, FormCollection collection)
{
// get the current room from the database.
Room room = findRoom(Building, RoomID);
// save the room being edited, but don't touch the key fields.
if (TryUpdateModel(room, null, null, new string[] {"Building", "RoomID"}))
{
// if the entity changed, audit and save
if (db.Entry(room).State == EntityState.Modified)
{
db.setLastUpdate(room); // this is a function in our context for auditing
db.SaveChanges();
}
}
return View(room);
}
View:
(not bothering to show the javascript used to create the autocomplete...)
#using (Html.BeginForm()) {
#Html.ValidationSummary(false, "The following errors occured when attempting to save this Room:")
<fieldset>
<legend>Room</legend>
<div class="field-block">
<div class="editor-label">
#Html.LabelFor(model => model.Building)
</div>
<div class="editor-field">
#Html.DisplayFor(model => model.Building)
</div>
</div>
<div class="field-block">
<div class="editor-label">
#Html.LabelFor(model => model.RoomID)
</div>
<div class="editor-field">
#Html.DisplayFor(model => model.RoomID)
</div>
</div>
<div class="field-block">
<div class="editor-label">
#Html.LabelFor(model => model.RoomType)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.RoomType)
</div>
</div>
<div class="field-block">
<div class="editor-label">
#Html.LabelFor(model => model.MaxStudents)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.MaxStudents)
#Html.ValidationMessageFor(model => model.MaxStudents)
</div>
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
What I am noticing is that if the user types -1 in the Maximum Seats field, client side validation will fire and let the user know that the value must be between 0 and 32767.
If the user clicks submit, client validation fires again and shows in the validation summary on the top of the form.
If a user enters a valid value in Maximum Seats, but types in a wrong value in the Room Type field, client side validation indicates no errors (which I understand, since Room Type is not being validated on the client), and if the user clicks Submit, the IValidateObject.Validate function is called during the TryUpdateModel() call, which returns a validation error, which then displays on the page in the validation summary on the top of the form.
BUT, if the user types both a wrong number (-1) and an invalid Room Type, and clicks Submit, client side validation will fire BUT the server side validation won't, so they will only see client-side related errors.
Is there a setting, or some trickery in JavaScript I can use to call BOTH the client and server side validation?
If not, I think my only alternative is to do all the validation server side, which will give the user less feedback as they go from field to field, or do the Room Type validation (check the value is in the Room Type table through an ajax call) on the client side, which will duplicate effort.
Any ideas?
I think remote validation is what you are looking for. With this, you can do the thing what you mentioned about using ajax for the validation. You do the number validation thing on the client, and the room validation on the server, the results of which get reported with the client side validation.