How to set decimal separators in ASP.NET MVC controllers? - asp.net-mvc

I'm working with the NerdDinner application trying to teach myself ASP.NET MVC. However, I have stumbled upon a problem with globalization, where my server presents floating point numbers with a comma as the decimal separator, but Virtual Earth map requires them with dots, which causes some problems.
I have already solved the issue with the mapping JavaScript in my views, but if I now try to post an edited dinner entry with dots as decimal separators the controller fails (throwing InvalidOperationException) when updating the model (in the UpdateModel() metod). I feel like I must set the proper culture somewhere in the controller as well, I tried it in OnActionExecuting() but that didn't help.

I have just revisited the issue in a real project and finally found a working solution. Proper solution is to have a custom model binder for the type decimal (and decimal? if you're using them):
using System.Globalization;
using System.Web.Mvc;
public class DecimalModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
object result = null;
// Don't do this here!
// It might do bindingContext.ModelState.AddModelError
// and there is no RemoveModelError!
//
// result = base.BindModel(controllerContext, bindingContext);
string modelName = bindingContext.ModelName;
string attemptedValue = bindingContext.ValueProvider.GetValue(modelName)?.AttemptedValue;
// in decimal? binding attemptedValue can be Null
if (attemptedValue != null)
{
// Depending on CultureInfo, the NumberDecimalSeparator can be "," or "."
// Both "." and "," should be accepted, but aren't.
string wantedSeperator = NumberFormatInfo.CurrentInfo.NumberDecimalSeparator;
string alternateSeperator = (wantedSeperator == "," ? "." : ",");
if (attemptedValue.IndexOf(wantedSeperator, StringComparison.Ordinal) == -1
&& attemptedValue.IndexOf(alternateSeperator, StringComparison.Ordinal) != -1)
{
attemptedValue = attemptedValue.Replace(alternateSeperator, wantedSeperator);
}
try
{
if (bindingContext.ModelMetadata.IsNullableValueType && string.IsNullOrWhiteSpace(attemptedValue))
{
return null;
}
result = decimal.Parse(attemptedValue, NumberStyles.Any);
}
catch (FormatException e)
{
bindingContext.ModelState.AddModelError(modelName, e);
}
}
return result;
}
}
Then in Global.asax.cs in Application_Start():
ModelBinders.Binders.Add(typeof(decimal), new DecimalModelBinder());
ModelBinders.Binders.Add(typeof(decimal?), new DecimalModelBinder());
Note that code is not mine, I actually found it at Kristof Neirynck's blog here. I just edited a few lines and am adding the binder for a specific data type, not replacing the default binder.

Set this in your web.config
<system.web>
<globalization uiCulture="en" culture="en-US" />
You appear to be using a server that is setup with a language that uses comma's instead of decimal places. You can adjust the culture to one that uses the comma's in a way that your application is designed, such as en-US.

Can you parse the text using the invariant culture - sorry, I don't have the NerdDinner code in fornt of me, but if you are passing in dot-separated decimals than the parsing should be OK if you tell it to use the invariant culture. E.g.
float i = float.Parse("0.1", CultureInfo.InvariantCulture);
Edit. I suspect that this is a bug in the NerdDinner code by the way, along the same lines as your previous problem.

I have a different take on this, you might like it. What I don't like about the accepted answer is it doesn't check for other characters. I know there will be a case where the currency symbol will be in the box because my user doesn't know better. So yeah I can check in javascript to remove it, but what if for some reason javascript isn't on? Then extra characters might get through. Or if someone tries to spam you passing unknown characters through... who knows! So I decided to use a regex. It's a bit slower, tiny fraction slower - for my case it was 1,000,000 iterations of the regex took just under 3 seconds, while around 1 second to do a string replace on a coma and period. But seeing as I don't know what characters might come through, then I am happy for this slightest of performance hits.
public class DecimalModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
string modelName = bindingContext.ModelName;
string attemptedValue =
bindingContext.ValueProvider.GetValue(modelName).AttemptedValue;
if (bindingContext.ModelMetadata.IsNullableValueType
&& string.IsNullOrWhiteSpace(attemptedValue))
{
return null;
}
if (string.IsNullOrWhiteSpace(attemptedValue))
{
return decimal.Zero;
}
decimal value = decimal.Zero;
Regex digitsOnly = new Regex(#"[^\d]", RegexOptions.Compiled);
var numbersOnly = digitsOnly.Replace(attemptedValue, "");
if (!string.IsNullOrWhiteSpace(numbersOnly))
{
var numbers = Convert.ToDecimal(numbersOnly);
value = (numbers / 100m);
return value;
}
else
{
if (bindingContext.ModelMetadata.IsNullableValueType)
{
return null;
}
}
return value;
}
}
Basically, remove all characters that are not digits, for a string that isn't empty. Convert to decimal. Divide by 100. Return result.
Works for me.

