RedirectToAction is swapping day and month on date - asp.net-mvc-3

I have a controller POST action that redirects to a GET method using RedirectToAction.
return RedirectToAction(SomeActionName, new { date = someModel.someUTCDate });
I have implemented a custom model binder which parses dates using the assumed culture and timezone information, so I am intercepting this correctly. The basic steps in my problem:
POST action is called with a date/time string, which is GMT Standard Time if not specified otherwise
The custom model binder parses this date to a DateTime with a Kind of Utc, and this is bound correctly in the correct format to the model
The POST action redirects to GET using RedirectToAction with the date that was bound as UTC time on the model
MVC3 is now trying to bind a DateTime string value with the month and day swapped around
Somewhere between calling RedirectToAction and rebinding the DateTime value for the GET action, a string conversion is taking place, which appears to be swapping the month and the day around.
Is there a simple way of ensuring that the correct string conversion takes place without the overhead of having to manually convert it for each RedirectToAction call? I would really like to know where this route value to string conversion takes place and whether or not I could influence it to do the correct string conversion.
To illustrate what I have a bit better, I have a custom DateTime model binder
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
ValueProviderResult value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (value == null || string.IsNullOrEmpty(value.AttemptedValue) || value.AttemptedValue == "null")
{
return null;
}
return FormatHelper.ConvertStringFromGMT(value.AttemptedValue);
}
On redirect I am finding that the model binder's value.AttemptedValue is an incorrectly formatted string.

Well, the issue is now resolved. As far as I can tell, when MVC gets the route values for your redirect, the route values are converted to query string parameters using the invariant culture. This is just some magic that happens behind the scenes. My solution to this problem was to override the controller methods RedirectToAction and RedirectToActionPermanent (the overloads that take a RouteValueDictionary). I iterate over the RouteValueDictionary and convert any date types here using the defined culture and timezone information. Fortunately all of my controllers were already inheriting from a custom base controller so the fix was simple to implement.

Related

Spring form backing object issue

I'm struggling with the way Spring handles form backing object during a POST. ScreenObject property that has a corresponding input tag in the html form survives the post, as expected. But if a property does not have an input tag, two scenarios occur:
If property is passed in as a request parameter, it survives the post.
If property is not passed in as a request parameter, it does not survive the post.
Here's my code.
screen object
private Integer hostSiteSectionId; // no input tag but survives if passed as a request param
private String name; // input tag so survives
private String orderFactor; // input tag so survives
private Integer hostSiteId; // no input tag but survives if passed as a request param
private String hostSiteName; // no input tag and not passed as request param so does not survive
GET
#RequestMapping(method=RequestMethod.GET)
public ModelAndView edit(#RequestParam(value="hostSiteId", required=false) Integer hostSiteId, #RequestParam(value="hostSiteSectionId", required=false) Integer hostSiteSectionId, Locale locale) {
HostSiteSectionHeaderEditScreenObject screenObject=new HostSiteSectionHeaderEditScreenObject();
initializeScreenObject(hostSiteId, hostSiteSectionId, screenObject, locale, true);
ModelAndView modelAndView=new ModelAndView();
modelAndView.addObject("screenObject", screenObject);
modelAndView.setViewName(WebView.HOST_SITE_SECTION_HEADER_EDIT_PAGE.getViewName());
return modelAndView;
}
POST with CANCEL
#RequestMapping(method=RequestMethod.POST, params="cancel")
public String cancel(#ModelAttribute("screenObject") HostSiteSectionHeaderEditScreenObject screenObject) {
// logic that returns redirect
}
My initializeScreenObject() method only sets properties of the screenObject. It doesn't operate on the model. I don't see how it would interfere, so I'm not posting it's basic code.
Within this post, and it works the same in other posts, screenObject has the following:
All input provided by user in the form via input tags is present. No issue.
hostSiteId (no input tag) is present in the screenObject only if getter url included it as parameter (edit?hostSiteId=2 for example)
hostSiteSectionId (no input tag) is present in the screenObject only if getter url included it as parameter (edit?hostSiteSectionId=2 for example)
All other properties that have no corresponding input tags and are not passed in as request params are null.
To illustrate further #4. I have a screenObject.hostSiteName property which is set in initializeScreenObject() method. The view is rendered properly with <td>${screenObject.getHostSiteName()}</td>. Now I click Cancel submit control. When controller takes over the submit, this property is null.
Please explain if this is expected or not. If expected, please explain how to go about it. I figured, I could add hidden form fields for those properties that need to survive the post but it's a bit of a hack. I hope there are better answers. And how does original request parameter come into focus within the post operation..?
It sounds like expected behavior. The HostSiteSectionHeaderEditScreenObject instance passed as an argument to the POST handler method is not the same instance as the one you put in the model in the GET handler method. By default it's a new instance that Spring creates. Spring will bind values to the object's fields based on what parameters are present in the request for the POST. So if a parameter isn't present in the POST (e.g. because you didn't put an input in the HTML form for it) that field will not be set by Spring, it will just be whatever the default initial value is for the field.
It sounds like maybe what you want is to have initializeScreenObject() applied to your screen object then have the request parameter values applied after that? There are a couple ways to go about that. One would be to have a controller method annotated with #ModelAttribute:
#ModelAttribute("screenObject")
public HostSiteSectionHeaderEditScreenObject initScreenObject(#RequestParam(value="hostSiteId", required=false) Integer hostSiteId, #RequestParam(value="hostSiteSectionId", required=false) Integer hostSiteSectionId, Locale locale) {
HostSiteSectionHeaderEditScreenObject screenObject=new HostSiteSectionHeaderEditScreenObject();
initializeScreenObject(hostSiteId, hostSiteSectionId, screenObject, locale, true);
}
http://static.springsource.org/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-modelattrib-methods
If you do that, your GET handler method could be simplified to just return a view name.

