In c # anonymen Typ in Schlüssel / Wert-Array konvertieren?

78

Ich habe den folgenden anonymen Typ:

new {data1 = "test1", data2 = "sam", data3 = "bob"}

Ich benötige eine Methode, die dies berücksichtigt und Schlüsselwertpaare in einem Array oder Wörterbuch ausgibt.

Mein Ziel ist es, dies als Post-Daten in einer HttpRequest zu verwenden, damit ich sie schließlich in die folgende Zeichenfolge verketten kann:

"data1=test1&data2=sam&data3=bob"
Chris Kooken
quelle

Antworten:

119

Dies erfordert nur ein kleines bisschen Nachdenken.

var a = new { data1 = "test1", data2 = "sam", data3 = "bob" };
var type = a.GetType();
var props = type.GetProperties();
var pairs = props.Select(x => x.Name + "=" + x.GetValue(a, null)).ToArray();
var result = string.Join("&", pairs);
kbrimington
quelle
7
var dict = props.ToDictionary (x => x.Name, x => x.GetValue (a_source, null))
Jordanien
30
Wir sind so weit gekommen ... wir können es zu einem var dict = a.GetType().GetProperties().ToDictionary(x => x.Name, x => x.GetValue(a, null));
Einzeiler
@ kape123, in der Tat. Tatsächlich erfordern die neuesten .NET-Versionen nicht mehr den Aufruf von ToArray(), was sehr schön ist. Auf jeden Fall passt die Antwort in ihrer jetzigen Form gut in SO ohne Zeilenumbruch, also lasse ich sie so wie sie ist.
Kbrimington
Die Antwort von @ kape123 funktioniert wirklich gut und ich kann (bisher) keine Probleme damit finden. kbrimington, können Sie (oder sollte ich) dies der Antwort hinzufügen?
Xan-Kun Clark-Davis
@ Xan-KunClark-Davis, die Antwort von kape ist in Ordnung; Es ist jedoch wirklich die gleiche Antwort. Deshalb habe ich seinen Kommentar positiv bewertet, anstatt ihn in meine Antwort zu integrieren. Heutzutage würde ich auf den neuesten .NET Frameworks das Ganze als Erweiterungsmethode bündeln. Dies würde sowohl die Wiederverwendbarkeit als auch die Klarheit verbessern.
Kbrimington
62

Wenn Sie .NET 3.5 SP1 oder .NET 4 verwenden, können Sie dies (ab) verwenden RouteValueDictionary. Es implementiert IDictionary<string, object>und verfügt über einen Konstruktor, objectder Eigenschaften akzeptiert und in Schlüssel-Wert-Paare konvertiert.

Es wäre dann trivial, die Schlüssel und Werte zu durchlaufen, um Ihre Abfragezeichenfolge zu erstellen.

