2013-01-23 21 views
21

Ho un'azione controller che deve ricevere un numero intero e un oggetto, contenente varie proprietà, una delle quali è un elenco generico di oggetti. Quando pubblico JSON sull'azione con una lista popolata, tutto viene mappato correttamente e ottengo una lista contenente l'oggetto che ho postato. Se la matrice è vuota, tuttavia, l'azione MVC associa la proprietà a un intead nullo di una lista vuota. Voglio che l'array vuoto sia mappato su un array vuoto e non su un null, in quanto l'array vuoto in questo caso significa che non c'è nulla nella collezione, e un null significa che il database dovrebbe essere controllato per vedere se c'è qualcosa in precedenza salvato nella raccolta, ma non riesco a capire cosa ho bisogno di cambiare per farlo mappare correttamente. Stiamo utilizzando Json.Net per eseguire la serializzazione degli oggetti per la restituzione di oggetti, ma non penso che venga utilizzato per la deserializzazione degli oggetti sul binding dei modelli.JSON empty array deserializing null in MVC

oggetti vengono passati:

public class ObjectInList 
{ 
    public decimal Value1 { get; set; } 
    public decimal Value2 { get; set; } 
} 

public class Criteria 
{ 
    public decimal? ANullableNumber { get; set; } 
    public IList<ObjectInList> ObjectsList { get; set; } 
} 

JSON richiesta: "{\" id \ ": 137, \" criteri \ ": {\" ObjectsList \ ": []}}"

azione controller:

public ActionResult ProcessCriteria(int id, Criteria criteria) 
{ 
    return Json(_service.ProcessCriteria(id, criteria)); 
} 