Passing a DateTime to controller via URL causing error in ASP .NET MVC 3 (culture)

My application is setted with pt-BR culture (Date is dd-mm-yyyy) in web.config:
<globalization enableClientBasedCulture="false" requestEncoding="utf-8" responseEncoding="utf-8" fileEncoding="iso-8859-15" responseHeaderEncoding="utf-8" resourceProviderFactoryType="string" enableBestFitResponseEncoding="true" culture="pt-BR" uiCulture="pt-BR" />
All DateTime created on my system is in right format, but I created a controller method like that:
public ActionResult Test(DateTime date)
{
}
Calling that method direct in the browser is passing null when the date is with portuguese-br format, like that:
mysite/Test/?date=19/01/2012 => date = null in my controller
mysite/Test/?date=01/01/2012 => date is fine, but in US format (mm-dd-yyyy)
How can I fix that, to accept my date format?
There's a gotcha with the default model binder that is not easy to know about but once you know it you no longer make the same mistake:
When you use a POST request, the default model binder uses your culture settings to parse the dates.
When you use a GET request, the default model binder uses CultureInfo.InvariantCulture to parse the dates and ignores your current culture settings.
Since you are using a GET request and passing the date as a query string parameter, you should format it using the invariant culture format when sending it in the url. The correct way to format your date as a query string parameter is yyyy-MM-dd.
You may take a look at the following blog post which gets into more details.
As someone who does a lot of work with US companies, I've had a lot of experience with date issues.
My best advice is to choose an unambiguous format when transmitting.
dd-MMM-yyyy
and
yyyy-MM-dd
Are safe bets, and will be successfully parsed by DateTime.Parse(obj).
If changing the date format is not an option, you should look at DateTime.ParseExact, which allows you to specify the exact format string you are after.
One approach would be to accept the date as a string and then manipulate it in the controller to the correct locale/culture.
Got the same problem using an #Html.Action(..) in a view. For this situation it can be solved by putting the DateTime in a model:
public class MyModel
{
public DateTime Value {get;set;}
}
and in the view:
#Html.Action("MyAction", new { myModel })
Note the new { } around the instance of MyModel, this way the DateTime is not converted to a string. This solution only works for Html.Action() and not for Html.ActionLink() or Url.Action() since MVC is doing a myModel.ToString() in the URL.

MVC 3 - Html.EditorFor seems to cache old values after $.ajax call

