wake-up-neo.net

Übergeben Sie mehrere komplexe Objekte an eine Post/Put-Web-API-Methode

Können mir einige helfen, wie Sie mehrere Objekte von einer C # -Konsolenanwendung an den Web-API-Controller übergeben, wie unten gezeigt?

using (var httpClient = new System.Net.Http.HttpClient())
{
    httpClient.BaseAddress = new Uri(ConfigurationManager.AppSettings["Url"]);
    httpClient.DefaultRequestHeaders.Accept.Clear();
    httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));   

    var response = httpClient.PutAsync("api/process/StartProcessiong", objectA, objectB);
}

Meine Web-API-Methode sieht folgendermaßen aus:

public void StartProcessiong([FromBody]Content content, [FromBody]Config config)
{

}
33
SKumar

In der aktuellen Version der Web-API wird die Verwendung von mehreren komplexen Objekten (wie Ihren Content und Config komplexen Objekten) empfohlen ) innerhalb der Web-API-Methodensignatur ist nicht zulässig. Ich wette mit gutem Geld, dass config (Ihr zweiter Parameter) immer als NULL zurückkommt. Dies liegt daran, dass für eine Anforderung nur ein komplexes Objekt aus dem Hauptteil analysiert werden kann. Aus Leistungsgründen darf auf den Web-API-Anforderungshauptteil nur einmal zugegriffen und analysiert werden . Nachdem das Scannen und Parsen des Anforderungshauptteils für den Parameter "content" erfolgt ist, enden alle nachfolgenden Hauptteil-Parsen mit "NULL". Also im Grunde genommen:

  • Mit [FromBody] Kann nur ein Artikel zugeordnet werden.
  • Beliebig viele Artikel können mit [FromUri] Zugeordnet werden.

Unten ist ein nützlicher Auszug aus Mike Stalls ausgezeichneter Blog-Artikel (oldie but goldie!). Beachten Sie Punkt 4 :

Hier sind die Grundregeln, um zu bestimmen, ob ein Parameter mit Modellbindung oder mit einem Formatierer gelesen wird:

  1. Wenn der Parameter kein Attribut enthält, wird die Entscheidung ausschließlich über den .NET-Typ des Parameters getroffen. "Einfache Typen" verwenden die Modellbindung. Komplexe Typen verwenden die Formatierer. Ein "einfacher Typ" beinhaltet: Primitive , TimeSpan, DateTime, Guid, Decimal, String Oder etwas mit einem TypeConverter, das aus Zeichenfolgen konvertiert.
  2. Sie können ein Attribut [FromBody] Verwenden, um anzugeben, dass ein Parameter aus dem Hauptteil stammen soll.
  3. Sie können ein Attribut [ModelBinder] Für den Parameter oder den Parametertyp verwenden, um anzugeben, dass ein Parameter modellgebunden werden soll. Mit diesem Attribut können Sie auch den Modellordner konfigurieren. [FromUri] Ist eine abgeleitete Instanz von [ModelBinder], Die einen Modellordner so konfiguriert, dass er nur in der URI nachschaut.
  4. Der Körper kann nur einmal gelesen werden. Wenn Sie also 2 komplexe Typen in der Signatur haben, muss mindestens einer ein [ModelBinder] - Attribut haben.

Es war ein wichtiges Entwurfsziel, dass diese Regeln statisch und vorhersehbar sind.

Ein Hauptunterschied zwischen MVC und Web-API besteht darin, dass MVC den Inhalt puffert (z. B. den Anforderungshauptteil). Dies bedeutet, dass die MVC-Parameterbindung wiederholt den Hauptteil durchsuchen kann, um nach Teilen der Parameter zu suchen. Während in der Web-API der Anforderungshauptteil (ein HttpContent) ein schreibgeschützter, unendlicher, nicht gepufferter und nicht zurückspulbarer Stream sein kann.

Sie können den Rest dieses unglaublich nützlichen Artikels auf eigene Faust lesen. Um es kurz zu machen: Was Sie versuchen, ist derzeit nicht möglich auf diese Weise (das heißt, Sie haben kreativ werden). Was folgt, ist keine Lösung, sondern eine Umgehung und nur eine Möglichkeit. es gibt andere möglichkeiten.

Lösung/Problemumgehung

