2009-04-27 3 views
18

Sto lavorando con l'applicazione NerdDinner cercando di insegnarmi ASP.NET MVC. Tuttavia, mi sono imbattuto in un problema con la globalizzazione, in cui il mio server presenta numeri in virgola mobile con una virgola come separatore decimale, ma la mappa Virtual Earth li richiede con punti, il che causa alcuni problemi.Come impostare i separatori decimali nei controller ASP.NET MVC?

Ho già risolto the issue with the mapping JavaScript in my views, ma se io ora cerco di inviare una voce cena curata con punti come separatori decimali il controller non riesce (gettando InvalidOperationException) quando si aggiorna il modello (nel UpdateModel() Metod). Mi sento come se dovessi impostare la cultura corretta da qualche parte nel controller, l'ho provato in OnActionExecuting() ma questo non ha aiutato.

risposta

54

Ho appena rivisitato il problema in un progetto reale e finalmente ho trovato una soluzione funzionante. soluzione adeguata è quella di avere un modello personalizzato legante per il tipo di decimal (e decimal? se si sta utilizzando loro):

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; 

     // 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) == -1 
      && attemptedValue.IndexOf(alternateSeperator) != -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; 
    } 
} 

Poi nel Global.asax.cs in Application_Start():

ModelBinders.Binders.Add(typeof(decimal), new DecimalModelBinder()); 
ModelBinders.Binders.Add(typeof(decimal?), new DecimalModelBinder()); 

Nota che il codice non è mio, l'ho trovato sul blog di Kristof Neirynck here. Ho appena modificato alcune righe e sto aggiungendo il raccoglitore per un tipo di dati specifico, non sostituendo il raccoglitore predefinito.

+2

Ho appena aggiornato la mia risposta aggiungendo un codice di controllo aggiuntivo per far sì che gestisca correttamente le proprietà nullable. Ho scoperto che se ha trovato una stringa vuota come valore tentato, genererebbe un'eccezione anche se la proprietà vincolata è nullable. Ora dovrebbe funzionare bene. –

+1

C'è una correzione complementare lato client che dovrebbe essere applicata a questa. Lo troverai qui: http://stackoverflow.com/a/8102159/41420 –

+0

Non funziona con la stringa come input – Furtiro

4

Set questo nel tuo web.config

<globalization uiCulture="en" culture="en-US" /> 

È sembrano essere utilizzando un server che è configurato con una lingua che utilizza una virgola di invece di cifre decimali. Puoi adattare la cultura a una che utilizza la virgola in un modo in cui è progettata la tua applicazione, come en-US.

+1

Ho già questo, per qualche motivo non funziona. –

+0

e le intestazioni nella tua pagina di aspx? Qualcuno di loro ha qualcosa per la cultura? –

+0

@Nick: No, non ci sono cose personalizzate lì dentro. –

0

È possibile analizzare il testo utilizzando la cultura invariante: mi dispiace, non ho il codice NerdDinner fornito da me, ma se passi dei decimali separati da punti piuttosto che l'analisi dovrebbe essere OK se lo dici a usa la cultura invariante. Per esempio.

float i = float.Parse("0.1", CultureInfo.InvariantCulture); 

Modifica. Sospetto che si tratti di un errore nel codice NerdDinner a proposito, sulla stessa falsariga del problema precedente.

+0

No, non sto toccando nessuna proprietà. Si suppone che avvenga "magicamente" in UpdateModel(), ma genera un'eccezione non così utile senza dettagli. Ci sono due problemi: quando si presentano i dati dal modello ho bisogno di punti, perché la mappatura del JS si romperà, tuttavia quando si inseriscono i dati nel modello ho bisogno di virgole questa volta, perché altrimenti il ​​controller si rompe. È tutto un po 'strano e non sono sicuro di come risolverlo, Google non ha aiutato, nemmeno SO. :( –

+0

Sì, non penso che tu possa risolverlo senza modificare il codice.Ho il codice ND e darò un'occhiata a volte e cerco di verificare questo e ti mando una correzione se posso, ma sono temo che non sarà presto necessario lavorare :-) –

+0

OK, penso che sia necessario annullare la modifica apportata nella tua altra domanda, ho inviato un suggerimento lì su come risolvere il problema e speriamo che la mia correzione sarà non causa l'effetto collaterale che stai vedendo qui. –

0

Ho un approccio diverso a questo, potrebbe piacerti. Quello che non mi piace della risposta accettata è che non controlla altri personaggi. So che ci sarà un caso in cui il simbolo della valuta sarà nella casella perché il mio utente non sa meglio. Quindi sì, posso controllare in javascript per rimuoverlo, ma cosa succede se per qualche motivo javascript non è attivo? Quindi potrebbero passare altri personaggi. O se qualcuno cerca di spammare passando personaggi sconosciuti attraverso ... chi lo sa! Così ho deciso di usare una regex. È un po 'più lento, una piccola frazione più lenta - nel mio caso si trattava di 1.000.000 iterazioni della regex in meno di 3 secondi, mentre circa 1 secondo di una stringa sostituiva su un coma e un punto. Ma visto che non so quali potrebbero essere i personaggi, sono felice per questo minimo risultato.

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; 
    } 
} 

Fondamentalmente, rimuovere tutti caratteri che non sono cifre, per una stringa che non è vuota. Converti in decimale. Dividi per 100. Restituisci il risultato.

Lavori per me.

+1

Benché in alcuni casi questo decimale funzioni, se il numero decimale non ha il valore di 0,00 (ovvero la convalida di javascript o qualcos'altro che lo spoglia), la tecnica di divisione per 100 non funziona. – Ben

+0

Sì. L'ho notato in un paio di circostanze, e poi ho fatto in modo che il mio codice aggiungesse sempre le cifre decimali tramite JavaScript prima di postare. Ma nella vita reale non puoi sempre aspettarti che ciò accada. –