This is a follow on from the following question:
MVC 3 + $.ajax - response seems to be caching output from partial view
There is a detailed description of the problem over there. However, I have now managed to narrow down the problem, that seems to be with the Html.EditorFor helpers, hence the new question.
The issue:
I post data to the server using $.ajax, then return the html of the partial view that holds the input controls. The problem is that, despite passing a newly created object to the Partial Views model, the various #Html.EditorFor and #Html.DropDownListFor helpers return the OLD DATA!.
I can prove that the model has correctly passed in a new object to the helpers, by printing the value out beside the Html helper. Ie:
#Html.EditorFor(model => model.Transaction.TransactionDate)
#Model.Transaction.TransactionDate.ToString()
As the following image shows, the #Html.EditorFor is returning the wrong data:
[Note that the value beside the Comentario text box is a date time, because I was testing replacing the default values with a value that would change with each post, ie, a DateTime.]
If I replace the #Html.EditorFor for TransactionDate with a plain old #Html.TextBox():
#Html.TextBox("Transaction_TransactionDate", Model.Transaction.TransactionDate)
Then it renders the correct TransactionDate value for a new Transaction object, ie, DateTime.MinValue (01/01/0001...).
Therefore...
The problem is with the #Html.EditorFor helpers. The problem also happens with TextBoxFor and DropDownListFor.
The problem being that these helpers seem to cache the old value.
What am I doing wrong??!
EDIT:
I have just tried debugging in the custom Editor template for dates, and in there, ViewData.TemplateInfo.FormattedModelValue shows the correct value, ie, "01/01/0001". However, once it gets to Fiddler, the response is showing the old date, eg, "01/09/2011" in the image above.
As a result, I just think that there is some caching going on here, but I have none set up, so nothing makes any sense.
There is no caching involved here. It's just how HTML helper work. They first look at the ModelState when binding their values and then in the model. So if you intend to modify any of the POSTed values inside your controller action make sure you remove them from the model state first:
[HttpPost]
public virtual ActionResult AjaxCreate(Transaction transaction)
{
if (ModelState.IsValid)
{
service.InsertOrUpdate(transaction);
service.Save();
}
service.ChosenCostCentreId = transaction.IdCostCentre;
TransactionViewModel viewModel = new TransactionViewModel();
ModelState.Remove("Transaction");
viewModel.Transaction = new Transaction();
ModelState.Remove("CostCentre");
viewModel.CostCentre = service.ChosenCostCentre;
...
return PartialView("_Create", viewModel);
}
Even if you do not specify caching, it sometimes can occur. For my controllers which handle AJAX and JSON requests, I decorate them as follows:
[OutputCache(Location = OutputCacheLocation.None, NoStore = true)]
This specifically declares no caching should occur.
UPDATE
Based on an answer Darin Dimitrov gave here, try adding the following line to your controller action:
ModelState.Clear();
i have never seen this but basically if you are using ajax to request this data, you need to set nochache: i am assuming you using jQuery.ajax here so will show the code:
$.ajax({
url: "somecontroller/someAction,
cache: false, // this is key to make sure JQUERY does not cache your request
success: function( data ) {
alert( data );
}
});
just a stab in the dark, i assume you have probably already covered this already. have you tried to create a new model first and then populate that new instance of the model with your data, and then send this to your view!
Finally not sure what DB server your using but have you check to see that DB results are not cached and that you are not just requesting SQL results from the DB cache... i dont use MsSQL but i hear that it has outputCaching until something is change on the DB server itself?? anyway just a few thoughts
This was unexpected behavior for me, and although I understand the reason why it's necessary to give ModelState precedence, I needed a way to remove that entry so that the value from Model is used instead.
Here are a couple methods I came up with to assist with this. The RemoveStateFor method will take a ModelStateDictionary, a Model, and an expression for the desired property, and remove it.
HiddenForModel can be used in your View to create a hidden input field using only the value from the Model, by first removing its ModelState entry. (This could easily be expanded for the other helper extension methods).
/// <summary>
/// Returns a hidden input field for the specified property. The corresponding value will first be removed from
/// the ModelState to ensure that the current Model value is shown.
/// </summary>
public static MvcHtmlString HiddenForModel<TModel, TProperty>(this HtmlHelper<TModel> helper,
Expression<Func<TModel, TProperty>> expression)
{
RemoveStateFor(helper.ViewData.ModelState, helper.ViewData.Model, expression);
return helper.HiddenFor(expression);
}
/// <summary>
/// Removes the ModelState entry corresponding to the specified property on the model. Call this when changing
/// Model values on the server after a postback, to prevent ModelState entries from taking precedence.
/// </summary>
public static void RemoveStateFor<TModel, TProperty>(this ModelStateDictionary modelState, TModel model,
Expression<Func<TModel, TProperty>> expression)
{
var key = ExpressionHelper.GetExpressionText(expression);
modelState.Remove(key);
}
Call from a controller like this:
ModelState.RemoveStateFor(model, m => m.MySubProperty.MySubValue);
or from a view like this:
#Html.HiddenForModel(m => m.MySubProperty.MySubValue)
It uses System.Web.Mvc.ExpressionHelper to get the name of the ModelState property. This is especially useful when you have "Nested" models since the key name isn't obvious.
Make sure you're not doing this:
#Html.EditorFor(model => model.Transaction.TransactionDate.Date)
I did this, and the model never got the value back. It worked perfectly once I remove the .Date.

