JavaScript populated DropDown not valid in ModelState - c#

What I'm doing: I have 3 combo boxes. The selection in the first 2 boxes determines the list in the 3rd box. Whenever a selection is made in either of the first 2 boxes, I run some jQuery that makes an $.ajax call to get the list for the 3rd box. The 3rd box is my "Item Description" box.
The problem: The jquery ajax call works. When I pick the values in the first 2 boxes, the Item Descriptions are loaded into the 3rd box. However, when I try to submit the form, the ModelState.IsValid = false and the ModelState error message says, "Item Description is required."
This is from the meta data for my model:
[Required(ErrorMessage = "Item Description is required.")]
public int ItemDescriptionID { get; set; }
I don't want the user to be able to submit the form without selecting a description. I could just validate after submission, but I was hoping MVC would do the validation.
Here's the JavaScript I am using...
function getModels() {
catId = $('#ItemModel_ItemCategoryID').val();
manuId = $('#ItemModel_ItemManufacturerID').val();
// remove all of the current options from the list
$('#ItemDescriptionID').empty();
// send request for model list based on category and manufacturer
var jqxhr = $.ajax({
url: '/Threshold/GetModels',
type: 'POST',
data: '{ CategoryID: ' + catId.toString() + ', ManufacturerID: ' + manuId.toString() + ' }',
contentType: 'application/json',
dataType: 'json',
cache: false,
async: true
});
// received list of models...
jqxhr.done(function (data) {
if (data == null) return;
try {
var ddl = $('#ItemDescriptionID');
// add each item to DDL
$.each(data, function (index, value) {
ddl.append($('<option/>', {value: data[index].ItemDescriptionID}).html(data[index].ItemDescription))
});
}
catch (result) {
alert("Done, but with errors! " + result.responseText);
}
});
// failed to retrieve data
jqxhr.error(function (result) {
alert("Error! Failed to retrieve models! " + result.responseText);
});
}
So what am I doing wrong? The form will submit if I remove the Required metadata tag. Also, after the form submits, I am able to grab the ID from the dropdown and everything works. The problem is with the modelstate/validation somehow.
EDIT:
This is my Razor for the description box...
#Html.DropDownListFor(m => m.ItemDescriptionID, new SelectList(new List<SelectListItem>()) )
#Html.ValidationMessageFor(m => m.ItemDescriptionID)

If I understand your question correctly the problem you perceive is that even if the user hasn't selected anything valid from your dropdown they can still post to the server. The Model appears to be validating correctly, when the user hasn't selected anything ModelState.IsValid is false and you get the correct error message. But you want to avoid the data being posted at all if a selection hasn't been made.
The issue here is that ModelState and all the MVC validation is actually done server side. So in order for your application to detect that the user's choices aren't valid according to your model's Data Annotations a form submit really is required.
What you want in order to stop users posting is client side validation, which is not part of MVC Model validation but MVC 4 has built in support for it.
Built-in MVC 4 client validation
In order to enable client side validation with the built-in MVC 4 jQuery validation do the following.
Enable MVC client validation support
Ensure that both "Unobtrusive JavaScript" (which will output the required HTML attributes) and "Client side validation" (which will wire up the javascript).
In web.config (for entire site)
<appSettings>
<add key="ClientValidationEnabled" value="true"/>
<add key="UnobtrusiveJavaScriptEnabled" value="true"/>
</appSettings>
Or in code - global.asax.cs, Views or Controllers depending on the scope you want
HtmlHelper.ClientValidationEnabled = true;
HtmlHelper.UnobtrusiveJavaScriptEnabled = true;
Include the required scripts in the Views
MVC 4 has prepared a bundle including jQuery validation and the unobtrusive validation scripts. This must be added after the jQuery bundle.
#Scripts.Render("~/bundles/jqueryval")
Always check ModelState.IsValid
Even if you have client-side validation enabled you have to perform server-side validation of posted data before you perform any actions. Whether the data that a user submits is valid or invalid your ActionMethod will still execute... So you need to check IsValid before you take action on the data that has been submitted.
[HttpPost]
public ActionResult ActionMethodName(ModelClass modelParameterName)
{
//exit directly on invalid data
if(!ModelState.IsValid) return View(modelParameterName);
//okay to save data etc...
...
...
//return whatever makes sense for your scenario
return View(modelParameterName);
}
Other options
You could also use jQuery validation manually or another third party javascript validation library.
Also custom javascript form validation is an option - return false from a function which is called from the form's submit event.
HTML5 has support for client-side validation attributes ('required' in your case) although not all browsers implement it.
If you perform validation yourself without using the MVC 4 unobtrusive scripts you should turn of the built-in feature to avoid conflicts and reduce the size of the returned HTML.
<appSettings>
<add key="ClientValidationEnabled" value="false"/>
<add key="UnobtrusiveJavaScriptEnabled" value="false"/>
</appSettings>
Or in code where you can control the scope you want it to apply to.
HtmlHelper.ClientValidationEnabled = false;
HtmlHelper.UnobtrusiveJavaScriptEnabled = false;