( Haftungsausschluss: Ich habe es nicht selbst benutzt, ich bin mir nur der Theorie bewusst!)

Eine mögliche "Lösung" besteht darin, das Objekt JObject zu verwenden. Dieses Objekt stellt einen konkreten Typ bereit, der speziell für die Arbeit mit JSON entwickelt wurde.

Sie müssen lediglich die Signatur anpassen, um nur ein komplexes Objekt aus dem Hauptteil zu akzeptieren, das JObject. Nennen wir es stuff. Anschließend müssen Sie die Eigenschaften des JSON-Objekts manuell analysieren und die Betontypen mithilfe von Generika hydratisieren.

Im Folgenden finden Sie beispielsweise ein Beispiel, das Ihnen eine Idee vermittelt:

public void StartProcessiong([FromBody]JObject stuff)
{
  // Extract your concrete objects from the json object.
  var content = stuff["content"].ToObject<Content>();
  var config = stuff["config"].ToObject<Config>();

  . . . // Now do your thing!
}

Ich habe gesagt, es gibt andere Möglichkeiten, zum Beispiel können Sie Ihre beiden Objekte einfach in ein Superobjekt Ihrer eigenen Schöpfung einwickeln und das an Ihre Handlungsmethode übergeben. Oder Sie können einfach die Notwendigkeit für zwei komplexe Parameter im Anforderungshauptteil beseitigen, indem Sie einen davon in der URI angeben. Oder ... nun, du verstehst, worum es geht.

Lassen Sie mich noch einmal wiederholen, dass ich nichts davon selbst ausprobiert habe, obwohl alles funktionieren sollte in der Theorie.

50
djikay

Wie @djikay erwähnt, können Sie nicht mehrere FromBody-Parameter übergeben.

Eine Problemumgehung besteht darin, eine CompositeObject zu definieren.

public class CompositeObject
{
    public Content Content { get; set; }
    public Config Config { get; set; }
}

und Ihre WebAPI nimmt stattdessen diesen CompositeObject als Parameter an.

public void StartProcessiong([FromBody] CompositeObject composite)
{ ... }
18
Maggie Ying

Sie könnten versuchen, mehrteiligen Inhalt vom Client aus zu veröffentlichen: 

 using (var httpClient = new HttpClient())
{
    var uri = new Uri("http://example.com/api/controller"));

    using (var formData = new MultipartFormDataContent())
    {
        //add content to form data
        formData.Add(new StringContent(JsonConvert.SerializeObject(content)), "Content");

        //add config to form data
        formData.Add(new StringContent(JsonConvert.SerializeObject(config)), "Config");

        var response = httpClient.PostAsync(uri, formData);
        response.Wait();

        if (!response.Result.IsSuccessStatusCode)
        {
            //error handling code goes here
        }
    }
}

Auf der Serverseite konnte man den Inhalt folgendermaßen lesen:

public async Task<HttpResponseMessage> Post()
{
    //make sure the post we have contains multi-part data
    if (!Request.Content.IsMimeMultipartContent())
    {
        throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
    }

    //read data
    var provider = new MultipartMemoryStreamProvider();
    await Request.Content.ReadAsMultipartAsync(provider);

    //declare backup file summary and file data vars
    var content = new Content();
    var config = new Config();

    //iterate over contents to get Content and Config
    foreach (var requestContents in provider.Contents)
    {
        if (requestContents.Headers.ContentDisposition.Name == "Content")
        {
            content = JsonConvert.DeserializeObject<Content>(requestContents.ReadAsStringAsync().Result);
        }
        else if (requestContents.Headers.ContentDisposition.Name == "Config")
        {
            config = JsonConvert.DeserializeObject<Config>(requestContents.ReadAsStringAsync().Result);
        }
    }

    //do something here with the content and config and set success flag
    var success = true;

    //indicate to caller if this was successful
    HttpResponseMessage result = Request.CreateResponse(success ? HttpStatusCode.OK : HttpStatusCode.InternalServerError, success);
    return result;

}

}

8
Brian Wenhold

Ich weiß, das ist eine alte Frage, aber ich hatte das gleiche Problem und hier ist, was ich mir ausgedacht habe, und hoffentlich wird es für jemanden nützlich sein. Dadurch können JSON-formatierte Parameter einzeln in der Anforderungs-URL (GET) als einzelnes JSON-Objekt übergeben werden. (GET) oder innerhalb eines einzelnen JSON-Body-Objekts (POST). Mein Ziel war die RPC-Funktionalität.