Related

MVC 4 version of NerdDinner has binding issue when I create a dinner

If you goto
http://nerddinner.codeplex.com/SourceControl/changeset/view/b1a032d1532b
Get the MVC4 version. Run it. Host a dinner. Click Create I always get this error (screenshot)
It is one of the main reference apps I want to use to help me learn so if anyone know what I am missing please tell me
I don't know if the screenshot worked but the error is :
IndexOutOfRangeException in this piece of code on the LatLongStr
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult != null)
{
string[] latLongStr = valueProviderResult.AttemptedValue.Split(',');
string point = string.Format("POINT ({0} {1})", latLongStr[1], latLongStr[0]);
//4326 format puts LONGITUDE first then LATITUDE
DbGeography result = DbGeography.FromText(point, 4326);
return result;
}
return null;
}
That's probably because you're entering a value as the LatLongString without a , character
This command
string[] latLongStr = valueProviderResult.AttemptedValue.Split(',');
splits the given string if there is a , sign, to create the lat long.
Later, there is a reference to the two values
... latLongStr[1], latLongStr[0]);
However, if the value entered does not contain a , latLongStr only has 1 item, so latLongStr[1] is out of bounds.
You can prevent this by making sure a valid LatLong is entered.

asp.net mvc controller arguments a?b1,c2... how to parse variable number?

If a URL, in asp.net mvc, has args that are not catered for in the index/show method signature of a controller, is it possible to use something like a params string[] args in the signature to gather them up. Or what is a good way to do this when the arg is a list of separated values (ie not name/value pairs)?
We have users, ultimately, creating urls with a variable number of arguments & need to parse them.
This is the code we have at the moment, but we can't help thinking there's a better way without splitting the string ourselves:
var url = Request.RawUrl.Split('?');
if (url.Length > 1)
{
var queryString = url[1];
var queryStringArgs = queryString.Split('&');
var queryStringMembers = from arg in queryStringArgs
let c = arg.Split('=').Length == 1
where c
select arg;
ViewBag.QueryStringMembers = queryStringMembers.ToJson();
}
*Append: these args dont have name=value, it's just a list of values.
Request.QueryString doesn't seem to help us, as it treats these query string args differently because they are not name=value, they are just value. So it puts them in a Request.QueryString[null] key as comma separated
First things first what you have is a malformed URL. So you are totally on your own parsing it. You may also expect it to fail any time. Everything that is part of the querystring, i.e. following the first ?, must be url encoded, you should not have multiple ?.
This being said you could write a custom model binder:
public class CustomModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var tokens = controllerContext.HttpContext.Request.RawUrl.Split('?');
if (tokens.Length > 1)
{
return tokens.Skip(1).ToArray();
}
return null;
}
}
and then:
public ActionResult Index([ModelBinder(typeof(CustomModelBinder))]string[] args)
{
return View();
}
Request.QueryString.AllKeys will contain a string array of the argument names sent in on the QueryString. Is that what you're looking for?

asp.net mvc 1.0 Validations

Im working in asp.net mvc application which was done in mvc 1... so validations were done following the nerd dinner 1.0 tutorial
I just defined a rule like this
public bool Is_CellPhone(string val)
{
Regex celular = new Regex("^04[12][246][0-9]{7}$");
return celular.IsMatch(val);
}
and in my GetRuleValidations I do this
if (!Is_CellPhone(Celular))
yield return new RuleViolation("El celular no cumple el formato",
"Celular");
The problem is.. cell phone is not required so when the user doesnt submit that value the validation method runs anyway and returns an error because of the null string... what can I do to properly prevent this error?
Just return true if the string is null or empty:
public bool Is_CellPhone(string val)
{
if (string.IsNullOrEmpty(val)) { return true; }
Regex celular = new Regex("^04[12][246][0-9]{7}$");
return celular.IsMatch(val);
}
I suspect you can also cover this in the regular expression, but I suck at regex so I won't pretend to give advice there.

ASP.net mvc, generic/dynamic controllers and Type.GetType: how can I keep my URLs pretty?