Related

How can I delete unavailable URL in browser history using C# .NET?

I'm building an animal feed supplying website using C# .NET
It has those features below:
http://localhost:52000/Account/Index =>show a list of accounts (ID, name...).
Click on an ID from Index page, it leads into Detail page: http://localhost:52000/Account/Details/6cc608a5-3b4b-4c6f-b220-3422c984919a
In account detail's page, it also has 2 buttons(functions): Delete account and Edit account information.
All I want is after deleting an account (in Detail view), website will redirect to the previous available page (Index,...). Therefore I use window.location.href = "/Account/Index/"; in Delete function.
Here is my delete function with redirecting solution:
function deleteAccount(id) {
var data = { 'id': id };
$.ajax({
*//....*
success: function (result) {
if (result) {
*//redirect to the previous page (Index)*
window.location.href = "/Account/Index/";
}
}
});
}
However,after deleting and redirecting to "/Account/Index/" successfully, if Admin click on Back Button on Browser, website redirect to unavailable page (the Detail page of that deleted account: http://localhost:52000/Account/Detail/6cc608a5-3b4b-4c6f-b220-3422c984919a).
Then I tried to use window.history.back();, window.history.go(-1);, window.location.replace("/Account/Index/"); in turn instead, it worked perfectly only when Admin just deletes that account, if Admin Edits this account first then updates then deletes (Press Edit in Detail view -> Go to Edit view -> press Update -> Go back to Detail View ) --> website redirect to unavailable page (the editing page of that deleted account: http://localhost:52000/Account/Edit/6cc608a5-3b4b-4c6f-b220-3422c984919a).
function deleteAccount(id) {
var data = { 'id': id };
$.ajax({
*//....*
success: function (result) {
if (result) {
*//redirect to the previous page (Index)*
window.history.back();
// or window.history.go(-1)
//or window.location.replace("/Account/Index/");
}
}
});
}
Is that possible to remove the unavailable URLs (those include ID of deleted account) in browser? How can I handle the Back Button in Browser to go through those unavailable URLs? (http://localhost:52000/Account/Detail/6cc608a5-3b4b-4c6f-b220-3422c984919a and http://localhost:52000/Account/Edit/6cc608a5-3b4b-4c6f-b220-3422c984919a)
You could try with the following:
window.location.replace("/Account/Index/");
This is the equivalent of an HTTP redirect using Javascript.
When you use window.location.href it would be as if a user has clicked on a link and therefore you can go back to the previous URL afterwards.

MVC javascript redirect page not working

In MVC, the default views for a controller allow one to reach the edit page via selecting an item in an index and using that id to reach the specific edit page.
In this MVC edit page, I have a javascript that reacts to a change in a dropdown. The dropdown represents a subset of the potential id's available from the index page, and in general, someone will choose a different one than the currently displayed one.
The postback to the control works correctly in C#, and I can find the relevant model that goes with the id. It all appears correct on the C# controller side. However, when I try to get it to redirect back to the same edit page but with a different id (that from the dropdown), the page reverts back to the ajax call.
Is there anyway to "short-circuit" the ajax call so that it "knows" that it doesn't return but lets the C# redirect to the edit page (just like what happens when an element is chosen from the index page).
Thanks in advance,
Joseph Doggie
If you are making ajax requet, then you have to implement a way to redirect.
Depends on your ajax protocol... Are you returning json? html ...
If returning json, you could add a flag in your response telling wether this is a redirect answer and do redirect in js :
window.location = url
OK, there is at least one way to do this.
Assume editing X with Controller named YController:
JavaScript:
var MyControllerUrlSettings = {
MyControllerPrepareModifyXInfoUrl: '#Url.Action("PrepareModifyAssetInfo", "Y", new { x_txt = "param" })'
}
one then has a JavaScript to handle the dropdown change:
$('#ModelXList').change(function () {
//// alert('Change detected');
if ($("#ModelXList").val() != "") {
//// alert('Reached here');
var XNbrString = $("#ModelXList").val();
var trimmedXNbrString = $.trim(XNbrString);
//// debugger;
if (trimmedXNbrString != "") {
var url = MyControllerUrlSettings.MyControllerPrepareXInfoUrl;
window.location.href = url.replace('__param__', trimmedXNbrString);
}
}
else {
}
});
Finally, in the controller, there is a method:
public ActionResult PrepareModifyXInfo(string XNbr_txt)
{
// we cannot save anything here to cdll_cdcloanerlist;
// static variables must be used instead.
/// .... do what you have to do....
return RedirectToAction("ModifyEdit", new { XNbr_txt = XNbr_txt });
}
Note: For proprietary reasons, I changed some of the syntax so that everything would be general, therefore, you may have to work with the above code a little, but it works
Alternate answers are really welcome, also!

Validating payment amounts with WorldPay

We are using WorldPay to process payments for a tiered membership system, for which the payment amount varies dependent upon the membership tier selected.
The payment is passed to WorldPay via a form post from a number of hidden fields, including:
<input type="hidden" name="amount" value="295.00" />
Essentially, the form is submitted via POST to WorldPay and the user follows a number of steps to process their payment. Once complete, the user is redirected to a specified confirmation page.
This appears to be the typical manner in which WorldPay accepts payments. There's an obvious issue here, in that the value of the hidden field could easily be tampered with by anyone with a basic knowledge of HTML. The form is posted directly to WorldPay, so we have no PostBack in which to validate the amount against the membership tier.
We have the option to validate the payment amount when a payment notification is returned to us from WorldPay by routing the callback through a handler before the confirmation page; however, I would like to avoid the situation where user submits a tampered form, pays the incorrect amount and receives no membership, then has to contact the company to have their money returned.
How might we validate that the amount being submitted is correct before processing payment?
Update
It has occurred to me that we have an additional problem whereby, even if we validate the form post server-side, there is nothing stopping a malicious user from spoofing the form post direct to WorldPay.
It is a vulnerability indeed, it can be solved easily using a signature. Check out this link:
http://culttt.com/2012/07/25/integrating-worldpay-into-a-database-driven-website/
This method should be better promoted on the help page, too bad.
One solution I can think of is this, capture the form tag's submit:
<form id="myForm" onsubmit="return validatePayment();">
and then create that JavaScript file that looks like this:
var isValidAmount = false;
function validatePayment() {
if (isValidAmount) { return true; }
// in here you want to issue an AJAX call back to your server
// with the appropriate information ... I would recommend using
// jQuery and so it might look something like this:
$.ajax( {
type: "POST",
url: url,
data: { amount: $("#amount").val(), someotherfield: somevalue },
success: function(data, textStatus, jqXHR) {
// set the flag so that it can succeed the next time through
isValidAmount = true;
// resubmit the form ... it will reenter this function but leave
// immediately returning true so the submit will actually occur
$("myForm").submit();
},
});
// this will keep the form from actually submitting the first time
return false;
}

JavaScript in C# ASP MVC issue

We have a web project that takes data from an MS SQL database and uses the Google Visualisation API to display these charts on the web view.
Recently we have added castle windsor so we can configure the application to different users with an XML file. Before we added this, the view worked fine, using the baked in parameters that were needed for this query. For some reason, when we send in the parameters from the XML files (Running with breakpoints shows that the parameters are being passed to the main controller action for the page) the data isn't being returned. here is some of the code for you.
JavaScript
<script type="text/javascript">
var csvDataUrl = '#Url.Action("TradeValuesDataCsv", "Dashboard")';
var jsonDataUrl = '#Url.Action("TradeValuesDataJson", "Dashboard")';
google.load("visualization", "1", { packages: ['table', 'corechart', 'gauge'] });
google.setOnLoadCallback(drawCharts);
drawCharts();
$("body").on({
ajaxStart: function () {
$(this).addClass("loading");
},
ajaxStop: function () {
$(this).removeClass("loading");
}
});
function drawCharts() {
var queryString = 'platform=' + $('#PlatformDropDownList').val();
queryString += '&startDate=' + $('#startDatePicker').val();
queryString += '&endDate=' + $('#endDatePicker').val();
queryString += '&model=' + $('#ModelDropDownList').val();
queryString += '&eventType=' + '#Model.EventType';
queryString += '&parameterName=' + '#Model.ParameterName';
$.ajax({
type: "POST",
url: jsonDataUrl,
data: queryString,
statusCode: {
200: function (r) {
drawToolbar(queryString);
drawTable(r);
drawChart(r);
},
400: function (r) {
},
500: function (r) {
}
}
});
}
Main controller Method for this page:
public ActionResult ActionResultName(EventTypeParameterNameEditModel model)
{
var viewModel = new EventTypeParameterNameViewModel(_queryMenuSpecific);
viewModel.EventType = model.EventType;
viewModel.ParameterName = model.ParameterName;
PopulateFilters(viewModel);
return this.View(viewModel);
}
Retrieve the JSON Data Controller Method:
public ActionResult ActionResultNameJson(EventTypeParameterNameEditModel filters)
{
List<CustomDataType> results = this.GetTradeValues(filters);
return this.Json(results, JsonRequestBehavior.AllowGet);
}
EDIT I have managed to find a solution, even if it is a rather messy one. I have some filters built into the page that allow the user to filter by device and by OS, and these were being populated on the page load with 'undefined'. I didn't spot this first time round with NHProf Running, but this wasn't happening when the page loaded before we configured the input to be from XML. I will add this as an answer and accept it and close the question. Thanks everyone for your attempts to help. Starting to really like this community. Perfect place to find help as a Graduate Developer.
Yep. I'm not a Razor syntax expert but I think these property references are probably your problem. I suspect razor is going to tend to avoid asserting itself inside strings being used in statements with properties in JS contexts. Or you could try implementing as getter functions which would probably work. Otherwise an # and a . in a string could easily lead to confusing mixups with email addresses when it's not an obvious method call:
queryString += '&eventType=' + '#Model.EventType';
queryString += '&parameterName=' + '#Model.ParameterName';
As a general rule in any server to client-side scenario, my advice is to confine JavaScript direct from the back end to JSON objects only. That way you have more granular control over what's going on on both sides of the http request wall and your client-side devs don't have to figure where stuff is getting built if there's a short-term need to quickly modify. In general, don't build behavioral code with other code if you can avoid it.
I couldn't convince my .net MVC boss at first but he slowly came around to the idea on his own months later.
We also store a URL base path along with some other context-shifting params in a standard JSON object that loads on every page so the JS devs can add these things linked JS files rather than have to work with JS in the HTML (I don't recall why but document.location wasn't always going to work).
Lastly, try to keep the JS out of the HTML. Link it. It seems like a pain from a procedural POV but trust me. It makes life much easier when you are juggling 3 major concerns as one ball each rather than all in the same jumbled HTML/template mess.
It turned out that the problem was not in my Javascript. I have some filters in there that allow the user to filter the results my model and operating system and date and what not. These were being automatically populated on page load with 'undefined' which is not an option in the database. I added something to catch that in the call to the query and it seemed to solve the problem.

Redirect without post back

I have a user registration form. Here I have link to another page (card info page) which has to be filled for the registration. User fills the few fields in the user registration form and click on the link that takes to card info page. When user clicks the link in card info page to navigate back to registration page, the previous details entered in registration got vanished. So I need to redirect from card info page to registration page without postback. How can i accomplish that?
Response.Redirect() is used for redirection.
You can't do this without a postback I don't think. I'd recommend storing the details from your registration page in session state then if the user returns to that page, re-populate the fields from session state.
//eg in registration page
protected void CardInfoLink_Click(object sender, EventArgs e)
{
//store details entered
Session["Registered"] = true;
Session["Username"] = txtUserName.Text;
//etc for any other fields
Response.Redirect("~/CardDetailsPage.aspx");
}
then in the Page_Load method you could, if the session data exists, pre-populate the form from session. e.g
if (!Page.IsPostback && (bool)Session["Registered"])
{
txtUserName.Text = (string)Session["Username"];
//repopulate other fields as necessary
}
When you redirect to another page it will lose all the context of that first page unless you do something to stop it. The options that spring to mind are:
Create the card info page in a popup window. This will mean that your main window is unchanged in the background. You'd preferably use purely client side code to do this but if you need server side code to do it its still possible, just slightly more fiddly.
Save the information on postback before redirect. This could either be just in session or in a database or you could even do it clientside in cookies if you want. Then when you revisit the page you can check if you have saved information and load it up automatically.
If you redirect the user to another page all captured info on that screen WILL be lost. View-state is not kept on redirects, but only on post-backs / callbacks
The only way to maintain information across redirects is to make use of Session Variables, Cookies, or even persisting the data to a Database / XML file and repopulate on return to that page.
I would suggest you save your info as the user gets directed to the info card, then on return, check for the values and re-populate it.
You can store the values in ViewState/Session and redirects to another page (card info page) and then re-populate the values when returning to registration page. Or showing pop-ups or panels (show/hide using Visible property) in the same page you can retain the user inputs. (If you are used server side controls the values are not cleared).
Server.Transfer() will perform a transfer to another page on the server-side.
Update: it would be possible to populate the current pages Context.Items property with the state originally being transferred by query string. This behaves similarly to session state but is limited to the current request.
Update 2: the Server.Transfer() method has an overload that accepts a bool parameter preserveForm, this preserves query string parameters:
http://msdn.microsoft.com/en-us/library/aa332847(v=VS.71).aspx
You can use any kind of ajax request on "go to the next page" button click to copy the registration data into session. Then after the returning you can populate the data again and to remove the session. Your code should be similar to this one:
----------------jquery ajax request-----------------------
function SetValuesIntoSession(value1, value2, value3) {
$.ajax(
{
type: "POST",
url: WebServicePathAndName.asmx/InsertIntoSessionMethodName",
contentType: "application/json; charset=utf-8",
data: "{value1:'" + value1 + "', value2:'" + value2 + "', value3:'" + value3 + "'}",
dataType: "json",
success: function(response) {
if (response.d == "Yes") {
//do something in correct response
}
if (response.d == "No") {
//do something for incorrect response
}
},
error: function(xhr) {
alert('Error! Status = ' + xhr.status);
}
});
}
below is the code for the web service, that should insert the data into the session. Note, that you must set "EnableSession = true" if you want to use session state in your web service
---------------------WebServicePathAndName.asmx------------------
[WebMethod( EnableSession = true )]
public void InsertIntoSessionMethodName( string value1, string value2, string value3 )
{
Session[ "value1" ] = value1;
Session[ "value2" ] = value2;
Session[ "value2" ] = value3;
}
I think, that the rest of the code should be easy to be implemented.

Categories