2011-12-11 9 views
35

Ho una funzione che restituisce un tipo anonimo che voglio testare nel mio controller MVC.Lancio del tipo anonimo a dinamico

public JsonResult Foo() 
{ 
    var data = new 
        { 
         details = "something", 
         more = "More" 
        }; 
    return Json(data); 
} 

voglio verificare i dati che ricevo dalla funzione Foo, cosa che sto facendo ora sta ottenendo il tipo di dati e ottenere suoi valori proprietà con la riflessione.

[Test] 
public void TestOne() 
{ 
    var data = _controller.Foo().Data; 
    var details = data.GetType().GetProperty("details").GetValue(data, null); 
    var more = data.GetType().GetProperty("more").GetValue(data, null); 

    Assert.AreEquals("something", details); 
    Assert.AreEquals("More", more); 
} 

C'è un modo semplice simile per controllare le proprietà anonime?

[Test] 
public void TestTwo() 
{ 
    var data = (dynamic) _controller.Foo().Data; 
    var details = data.details; // RunTimeBinderException object does not contain definition for details 
    var more = data.more; 

    Assert.AreEquals("something", details); 
    Assert.AreEquals("More", more); 
} 
+7

Dal momento che questo è per unit testing, è possibile utilizzare 'InternalsVisibleTo'. Vedi [Tipi anonimi sono interni, C# 4.0 Attenzione dinamica!] (Http://www.heartysoft.com/anonymous-types-c-sharp-4-dynamic) Grazie a @MarcGravell per aver sottolineato che gli oggetti anonimi sono "interni" ! – TrueWill

+0

+1 per InternalsVisibleTo suggerimento. Funziona come un fascino. –

risposta

34

oggetti anonimi sono internal, il che significa che i loro membri sono molto limitati al di fuori del gruppo che li dichiara. dynamic rispetta l'accessibilità, quindi fa finta di non essere in grado di vedere quei membri. Se il sito di chiamata fosse nello stesso assembly, mi aspetto che funzioni.

Il codice di riflessione rispetta l'accessibilità membro, ma ignora l'accessibilità del tipo - quindi funziona.

In breve: no.

+2

@ gdoron perché lo farebbe? È ancora un oggetto, dopo tutto. Semplicemente non espone molto altro. ToString(), Equals(), GetHashCode(), etc funzioneranno ancora tutti tramite la dinamica. Semplicemente non aggiunge nient'altro visibile. –

6

tipo anonimo è un tipo statico regolare NET, è solo che non dare un nome (un compilatore, però, lo fa). Ecco perché la sua conversione in dynamic non funzionerà. Tuttavia, se si ha il controllo su Foo(), è possibile costruire e restituire un oggetto dynamic anziché anonimo e quindi il codice funzionerà. Questo dovrebbe fare il trucco:

dynamic JsonResult Foo() { 
    dynamic data = new ExpandoObject(); 
    data.details = "something"; 
    data.mode = "More"; 
    return Json(data); 
} 
+0

. Dati è "oggetto". C'è poca differenza tra "oggetto" e "dinamico", tranne che per il consumatore (che è dove si diverte). Non credo che cambiare tra "oggetto" e "dinamico" (e poi tornare a dinamico) farà molto qui. –

+0

@MarcGravell Ho aggiunto il codice per chiarire cosa intendo per * restituendo dynamic * (in realtà intendevo "costruire un oggetto dinamico e restituirlo", non semplicemente "modifica il tipo restituito in dinamico"). Grazie per averlo preso - la modifica iniziale in effetti non era chiara. – dasblinkenlight

+1

La cosa principale che rende questo lavoro è: ExpandoObject è pubblico non interno (e ovviamente implementa l'interfaccia IDynamicBlahBlahBlah come richiesta pubblica). Tuttavia, suggerisce la domanda importante: il livello JSON come ExpandoObject? (potrebbe * * dovuto all'utilizzo di IDictionary). Significa anche che non stiamo più utilizzando un tipo anon (domanda) –

22

Questo blog ha avuto una risposta operativa: http://blog.jorgef.net/2011/06/converting-any-object-to-dynamic.html - Grazie @ Jorge-Fioranelli.

public static class DynamicExtensions { 
    public static dynamic ToDynamic(this object value) { 
     IDictionary<string, object> expando = new ExpandoObject(); 

     foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(value.GetType())) 
      expando.Add(property.Name, property.GetValue(value)); 

     return expando as ExpandoObject; 
    } 
} 
4

Come suggerito da @TrueWill e @ Marc Gravell, che ha anche indicato this blog post

Dal momento che questo è per unit testing, è possibile utilizzare InternalsVisibleTo. Vedi Tipi anonimi sono interni, C# 4.0 Attenzione dinamica! Grazie a @MarcGravell per aver sottolineato che gli oggetti anonimi sono interni!

Bottom line: impostare un mapping [assembly: InternalsVisibleTo("foo")] se si desidera condividere un oggetto anonimo da un assembly a un altro. Nel caso OP, si tratta di impostarlo nel progetto del controller MVC, facendo riferimento al progetto di prova . Nel mio caso specifico, il contrario (dato che sto passando un oggetto anonimo dal mio progetto di test al progetto "codice di produzione").

Il modo più semplice in cui "l'altro progetto" per poterlo utilizzare è sicuramente quello di convertirlo in dynamic e quindi utilizzare semplicemente le proprietà come normale. Funziona, senza problemi di sorta.

Quindi, linea di fondo: Sento che la risposta di Marc Gravell è leggermente errata; questo può essere fatto chiaramente
(iff i progetti in questione sono modificabili dall'utente, quindi è possibile impostare la mappatura InternalsVisibleTo di conseguenza, e ciò non rappresenta un problema per nessun altro motivo).

1

È possibile utilizzare NewtonSoft o Asp.biblioteche net MVC:

var data = Json.Decode(Json.Encode(_controller.Foo().Data));

var data=JsonConvert.DeserializeObject<Dictionary<string,object>>(JsonConvert.SerializeObject((_controller.Foo().Data))