Es wurde eine benutzerdefinierte Attribut- und Parameterbindung erstellt, die von HttpParameterBinding erbt: 

public class JSONParamBindingAttribute : Attribute
{

}

public class JSONParamBinding : HttpParameterBinding
{

    private static JsonSerializer _serializer = JsonSerializer.Create(new JsonSerializerSettings()
    {
        DateTimeZoneHandling = DateTimeZoneHandling.Utc
    });


    public JSONParamBinding(HttpParameterDescriptor descriptor)
        : base(descriptor)
    {
    }

    public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider,
                                                HttpActionContext actionContext,
                                                CancellationToken cancellationToken)
    {
        JObject jobj = GetJSONParameters(actionContext.Request);

        object value = null;

        JToken jTokenVal = null;
        if (!jobj.TryGetValue(Descriptor.ParameterName, out jTokenVal))
        {
            if (Descriptor.IsOptional)
                value = Descriptor.DefaultValue;
            else
                throw new MissingFieldException("Missing parameter : " + Descriptor.ParameterName);
        }
        else
        {
            try
            {
                value = jTokenVal.ToObject(Descriptor.ParameterType, _serializer);
            }
            catch (Newtonsoft.Json.JsonException e)
            {
                throw new HttpParseException(String.Join("", "Unable to parse parameter: ", Descriptor.ParameterName, ". Type: ", Descriptor.ParameterType.ToString()));
            }
        }

        // Set the binding result here
        SetValue(actionContext, value);

        // now, we can return a completed task with no result
        TaskCompletionSource<AsyncVoid> tcs = new TaskCompletionSource<AsyncVoid>();
        tcs.SetResult(default(AsyncVoid));
        return tcs.Task;
    }

    public static HttpParameterBinding HookupParameterBinding(HttpParameterDescriptor descriptor)
    {
        if (descriptor.ActionDescriptor.ControllerDescriptor.GetCustomAttributes<JSONParamBindingAttribute>().Count == 0 
            && descriptor.ActionDescriptor.GetCustomAttributes<JSONParamBindingAttribute>().Count == 0)
            return null;

        var supportedMethods = descriptor.ActionDescriptor.SupportedHttpMethods;

        if (supportedMethods.Contains(HttpMethod.Post) || supportedMethods.Contains(HttpMethod.Get))
        {
            return new JSONParamBinding(descriptor);
        }

        return null;
    }

    private JObject GetJSONParameters(HttpRequestMessage request)
    {
        JObject jobj = null;
        object result = null;
        if (!request.Properties.TryGetValue("ParamsJSObject", out result))
        {
            if (request.Method == HttpMethod.Post)
            {
                jobj = JObject.Parse(request.Content.ReadAsStringAsync().Result);
            }
            else if (request.RequestUri.Query.StartsWith("?%7B"))
            {
                jobj = JObject.Parse(HttpUtility.UrlDecode(request.RequestUri.Query).TrimStart('?'));
            }
            else
            {
                jobj = new JObject();
                foreach (var kvp in request.GetQueryNameValuePairs())
                {
                    jobj.Add(kvp.Key, JToken.Parse(kvp.Value));
                }
            }
            request.Properties.Add("ParamsJSObject", jobj);
        }
        else
        {
            jobj = (JObject)result;
        }

        return jobj;
    }



    private struct AsyncVoid
    {
    }
}

Bindungsregel in WebApiConfig.cs Register - Methode einfügen: 

        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services

            // Web API routes
            config.MapHttpAttributeRoutes();

            config.ParameterBindingRules.Insert(0, JSONParamBinding.HookupParameterBinding);

            config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "{controller}/{action}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
        }

Dies ermöglicht Controller-Aktionen mit Standardparameterwerten und gemischter Komplexität:

[JSONParamBinding]
    [HttpPost, HttpGet]
    public Widget DoWidgetStuff(Widget widget, int stockCount, string comment="no comment")
    {
        ... do stuff, return Widget object
    }

beispiel Post Body:

{ 
    "widget": { 
        "a": 1, 
        "b": "string", 
        "c": { "other": "things" } 
    }, 
    "stockCount": 42, 
    "comment": "sample code"
} 

