The problem is Razor (MVC 5) renders out HiddenFor and Hidden helper incorrectly.
I have a view model like this:
public class MyViewModel
{
public bool BoolValueInsideViewModel { get; set; }
public MyViewModel()
{
BoolValueInsideViewModel = false;
}
}
and controller is simple as:
model = new MyViewModel();
model.BoolValueInsideViewModel = false;
return PartialView("_MyView", model);
Second line is just to be sure value is set to false.
View looks like this:
#model MyViewModel
#Html.Hidden("BoolValueInsideViewModel", false)
#Html.Hidden("BoolValueNotInViewModel", false)
But on browser I get this:
<input data-val="true" data-val-required="The BoolValueInsideViewModel field is required." id="BoolValueInsideViewModel" name="BoolValueInsideViewModel" type="hidden" value="true">
<input id="BoolValueNotInViewModel" name="BoolValueNotInViewModel" type="hidden" value="False">
Note that I have like 15 other view models and controller methods in same controller works fine with same code (I use HiddenFor with strongly typed values normaly, here I changed to Hidden to show that it does not work even with hard coded value in view).
Putting a break point on view, shows model is actually has BoolValueInsideViewModel equal to false, but result on browser is different. Also I get the result via JQuery post callback, so I checked raw string returning from AJAX function and the value there is also "True" instead of false.
What I tried:
Clean up project and rebuild!
Renaming property
Renaming view model, even renaming file containing view model
Renaming view
I know! It looks stupid and the most simplest thing on ASP.NET MVC, but I spent half a day on it. Other view models and partial views render correctly.
The weird thing is I don't have any data annotation on view model, but why it includes data validation attributes on the HTML element?
For me, it was because my url had a query parameter with the same name as the viewmodel property. It seems the model binder is overriding the model value with that in the query string.
I've confirmed this by changing and removing the query string parameter and values, and get consistent results.
For example,
let's say the query string param is: showMore=False
you have a model property of the same name, set to True.
check the value of Model.showMore will be True
check the value of the #HiddenFor(m => m.showMore), it will be False
change the query string param to showMore=True, check the value of #HiddenFor(m => m.showMore), it will be True
remove the query string param showMore, check the value of #HiddenFor(m => m.showMore), it will be True
Conclusion: the query string parameter value overrides the model value. Not sure if that is by design or a bug.
Related
I have the following controller that renders an HTML view. Inside this controller, I have defined a model.attribute("bill", bill); , which renders the default value of 0 on the view. The controller looks like this:
#RequestMapping(value = "/products", method = RequestMethod.GET)
public String index(Model model, Product product) {
//not relevant code above
String bill = "0";
model.addAttribute("bill", bill);
I have another controller in different class that I want to update the value of bill and redirect me to the same page. My attempt to achieve this ended in producing this:
#RequestMapping(value="/products/checkout", method = RequestMethod.POST)
public String getBill(#RequestParam("checkout") String order, #RequestParam("bill") String bill, #ModelAttribute Model model) {
String finalBill = "124pounds";
model.addAttribute("bill", finalBill);
And the view looks like this:
<form th:action="#{/products/checkout}" method="post">
<h3> Please type your order:</h3>
<input type="text" th:name= "checkout" id="checkout" placeholder="banana,apple,tomato (separated with commas)">
<input type="submit" value="Checkout">
<h3>Your bill is :<span th:name="bill" th:text="' '+${bill}+' c'"></span></h3>
</form>
What I want to achieve is to make the second controller called getBill() to update the value of bill and redirect to the same page. I got lost a bit and I am not sure how to achieve the desired functionality.
Note: String bill = "0"; and String finalBill = "124pounds"; are just there to test if the value is changing when the Checkout button is pressed. The error I am getting looks like this.
There was an unexpected error (type=Bad Request, status=400).
Required String parameter 'bill' is not present
org.springframework.web.bind.MissingServletRequestParameterException: Required String parameter 'bill' is not present
at org.springframework.web.method.annotation.RequestParamMethodArgumentResolver.handleMissingValue(RequestParamMethodArgumentResolver.java:204)
at org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver.resolveArgument(AbstractNamedValueMethodArgumentResolver.java:114)
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:167)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:134)
What happens now is that the default 0 value is shown, but when I click the Checkout I get the error above. Basically I want to update the value from the getBill() and return the same view, which I am not sure is possible ?!
If I understand correctly, you are trying to only change the value o bill, and then return to the same html file, which is the one with the form. If that is correct, then all you need to do is remove the #ModelAttribute from getBill() and add the order string to the model.
it would look something like this:
#RequestMapping(value="/products/checkout", method = RequestMethod.POST)
public String getBill(#RequestParam("checkout") String order, #RequestParam("bill") String bill, Model model) {
String finalBill = "124pounds";
model.addAttribute("bill", finalBill);
model.addAttribute("checkout", order);
return "yourview";
Form 1 is a set of filters, and the "submit" button applies those filters to a GET method. Form 2 is a set of data, and the "submit" button saves that data and also continues the filters via a POST method. The filter options are passed back and forth - the user GETs the initial page, possibly sets up some filters which GETs the same page again in terms of controller method, then the user can modify some data and save via a POST, which then returns back with a GET for the same filtered page.
Simplified (lots more that is likely to be irrelevant):
#model PagedList.IPagedList<xxx.Models.WttPlatformGridRow>
#using (Html.BeginForm("PlatformGridEdit", "Wtt", FormMethod.Get))
{
#Html.CheckBox("ExcludeThrough", (bool)ViewBag.ExcludeThrough)
<input type="submit" value="Filter" />
}
#using (Html.BeginForm("PlatformGridEdit", "Wtt", FormMethod.Post))
{
#Html.Hidden("ExcludeThrough", (bool)ViewBag.ExcludeThrough)
<input type="submit" value="Save" />
}
Simplified controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult PlatformGridEdit(List<WttPlatformGridRow> rows, bool? excludeThrough)
{ etc }
public ActionResult PlatformGridEdit(bool? excludeThrough)
{ etc }
Obviously naming two elements the same is illegal in HTML, and it doesn't work anyway (parameter is null in the C# method).
The answers I've seen so far suggest a single BeginForm with all the data. Fine. Except one is a GET (no data changes) and one is a POST (data changes). User need to be able to bookmark the filter so I can't handle it all as a POST anyway, otherwise the browser will ask the user if resubmitting form data is okay.
I'm also using an IPagedList which (as far as I know) precludes the use of a single model with a list field instead of using ViewBag.
Another option seems to be using client side scripting to copy the value from one field to another. But I don't see how to do this when the parameter name in the controller method is the same for both client side fields.
What is the best way of handling this please?
I have a solution but I can't help thinking there must be a better way.
FWIW, this is what I've done. The two fields have non-identical names (but similar). The "master" version (visible checkbox) has script that copies the value to the "slave" version (hidden field) on submit. The controller method takes both names and decides which is relevant - one or both should be null, but both shouldn't have a value, but we handle that just in case.
Finally, the controller returns the view with the combined value (using the "master" identity).
View - form 1:
bool excludeThrough = ViewBag.ExcludeThrough != null ? ViewBag.ExcludeThrough : false;
#Html.CheckBox("ExcludeThrough", excludeThrough, new
{
#class = "form-control",
onchange = "document.getElementById('ExcludeThroughFilter').value = document.getElementById('ExcludeThrough').value;"
})
View - form 2:
#Html.Hidden("ExcludeThroughFilter", excludeThrough)
Controller:
public ActionResult PlatformGridEdit(..., bool? excludeThrough, bool? excludeThroughFilter)
{
bool excThru = false;
if (excludeThrough.HasValue) excThru = excludeThrough.Value;
if (excludeThroughFilter.HasValue && excludeThroughFilter.Value) excThru = true;
...etc...
}
I have a main view using several partial Views.
Each of these partials use a different model and have post action.
My problem is I need one property from my main view's model to be used in one of my partials.
The partial view which I need to pass this property view is the last stage in the process.
The application reaches a partial view that contains a switch statement , based on the status on the item being queried, decides which partial will be rendered.
I have the property passing that far and even have it included in the Renderaction for the partial but I don't know how to retrieve it in the controller, PartialViewResult.
In the main view:
#{Html.RenderPartial("StatusForm", Model.HeadingDataModel.Status, new ViewDataDictionary { { "PurchaseOrderNumber", Model.AccordionModel.LtsSpecific.PurchaseOrderNumber } });}
PurchaseOrderNumber is what I'm after. The value gets passed to the next stage:
#{
var obj = ViewData["PurchaseOrderNumber"];
}
And within the same view:
Html.RenderAction("FinishedCalibrationForm", obj);
How can I retreive this in my controller ?? The following is not correct I know, but you get the idea.
public PartialViewResult FinishedCalibrationForm( string obj)
All help is appreciated.
Calling Html.RenderAction or Html.Action is largely the same as Url.Action. There's many different overloads, but essentially, the first parameter is the action name, the second parameter is going to be either the controller name or an anonymous object of route values, and the third parameter will be an anonymous object of route values if the second parameter was used for the controller name.
Anyways, whatever you pass in the route values will be used to find and call the associated action, which includes parameters for the action. So, for your example:
Html.RenderAction("FinishedCalibrationForm", new { obj = obj })
Would properly pass obj into your action method. As you have it now, it's going to interpret the value of obj as the controller name the action is within, which is obviously not correct.
I have many submit buttons in my plain HTML . The one not working is as below:- the other are as same as below
< form:submit cssClass="action-button" name="excelBTNX" value="Excel" id="excelBTNX" />
The function of the above button in the controller is to create a excel sheet and put in session(I can download it from cookies ) and returns back .
The defination of the corrosponding method in Controller is as same as for other buttons which are working fine .
The problem with this is ,it works only at even count hit .When I click for the first time the page gets refreshed . When I click for the second time , control passes to the controller and my excel comes up as cookies.
I tried to track whether the submit is working or not with javaScript code as
$(‘form’).submit(function(){
alert("event getting fired");
});
and it gives the alert for both the cases.
I have done the validation part from the controller(manually), so local inbuilt validators are not used . So I believe they are not the case.
How do I fix it ?
Controller codes:-
#RequestMapping(value = "execute.action", method = RequestMethod.POST, params = "excelBTNX")
public String excelOut(HttpServletRequest request, HttpServletResponse response,
#ModelAttribute("mymodel") myModel model, BindingResult bindingResult, ModelMap modelmap) {
scr14(request).initializeSomeCalculation(model);// some innercalss called to manupulate model
HttpSession session = request.getSession(false);
if(1=1){//CRUD condition here true in READ mode.
model= new myModel ();
}
byte[] excel = createExcelS14(model,request);
String fileName = getExcelName() + ".xls";
String filepath = myFrameWorkUtils.createTempFile(excel, fileName);
if (session != null) {
session.setAttribute(fileDownload, filepath);
}
scr14(request).initializeSomeCalculation(model);
model.setDate(somedate);
return "myPanel";}
Here are some steps:
Check whether this issue is related to your Excel processing or whether it is something with your Controller. I assume you have something like
#RequestMapping(..., params = "excelBTNX")
public ModelAndView next(...)
{ <EXCEL FUNCTIONALITY> }
Just comment out the in the Controller and verify that the method is called every time. Please test this a let us know whether this is working.
What happens that makes you think the Controller is only called at the second click? Maybe the signs that you are looking at don't really mean that the controller is only called every second click. Please explain.
Fix if (1=1) code. = in Java is the assignment operator, == is the comparison operator. I assume you want to do a comparison. It also seems like you simplified this part of the code, but it may actually be the problem. Please post the actual code here.
I don't see anything about cookies here. It looks to me like you are creating a temporary Excel file, and setting the name of the file in the session.
session.setAttribute(fileDownload, filepath) cannot work, since the key of the session attribute map is of type String. It should probably be session.setAttribute("fileDownload", filepath).
Can you see whether there is a new temp Excel file generated with each click? You should be able to tell by the timestamp.
This may still not point to the problem, but it will certainly get us closer.
I have a form and the only item that is required is the customer name. So in my model, I have:
[DisplayName("Customer name*:")]
[Required]
public string CustomerName
{ get; set; }
Previously, I was doing an HTML post and everything worked fine including validation.
Now, I've "ajaxified" the form, using Ext.direct.mvc (http://code.google.com/p/ext-direct-mvc/), which is a significant fact, and posting the data in Json format and the data is getting posted successfully.
When I put a breakpoint in my code (currently modified for debugging purposes):
[DirectInclude]
[HttpPost]
public ActionResult SaveOrUpdateOrderLines(CustomerOrderModel customerOrderModel)
{
if (!ModelState.IsValid)
{
return ModelState.JsonValidation();
}
return null;
I see that the CustomerOrderModel.CustomerOrderHeader.CustomerName = ""
But the ModelState.IsValid is true.
Now for some of the things I've tried:
I have inserted the following code, just before checking for ModelState.isValid, in order to ensure that CustomerName = null
customerOrderModel.CustomerOrderHeader.CustomerName = null;
I have tried using TryUpdateModel(customerOrderModel) but I get the following error message:
TryUpdateModel threw an exception of type 'System.MissingMethodException'
I've tried modifying the json data so that the "root" "CustomerOrderHeader" was renamed to "customerOrderModel" to match the parameter.
None of these things worked. So what could I be doing wrong that validation isn't working anymore? What steps can I take to debug the issue?
EDIT for counsellorBen
EDIT 2 for counsellorben
The problem is that when trying to bind a Json response, the name of the variable in your controller action must match the name of the variable passed on the client side. Your model is valid, because CustomerOrderHeader is null.
In your client script, you should wrap your entire model in an element named "customerOrderModel", so the name matches the variable name in your action.