GWB
quelle
4
Ich sage "Missbrauch", weil die Klasse ursprünglich für das Routing konzipiert wurde (oder zumindest der Name und der Namespace dies implizieren). Es enthält jedoch keine routingspezifischen Funktionen und wird bereits für andere Funktionen verwendet (z. B. das Konvertieren anonymer Objekte in Wörterbücher für HTML-Attribute in den ASP.NET MVC- HtmlHelperErweiterungsmethoden.
GWB
Ich habe genau das getan, aber jetzt muss ich vom RouteValueDictionary zurück zum anomischen Objekt gehen, irgendwelche Gedanken?
Joshy
1
Dies ist nicht einmal ein Missbrauch davon. Microsoft tut es. Wenn Sie beispielsweise HtmlHelper.TextBox ... aufrufen, müssen Sie einen anonymen Typ übergeben, um die Attributwerte festzulegen. Dies führt tatsächlich zu einem Bindungsfehler in Razor (z. B. versuchen Sie, @ Html.Partial aufzurufen ("~ / Shared / _PartialControl.cshtml", new {id = "id", value = "value"}), und es wird ein Bindungsfehler ausgelöst Die TextBox-Methode ruft intern "public static RouteValueDictionary AnonymousObjectToHtmlAttributes (object htmlAttributes)" auf, das ein RouteValueDictionary zurückgibt. Also los geht's.
Triynko
@Triynko das bedeutet nur, dass Microsoft es auch missbraucht. In Zukunft, wenn sie die Funktionalität ändern, um etwas zu tun, das spezifisch für das Routing ist. Ich würde empfehlen, eine eigene Implementierung basierend auf der Quelle zu erstellen und ihr einen passenderen Namen zu geben.
Nick Coad
6
Abgesehen davon, dass abhängig von Ihrer Codebasis eine Abhängigkeit von System.Web erzwungen wird, die sonst möglicherweise nicht benötigt wird. Art von Wunsch war die Klasse generischer und saß höher / in einem anderen Namespace.
Nick Albrecht
28

So machen sie es in RouteValueDictionary:

  private void AddValues(object values)
    {
        if (values != null)
        {
            foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(values))
            {
                object obj2 = descriptor.GetValue(values);
                this.Add(descriptor.Name, obj2);
            }
        }
    }

Die vollständige Quelle finden Sie hier: http://pastebin.com/c1gQpBMG


quelle
Ich habe versucht, den Code aus Pastebin zu verwenden, und Visual Studio sagte, dass einige der Dictionary-Methoden nicht implementiert wurden. Ich musste eine explizite Besetzung für IDictionary vornehmen. Ich habe gerade ein paar "this._dictionary" auf "((IDictionary <string, object>) this._dictionary)"
umgestellt
3

Es gibt eine integrierte Methode zum Konvertieren anonymer Objekte in Wörterbücher:

HtmlHelper.AnonymousObjectToHtmlAttributes(yourObj)

Es kehrt auch zurück RouteValueDictionary. Beachten Sie, dass es statisch ist

Xymanek
quelle
Laut docs.microsoft.com/en-us/previous-versions/aspnet/… "Ersetzt Unterstriche (_) durch Bindestriche (-) in den angegebenen HTML-Attributen" kann dies in einigen Fällen ein Problem sein.
Leres Aldtai
2
using Newtonsoft.Json;
var data = new {data1 = "test1", data2 = "sam", data3 = "bob"};
var encodedData = new FormUrlEncodedContent(JsonConvert.DeserializeObject<Dictionary<string, string>>(JsonConvert.SerializeObject(data))
Konstantin Salavatov
quelle
2
Bitte fügen Sie der Antwort eine Erklärung hinzu. Nur-Code-Antworten verschwenden die Zeit des Prüfers und werden oft missverstanden. Sie können sogar gelöscht werden.
Munim Munna
1

Die Lösung von @ kbrimington ist eine nette Erweiterungsmethode - mein Fall ist die Rückgabe eines HTML-Strings

    public static System.Web.HtmlString ToHTMLAttributeString(this Object attributes)
    {
        var props = attributes.GetType().GetProperties();
        var pairs = props.Select(x => string.Format(@"{0}=""{1}""",x.Name,x.GetValue(attributes, null))).ToArray();
        return new HtmlString(string.Join(" ", pairs));
    }

Ich verwende es, um beliebige Attribute in einer Razor MVC-Ansicht abzulegen. Ich habe mit Code angefangen, der RouteValueDictionary verwendet und die Ergebnisse wiederholt, aber das ist viel ordentlicher.

Andiih
quelle
6
Dies ist bereits in der Box vorhanden (zumindest jetzt):HtmlHelper.AnonymousObjectToHtmlAttributes
Andrew
1

Ich habe so etwas gemacht:

public class ObjectDictionary : Dictionary<string, object>
{
    /// <summary>
    /// Construct.
    /// </summary>
    /// <param name="a_source">Source object.</param>
    public ObjectDictionary(object a_source)
        : base(ParseObject(a_source))
    {

    }

    /// <summary>
    /// Create a dictionary from the given object (<paramref name="a_source"/>).
    /// </summary>
    /// <param name="a_source">Source object.</param>
    /// <returns>Created dictionary.</returns>
    /// <exception cref="ArgumentNullException">Thrown if <paramref name="a_source"/> is null.</exception>
    private static IDictionary<String, Object> ParseObject(object a_source)
    {
        #region Argument Validation

        if (a_source == null)
            throw new ArgumentNullException("a_source");

        #endregion

        var type = a_source.GetType();
        var props = type.GetProperties();

        return props.ToDictionary(x => x.Name, x => x.GetValue(a_source, null));
    }
}
Jordanien
quelle
1

Aufbauend auf dem Vorschlag von @ GWB, a zu verwenden RouteValueDictionary, habe ich diese rekursive Funktion geschrieben, um verschachtelte anonyme Typen zu unterstützen, wobei diesen verschachtelten Parametern die Schlüssel ihrer Eltern vorangestellt werden.

public static string EncodeHtmlRequestBody(object data, string parent = null) {
    var keyValuePairs = new List<string>();
    var dict = new RouteValueDictionary(data);

    foreach (var pair in dict) {
        string key = parent == null ? pair.Key : parent + "." + pair.Key;
        var type = pair.Value.GetType();
        if (type.IsPrimitive || type == typeof(decimal) || type == typeof(string)) {
            keyValuePairs.Add(key + "=" + Uri.EscapeDataString((string)pair.Value).Replace("%20", "+"));
        } else {
            keyValuePairs.Add(EncodeHtmlRequestBody(pair.Value, key));
        }
    }

    return String.Join("&", keyValuePairs);
}

Anwendungsbeispiel:

var data = new {
    apiOperation = "AUTHORIZE",
    order = new {
        id = "order123",
        amount = "101.00",
        currency = "AUD"
    },
    transaction = new {
        id = "transaction123"
    },
    sourceOfFunds = new {
        type = "CARD",
        provided = new {
            card = new {
                expiry = new {
                    month = "1",
                    year = "20"
                },
                nameOnCard = "John Smith",
                number = "4444333322221111",
                securityCode = "123"
            }
        }
    }
};

string encodedData = EncodeHtmlRequestBody(data);

encodedData wird:

"apiOperation=AUTHORIZE&order.id=order123&order.amount=101.00&order.currency=AUD&transaction.id=transaction123&sourceOfFunds.type=CARD&sourceOfFunds.provided.card.expiry.month=1&sourceOfFunds.provided.card.expiry.year=20&sourceOfFunds.provided.card.nameOnCard=John+Smith&sourceOfFunds.provided.card.number=4444333322221111&sourceOfFunds.provided.card.securityCode=123"

Hoffe das hilft jemand anderem in einer ähnlichen Situation.

Bearbeiten: Wie DrewG betonte, unterstützt dies keine Arrays. Die ordnungsgemäße Implementierung der Unterstützung für beliebig verschachtelte Arrays mit anonymen Typen wäre nicht trivial, und da keine der von mir verwendeten APIs Arrays akzeptiert hat (ich bin nicht sicher, ob es überhaupt eine standardisierte Methode gibt, sie mit Formularcodierung zu serialisieren). Ich überlasse das euch, wenn ihr sie unterstützen müsst.

Extragorey
quelle
Heads up, dies behandelt Arrays nicht richtig. Stapelt Überlauf. brauche so etwas wenn (type.IsArray) {var arr = pair.Value as string []; if (arr! = null) {foreach (var s in arr) {keyValuePairs.Add ((key + "[] =" + s) .Replace ("", "+")); }}}
DrewG
0

Es ist zu spät, aber ich würde dies für eine robustere Lösung hinzufügen. Diejenigen, die ich hier sehe, haben einige Probleme (als würden sie mit DateTime nicht richtig funktionieren). Aus diesem Grund schlage ich vor, zuerst in einen JSON (Newtonsoft Json.Net) zu konvertieren:

var data = new {data1 = "test1", data2 = "sam", data3 = "bob"};

var result = string.Join("&",
            JsonConvert.DeserializeObject<Dictionary<string, string>>(
            JsonConvert.SerializeObject(data))
            .Select(x => $"{x.Key}={x.Value}")
        );
Cetin Basoz
quelle