oder GET single param (benötigt URL-Kodierung)

controllerPath/DoWidgetStuff?{"widget":{..},"comment":"test","stockCount":42}

oder mehrere Parameter erhalten (benötigt URL-Kodierung)

controllerPath/DoWidgetStuff?widget={..}&comment="test"&stockCount=42
4
user6775030

Erstellen Sie ein komplexes Objekt, um Inhalt und Konfig darin zu kombinieren, verwenden Sie dynamic und führen Sie einfach ein. wie:

[HttpPost]
public void StartProcessiong([FromBody] dynamic obj)
{
   var complexObj= obj.ToObject<ComplexObj>();
   var content = complexObj.Content;
   var config = complexObj.Config;
}
1
harlandgomez

Hier ist ein anderes Muster, das für Sie nützlich sein kann. Es ist für ein Get, aber das gleiche Prinzip und derselbe Code gelten für ein Post/Put, aber umgekehrt. Es funktioniert im Wesentlichen nach dem Prinzip der Konvertierung von Objekten in diese ObjectWrapper-Klasse, die den Namen des Typs auf der anderen Seite beibehält:

using Newtonsoft.Json;
using System;
using System.Collections.Generic;

namespace WebAPI
{
    public class ObjectWrapper
    {
        #region Public Properties
        public string RecordJson { get; set; }
        public string TypeFullName { get; set; }
        #endregion

        #region Constructors

        public ObjectWrapper() : this(null, null)
        {
        }

        public ObjectWrapper(object objectForWrapping) : this(objectForWrapping, null)
        {
        }

        public ObjectWrapper(object objectForWrapping, string typeFullName)
        {
            if (typeFullName == null && objectForWrapping != null)
            {
                TypeFullName = objectForWrapping.GetType().FullName;
            }
            else
            {
                TypeFullName = typeFullName;
            }

            RecordJson = JsonConvert.SerializeObject(objectForWrapping);
        }
        #endregion

        #region Public Methods
        public object ToObject()
        {
            var type = Type.GetType(TypeFullName);
            return JsonConvert.DeserializeObject(RecordJson, type);
        }
        #endregion

        #region Public Static Methods
        public static List<ObjectWrapper> WrapObjects(List<object> records)
        {
            var retVal = new List<ObjectWrapper>();
            records.ForEach
            (item =>
            {
                retVal.Add
                (
                    new ObjectWrapper(item)
                );
            }
            );

            return retVal;
        }

        public static List<object> UnwrapObjects(IEnumerable<ObjectWrapper> objectWrappers)
        {
            var retVal = new List<object>();

            foreach(var item in objectWrappers)
            {
                retVal.Add
                (
                    item.ToObject()
                );
            }

            return retVal;
        }
        #endregion
    }
}

Im REST Code:

[HttpGet]
public IEnumerable<ObjectWrapper> Get()
{
    var records = new List<object>();
    records.Add(new TestRecord1());
    records.Add(new TestRecord2());
    var wrappedObjects = ObjectWrapper.WrapObjects(records);
    return wrappedObjects;
}

Dies ist der Code auf der Clientseite (UWP), der eine REST - Clientbibliothek verwendet. Die Client-Bibliothek verwendet nur die Newtonsoft Json-Serialisierungsbibliothek - nichts Besonderes.

private static async Task<List<object>> Getobjects()
{
    var result = await REST.Get<List<ObjectWrapper>>("http://localhost:50623/api/values");
    var wrappedObjects = (IEnumerable<ObjectWrapper>) result.Data;
    var unwrappedObjects =  ObjectWrapper.UnwrapObjects(wrappedObjects);
    return unwrappedObjects;
}
0

Der beste Weg, mehrere komplexe Objekte an webapi-Dienste zu übergeben, ist die Verwendung von Tuple, mit Ausnahme der benutzerdefinierten dynamischen Klasse json string.

HttpClient.PostAsJsonAsync("http://Server/WebService/Controller/ServiceMethod?number=" + number + "&name" + name, Tuple.Create(args1, args2, args3, args4));

[HttpPost]
[Route("ServiceMethod")]
[ResponseType(typeof(void))]
public IHttpActionResult ServiceMethod(int number, string name, Tuple<Class1, Class2, Class3, Class4> args)
{
    Class1 c1 = (Class1)args.Item1;
    Class2 c2 = (Class2)args.Item2;
    Class3 c3 = (Class3)args.Item3;
    Class4 c4 = (Class4)args.Item4;
    /* do your actions */
    return Ok();
}