E 'in azione di controllo che sto ottenendo un null al posto di una lista vuota nei criteri oggetto. Succede se invio valori null per le altre proprietà o no. Non sei sicuro se sia l'oggetto essere un IList e non un IEnumerable? (Il metodo Json che avvolge la chiamata di servizio è il nostro wrapper per restituire un risultato json utilizzando Json.Net per serializzare la risposta: il null è nell'oggetto criteri ricevuto, non nel ritorno.)

Sto supponendo che sia qualcosa piuttosto semplice che mi manca, ma non riesco a capire cosa, ogni aiuto è molto apprezzato.

+0

È più facile per noi se inserisci del codice. Una riga di codice è migliore di 100 parole: D –

+0

Concordato, tuttavia in questo caso, non sono sicuro di quanto possa essere d'aiuto, a meno di postare l'intero controller/controller di base e i post json associati. Proverò a postare qualcosa che abbia un senso, ma fino ad allora, il json sta mostrando una matrice vuota, e prima che sia pubblicato e quando è legato al modello, la lista è una lista vuota piuttosto che vuota. Parti della gerarchia del controller sono state scritte da altri sviluppatori, quindi non posso dirlo con certezza, ma non riesco a trovare un'implementazione sovrascritta della deserializzazione di JSON, quindi penso che sia la gestione predefinita di JSON per il binding del modello. –

+0

Controlla questa domanda: http://stackoverflow.com/q/14203150/29555 e la seconda risposta – marto

risposta

0

Ecco quello che ho postato come commento:

public class Criteria 
{ 
    public decimal? ANullableNumber { get; set; } 
    private IList<ObjectInList> _objectsList = new List<ObjectInList>(); 
    public IList<ObjectInList> ObjectsList 
    { 
     get { return _objectsList; } 
     set { 
      if(value != null) 
       _objectsList = value; 
     } 
    } 
} 
+1

Come ho detto nel mio commento sopra, voglio essere in grado di avere uno stato nullo anche per questa lista, quindi se provo a inviare un null in questo caso, lo mostrerà comunque come una lista vuota - che è non il comportamento desiderato. –

10

ok, stavo affrontando questo problema quasi 5 ore cercando di trovare la soluzione poi mi sono trovato a guardare nel codice sorgente MVC. e ho trovato che questo è un problema con il codice sorgente in Mvc System.Web.Mvc.ValueProviderResult alla Line 173:

 else if (valueAsArray != null) 
     { 
      // case 3: destination type is single element but source is array, so      extract first element + convert 
      if (valueAsArray.Length > 0) 
      { 
       value = valueAsArray.GetValue(0); 
       return ConvertSimpleType(culture, value, destinationType); 
      } 
      else 
      { 
       // case 3(a): source is empty array, so can't perform conversion 
       return null; 
      } 
     } 

come si può vedere se la sorgente è array vuoto verrà restituito nulla.

quindi devo trovare un modo intorno ad esso, e poi mi ricordo come nei bei vecchi tempi ci stava facendo deserializzazione: questo è come si ottiene ciò che si vuole:

public ActionResult ProcessCriteria(int id, Criteria criteria) 
    { 
     var ser = new System.Web.Script.Serialization.JavaScriptSerializer(); 
     StreamReader reader = new StreamReader(System.Web.HttpContext.Current.Request.InputStream); 
     reader.BaseStream.Position = 0; 
     criteria = ser.Deserialize<Criteria>(reader.ReadToEnd()); 

     return Json(_service.ProcessCriteria(id, criteria)); 
    } 
+1

Questo è un bug ovvio, penso. È possibile pubblicare un problema o effettuare il commit in questo http://aspnetwebstack.codeplex.com/ –

+1

Penso che il problema reale sia in DefaultModelBinder https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src /System.Web.Mvc/DefaultModelBinder.cs line 711 dove restituisce null se il costruito 'objectList' non contiene nulla. Controlla questo: https://lostechies.com/jimmybogard/2013/11/07/null-collectionsarrays-from-mvc-model-binding/ – bigbearzhu

0

Ho un risposta per te che funzionerà a livello di struttura. Nel mio progetto, stavo lavorando con dati un po 'più grandi dei valori di default che avrebbero supportato. Così, ho creato la mia proprietà ValueProviderFactory. Si scopre che se un array non ha elementi in esso, il provider ha saltato del tutto quella voce. Invece, dobbiamo solo dire che non ci sono elementi nell'array. Ecco il codice che ti servirà.

In primo luogo, il globale.ASax Application_Start:

public void Application_Start() 
{ 
    ValueProviderFactories.Factories.Remove(ValueProviderFactories.Factories.OfType<System.Web.Mvc.JsonValueProviderFactory>().FirstOrDefault()); 
    ValueProviderFactories.Factories.Add(new LargeValueProviderFactory()); 

In secondo luogo, qui è l'altra classe è necessario:

#region <<Usings>> 

using System; 
using System.Collections.Generic; 
using System.Collections; 
using System.Web.Mvc; 
using System.IO; 
using System.Web.Script.Serialization; 
using System.Globalization; 

#endregion 

/// <summary> 
/// This class is to ensure we can receive large JSON data from the client because the default is a bit too small. 
/// </summary> 
/// <remarks>This class is from the web.</remarks> 
public sealed class LargeValueProviderFactory : System.Web.Mvc.ValueProviderFactory 
{ 

    #region <<Constructors>> 

    /// <summary> 
    /// Default constructor. 
    /// </summary> 
    public LargeValueProviderFactory() 
     : base() 
    { 
     // Nothing to do 
    } 

    #endregion 

    #region <<GetValueProvider>> 

    public override System.Web.Mvc.IValueProvider GetValueProvider(ControllerContext controllerContext) 
    { 
     if (controllerContext == null) 
     { 
      throw new ArgumentNullException("controllerContext"); 
     } 

     object jsonData = GetDeserializedObject(controllerContext); 
     if (jsonData == null) 
     { 
      return null; 
     } 

     Dictionary<string, object> backingStore = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); 
     AddToBackingStore(backingStore, String.Empty, jsonData); 
     return new DictionaryValueProvider<object>(backingStore, CultureInfo.CurrentCulture); 
    } 

    #endregion 

    #region << Helper Methods >> 

    private static void AddToBackingStore(Dictionary<string, object> backingStore, string prefix, object value) 
    { 
     IDictionary<string, object> d = value as IDictionary<string, object>; 
     if (d != null) 
     { 
      foreach (KeyValuePair<string, object> entry in d) 
      { 
       AddToBackingStore(backingStore, MakePropertyKey(prefix, entry.Key), entry.Value); 
      } 
      return; 
     } 

     IList l = value as IList; 
     if (l != null) 
     { 
      for (int i = 0; i < l.Count; i++) 
      { 
       AddToBackingStore(backingStore, MakeArrayKey(prefix, i), l[i]); 
      } 
      if (l.Count == 0) 
       backingStore[prefix] = value; 
      return; 
     } 

     // primitive 
     backingStore[prefix] = value; 
    } 

    private static object GetDeserializedObject(ControllerContext controllerContext) 
    { 

     if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase)) 
     { 
      // not JSON request 
      return null; 
     } 

     StreamReader reader = new StreamReader(controllerContext.HttpContext.Request.InputStream); 
     string bodyText = reader.ReadToEnd(); 
     if (String.IsNullOrEmpty(bodyText)) 
     { 
      // no JSON data 
      return null; 
     } 

     JavaScriptSerializer serializer = new JavaScriptSerializer(); 
     serializer.MaxJsonLength = Int32.MaxValue; 
     object jsonData = serializer.DeserializeObject(bodyText); 
     return jsonData; 
    } 


    private static string MakeArrayKey(string prefix, int index) 
    { 
     return prefix + "[" + index.ToString(CultureInfo.InvariantCulture) + "]"; 
    } 

    private static string MakePropertyKey(string prefix, string propertyName) 
    { 
     return (String.IsNullOrEmpty(prefix)) ? propertyName : prefix + "." + propertyName; 
    } 

    #endregion 

} 
-2

Questo perché il mai definire le proprietà nullable valore in classe 'criteri'; se mai definire, sarà nullo.

ad esempio:

public class Criteria { 
    public decimal? ANullableNumber { get; set; } 
    public IList<ObjectInList> ObjectsList { get; set; } 
    } 
    public class Criteria1 { 
    private IList<ObjectInList> _ls; 
    private decimal? _num; 
    public decimal? ANullableNumber { 
     get { 
     if (_num == null) return 0; 
     return _num; 
     } 
     set { 
     _num = value; 
     } 
    } 
    public IList<ObjectInList> ObjectsList { 
     get { 
     if (_ls == null) _ls = new List<ObjectInList>(); 
     return _ls; 
     } 
     set { 
     _ls = value; 
     } 
    } 
    } 
    public class HomeController : Controller { 
    public ActionResult Index() { 
     var dd = new Criteria(); 
     return Json(dd); //output: {"ANullableNumber":null,"ObjectsList":null} 
    } 
    public ActionResult Index1() { 
     var dd = new Criteria1(); 
     return Json(dd); //output: {"ANullableNumber":0,"ObjectsList":[]} 
    } 
    }