Using information from some of the questions here on generic views, I have created an MVC app that reads .dlls from its own /bin directory and builds the UI on the fly. InputBuilder partial views helped a lot. I also made a ControllerFactory, after the advice from here and elsewhere.
My problem is, while everything is working OK and reflection is recognizing the types I'm passing around, GetType() requires the full assembly qualified name ('scuse the code, still prototyping):
public IController CreateController(RequestContext requestContext, string controllerName)
{
Type controllerType = null;
Type genericType;
//controllerName coming in as full assembly-qualified path
Type baseControllerType = typeof(CoreDataController<>);
genericType = Type.GetType(controllerName);
if (genericType != null)
{
controllerType = baseControllerType.MakeGenericType(genericType);
}
if (controllerType != null)
{
return Activator.CreateInstance(controllerType) as IController;
}
return controllerType;
}
This makes my urls look like this:
http://localhost:1075/CoreData.Plans,%20PlansLib,%20Version=1.0.0.0,%20Culture=neutral,%20PublicKeyToken=null/Create
Obviously sub-optimal.
What I'd like is http://localhost:1075/CoreData.Plans/Create
or even better:
http://localhost:1075/Plans/Create
Should I store a dictionary accessible to my controller on Application_Start() mapping short names to fully-qualified names? Is there a feature of Reflection I'm missing that would solve this problem?
I think your idea of a dictionary mapping pretty names to types would be good. You may want to try putting attributes on your classes, then at startup, you can use reflection to extract out the attributes for building the dictionary:
[UrlName("my-class-name")]
public class MyClassName
{
// ...
}
I had this problem with long and even inconsistant type names across different platforms that i was using and came up with a way to search for the type in the dlls loaded in the current appdomain.
public static Type GetTypeFromName(string TypeNameStr, Assembly[] Asms)
{
Type VarType = null;
string TypeStr = TypeNameStr.Split(',')[0];
foreach (Assembly Dll in Asms)
{
VarType = Dll.GetType(TypeNameStr);
if (VarType == null)
VarType = Dll.GetType(TypeStr);
if (VarType != null)
break;
}
return VarType;
}
All that you need to do is pass the function a list of assemblies that you can get from the current appdomain and it will try to find the Type from there you can create a dictionary using the name and the type to cache this so that you don't have to do this over and over again.

Field max length in Entity framework

I need to put a max length on my test field on my Views using ASP.NET MVC with the Entity Framework and I can't find how to get the max length of a varchar field.
Is there an easy way to get that, or any other property of a database field
Here is how i manage to do it (with an extension method on entities) :
public static int? GetMaxLength(this EntityObject entite, string nomPropriete)
{
int? result = null;
using (XEntities contexte = XEntities.GetCurrentContext())
{
var queryResult = from meta in contexte.MetadataWorkspace.GetItems(DataSpace.CSpace)
.Where(m => m.BuiltInTypeKind == BuiltInTypeKind.EntityType)
from p in (meta as EntityType).Properties
.Where(p => p.DeclaringType.Name == entite.GetType().Name
&& p.Name == nomPropriete
&& p.TypeUsage.EdmType.Name == "String")
select p.TypeUsage.Facets["MaxLength"].Value;
if (queryResult.Count() > 0)
{
result = Convert.ToInt32(queryResult.First());
}
}
return result;
}
Update
I realize that this answer doesn't directly apply to EF. At the time that I answered, there had been no answers for about 20 minutes and I thought knowing how I solved a similar problem with LINQToSQL might help. Given that the OP basically used the same technique albeit with EF properties instead, seems to indicate that I made the right choice. I'm leaving this answer here for context and for those who get here having the same problem but with LINQToSQL.
Original
I don't know about EF, but LINQToSQL entity properties are decorated with ColumnAttributes. You may be able to get the ColumnAttribute from the PropertyInfo for the property by looking at the CustomAttributesCollection. The value of this attribute would need to be parsed for length. I do that in my validator classes to make sure that I'm not going to get a SQL error by using a string that is too long for my column.
This is the method I use to extract the column length for string properties.
public static int MaximumPropertyLength( Type type, string propertyName )
{
int maximumLength = -1;
PropertyInfo info = type.GetProperty( propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance );
if (info != null)
{
var attribute = info.GetCustomAttributes( typeof( ColumnAttribute ), false )
.Cast<ColumnAttribute>()
.FirstOrDefault();
if (attribute != null)
{
maximumLength = ExtractLength( attribute.DbType );
}
}
return maximumLength;
}
private static int ExtractLength( string dbType )
{
int max = int.MaxValue;
if (dbType.Contains( "(" ))
{
string[] parts = dbType.Split( new char[] { '(', ')' }, StringSplitOptions.RemoveEmptyEntries );
if (parts.Length > 1)
{
int.TryParse( parts[1], out max );
}
}
return max;
}
For EntityFramework you would need to add your own custom attributes to the classes using a Code Generator or T4 Template.
Then what tvanfosson stated above would hold true. EF does not persist this information by default.
http://blogs.msdn.com/adonet/archive/2008/01/24/customizing-code-generation-in-the-ado-net-entity-designer.aspx
Explains more of what I am talking about with your code generator. It is pretty slick I have done exactly what you are mentioning before, problem is with proprietary code so I do not have an example for you.

Resources