Wenn Sie mehr als sieben komplexe Objekte senden möchten, erstellen Sie ein internes Tuple-Objekt für das letzte Tuple-Argument.

0
kota

Späte Antwort, aber Sie können die Tatsache nutzen, dass Sie mehrere Objekte aus einem JSON-String deserialisieren können, solange die Objekte keine gemeinsamen Eigenschaftsnamen haben

    public async Task<HttpResponseMessage> Post(HttpRequestMessage request)
    {
        var jsonString = await request.Content.ReadAsStringAsync();
        var content  = JsonConvert.DeserializeObject<Content >(jsonString);
        var config  = JsonConvert.DeserializeObject<Config>(jsonString);
    }
0
Rob Sedgwick

Hier habe ich eine Problemumgehung gefunden, um mehrere generische Objekte (als Json) von Jquery mithilfe von JObject an eine WEB-API zu übergeben und dann in den API-Controller auf den gewünschten spezifischen Objekttyp zurückzustimmen. Dieses Objekt bietet einen konkreten Typ, der speziell für die Arbeit mit JSON entwickelt wurde.

var combinedObj = {}; 
combinedObj["obj1"] = [your json object 1]; 
combinedObj["obj2"] = [your json object 2];

$http({
       method: 'POST',
       url: 'api/PostGenericObjects/',
       data: JSON.stringify(combinedObj)
    }).then(function successCallback(response) {
         // this callback will be called asynchronously
         // when the response is available
         alert("Saved Successfully !!!");
    }, function errorCallback(response) {
         // called asynchronously if an error occurs
         // or server returns response with an error status.
         alert("Error : " + response.data.ExceptionMessage);
});

und dann können Sie dieses Objekt in Ihrem Controller erhalten 

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

public [OBJECT] PostGenericObjects(object obj)
    {
        string[] str = GeneralMethods.UnWrapObjects(obj);
        var item1 = JsonConvert.DeserializeObject<ObjectType1>(str[0]);
        var item2 = JsonConvert.DeserializeObject<ObjectType2>(str[1]);

        return *something*;
    } 

Ich habe eine generische Funktion zum Entpacken des komplexen Objekts erstellt, sodass die Anzahl der Objekte beim Senden und Entpacken nicht begrenzt ist. Wir können sogar mehr als zwei Objekte senden 

public class GeneralMethods
{
    public static string[] UnWrapObjects(object obj)
    {
        JObject o = JObject.Parse(obj.ToString());

        string[] str = new string[o.Count];

        for (int i = 0; i < o.Count; i++)
        {
            string var = "obj" + (i + 1).ToString();
            str[i] = o[var].ToString(); 
        }

        return str;
    }

}

Ich habe die Lösung in meinem Blog mit etwas mehr Beschreibung und einfacherem Code veröffentlicht, um sie leicht zu integrieren.

Mehrere komplexe Objekte an die Web-API übergeben

Ich hoffe es würde jemandem helfen. Ich würde gerne von den Experten hier über die Vor- und Nachteile dieser Methode erfahren.

0
Sheikh M. Haris

Grundsätzlich können Sie komplexe Objekte versenden, ohne dass Sie etwas Besonderes tun müssen. Oder ohne Änderungen an Web-Api vorzunehmen. Ich meine, warum sollten wir Änderungen an der Web-Api vornehmen müssen, während der Fehler in unserem Code liegt, der die Web-Api aufruft.

Alles, was Sie tun müssen, ist die Json-Bibliothek von NewtonSoft wie folgt.

string jsonObjectA = JsonConvert.SerializeObject(objectA);
string jsonObjectB = JsonConvert.SerializeObject(objectB);
string jSoNToPost = string.Format("\"content\": {0},\"config\":\"{1}\"",jsonObjectA , jsonObjectB );
//wrap it around in object container notation
jSoNToPost = string.Concat("{", jSoNToPost , "}"); 
//convert it to JSON acceptible content
HttpContent content = new StringContent(jSoNToPost , Encoding.UTF8, "application/json"); 

var response = httpClient.PutAsync("api/process/StartProcessiong", content);
0
Manzar Zafar