.NET MVC3 Remove Currency Symbol and Commas

In my model I have the following property:
[DataType(DataType.Currency)]
public decimal? Budget { get; set; }
When the user enters in $1,200.34, I need that value to be valid and strip out the currency symbol and comma.
In my controller I'm doing:
if (race.Budget != null)
{
race.Budget.ToString().Replace("$", "").Replace(",", "");
}
The problem is that client validation doesn't pass the value for budget into the controller. I get a value of null. How can I override the client validation so that I can strip out the currency symbol and comma?
Thank you in advance for the help.
UPDATE
So here's the strange thing. Let's say I want to bypass client validation all together. I added #{ Html.EnableClientValidation(false); } to my view and it's still sending a null value for Budget when I submit to the controller.
This isn't a client side validation problem. Your model has a field of type decimal? The model binder will try to bind a value of $123,456.78 into that and fail, so the value will be null. Here's one way to get around this:
Change your model to have a string property that masks your decimal:
public decimal? Budget { get; set; }
public string BudgetText {
get {
return Budget.HasValue ? Budget.ToString("$") : string.Empty;
}
set {
// parse "value" and try to put it into Budget
}
}
Then, just bind to BudgetText from your View. Validate it as a string with a regular expression that accepts only money input. It'll probably be the same regex you can use for your BudgetText's set method
So you can probably hook in some JQuery to pre-process the form field to strip the characters off you don't want (prior to form submission to the server). This is probably the quickest, dirtiest approach.
For something reusable, have a look into custom client validation adapters. The links aren't spot on, but should get you in the right direction. For Brad's screencast, I believe the relevant parts are fairly early on.
Check out the support for jQuery localization
cliente validation using jQuery validate for currency fields
also there is a plugin for currency validation as well
http://code.google.com/p/jquery-formatcurrency/
check out this recent post as well for a $ in binding
.NET MVC 3 Custom Decimal? Model Binder

Teach ASP.NET MVC to treat key-less query values as boolean flags

If a query value in URL doesn't have a key, e.g.: http://site.com?update
and an action has bool argument named update.
I want that update argument to received value true if 'update' value is present in the URL, and false otherwise.
Example:
Action: public ActionResult MyAction(string name, bool update) {...}
URL: http://site.com/path?name=Bob&update
Expected action call: controller.MyAction("Bob", true);
if URL is http://site.com/path?name=Bob (notice no update)
then expected call is controller.MyAction("Bob", false);
It is not a big deal, I do know I can just get Request.Query and find values with key=null, but I want to have it done through the framework.
Where do I begin?
I'm using ASP.NET MVC 3
Doing this through the framework would probably require you to implement your own value provider with a corresponding value provider factory and add the factory to ValueProviderFactories. You would probably implement it similar to the existing QueryStringValueProviderFactory and QueryStringValueProvider but then add your own implementation of GetValue that includes the additional logic you wanted to return true/false based on if there is a value provided for the query string key. Here is a link on adding a value provider, and check out the QueryStringValueProvider in the framework.
http://mgolchin.net/posts/19/dive-deep-into-mvc-ivalueprovider

Resources