Passer plusieurs objets complexes à une méthode D'API POST / put Web
est-ce que certains peuvent s'il vous plaît m'aider à savoir comment passer plusieurs objets d'une application c# console au contrôleur D'API Web comme indiqué ci-dessous?
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);
}
ma méthode D'API Web est comme ceci:
public void StartProcessiong([FromBody]Content content, [FromBody]Config config)
{
}
10 réponses
dans la version actuelle de L'API Web, l'utilisation de objets complexes multiples (Content
et Config
objets complexes) dans la méthode de l'API Web, la signature est permis. Je vais parier de l'argent bon que config
(votre second paramètre) revient toujours comme NULL. C'est parce qu'un objet complexe peut être analysée à partir du corps pour une seule demande. Pour des raisons de performance, le corps de requête de L'API Web n'est autorisé qu'à être consulté et analysée une fois. Ainsi, après le scan et l'analyse se produit du corps de la requête pour le paramètre "content", toutes les parties suivantes du corps se termineront par"NULL". Donc en gros:
- un Seul élément peut être attribuée avec
[FromBody]
. - N'importe quel nombre d'articles peut être attribué avec
[FromUri]
.
ci-Dessous est utile pour extraire de la Mike Décrochage de l'excellent article du blog (ancien mais goldie!). Vous voudrez faire attention à point 4:
Voici les règles de base pour déterminer si un paramètre est lu avec une liaison de modèle ou un formatteur:
- si le paramètre n'a pas d'attribut sur lui, alors la décision est prise purement sur le type .net du paramètre. Les" types simples " utilisent la reliure de modèle. Les types complexes utilisent les formateurs. Un "simple" comprend: primitives,
TimeSpan
,DateTime
,Guid
,Decimal
,String
, ou quelque chose avec unTypeConverter
qui se convertit en chaînes.- Vous pouvez utiliser un
[FromBody]
attribut pour spécifier qu'un paramètre doit provenir du corps.- Vous pouvez utiliser un
[ModelBinder]
l'attribut sur le paramètre ou le paramètre du type d'indiquer qu'un paramètre doit être lié. Cet attribut permet également de configurer le modèle de classeur.[FromUri]
est une instance dérivée de[ModelBinder]
qui configure spécifiquement un modèle de classeur pour ne regarder que dans L'URI.- Le corps ne peut être lu une fois. Donc si vous avez 2 types complexes dans la signature, au moins l'un d'entre eux doit avoir un
[ModelBinder]
attribut.c'était un objectif clé de conception pour que ces règles soient statiques et prévisibles.
une différence clé entre MVC et L'API Web est que MVC protège le contenu (par exemple le corps de la requête). Cela signifie que la liaison de paramètres de MVC peut chercher à plusieurs reprises dans le corps pour trouver des morceaux des paramètres. Alors que dans l'API Web, le du corps de la requête (un
HttpContent
) peut être un flux en lecture seule, infini, non-tamponné, non-rewindable.
vous pouvez lire le reste de cet article incroyablement utile sur votre propre ainsi, pour couper une longue histoire courte, ce que vous essayez de faire n'est pas actuellement possible de cette façon (c'est-à-dire que vous devez être créatif). Ce qui suit n'est pas une solution, mais une solution de contournement et seulement une possibilité; Il ya d'autres façon.
Solution / Workaroundaround
(Avertissement: Je ne l'ai pas utilisé moi-même, je suis juste au courant de la théorie!)
une "solution" possible est d'utiliser le JObject
objet. Cet objet fournit un type de béton spécialement conçu pour travailler avec JSON.
vous avez simplement besoin d'ajuster la signature pour n'accepter qu'un seul objet complexe du corps, le JObject
, nous allons l'appeler stuff
. Puis, vous devez analyser manuellement les propriétés de L'objet JSON et utiliser des génériques pour hydrater les types de béton.
Par exemple, ci-dessous un rapide n'dirty exemple pour vous donner une idée:
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!
}
j'ai dit il y a d'autres façons, par exemple, vous pouvez simplement envelopper vos deux objets dans un super-objet de votre propre création et le passer à votre méthode d'action. Ou vous pouvez simplement éliminer la nécessité de deux paramètres complexes dans le corps de la requête en fournissant l'un des dans l'URI. Ou. .. eh bien, vous obtenez le point.
Permettez-moi juste de répéter que je n'ai pas essayé tout cela moi-même, bien que tout devrait fonctionner en théorie.
comme @djikay l'a mentionné, vous ne pouvez pas passer plusieurs FromBody
paramètres.
un contournement que j'ai est de définir un CompositeObject
,
public class CompositeObject
{
public Content Content { get; set; }
public Config Config { get; set; }
}
et vous avez votre WebAPI prend ce CompositeObject
en tant que paramètre à la place.
public void StartProcessiong([FromBody] CompositeObject composite)
{ ... }
vous pouvez essayer de poster du contenu multipartite à partir du client comme ceci:
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
}
}
}
Sur le serveur, vous pourriez lire le contenu comme ceci:
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;
}
}
je sais que c'est une vieille question, mais j'ai eu le même problème et voici ce que j'ai trouvé et je l'espère sera utile à quelqu'un. Cela permettra de passer les paramètres formatés par JSON individuellement dans l'URL de la requête (GET), comme un seul objet JSON après ? (GET) ou à l'intérieur d'un seul objet corporel JSON (POST). Mon objectif était la fonctionnalité de type RPC.
créé un attribut personnalisé et une liaison de paramètre, héritant de HttpParameterBinding:
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
{
}
}
Injecter de liaison règle dans WebApiConfig.cs s'Inscrire méthode:
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 }
);
}
cela permet des actions de controller avec des valeurs de paramètres par défaut et une complexité mixte, en tant que telle:
[JSONParamBinding]
[HttpPost, HttpGet]
public Widget DoWidgetStuff(Widget widget, int stockCount, string comment="no comment")
{
... do stuff, return Widget object
}
exemple de poste corps:
{
"widget": {
"a": 1,
"b": "string",
"c": { "other": "things" }
},
"stockCount": 42,
"comment": "sample code"
}
ou OBTENIR de l'unique param (besoins d'encodage d'URL)
controllerPath/DoWidgetStuff?{"widget":{..},"comment":"test","stockCount":42}
ou OBTENIR plusieurs param (besoins d'encodage d'URL)
controllerPath/DoWidgetStuff?widget={..}&comment="test"&stockCount=42
créer un objet complexe pour combiner le contenu et la configuration en elle comme d'autres l'ont mentionné, utiliser dynamique et juste faire un .ToObject (); as:
[HttpPost]
public void StartProcessiong([FromBody] dynamic obj)
{
var complexObj= obj.ToObject<ComplexObj>();
var content = complexObj.Content;
var config = complexObj.Config;
}
voici un autre modèle qui pourrait vous être utile. C'est pour un Get mais le même principe et le code s'applique pour un Post/Put mais à l'inverse. Il fonctionne essentiellement sur le principe de convertir les objets en cette classe ObjectWrapper qui persiste le nom du Type de l'autre côté:
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
}
}
Dans le RESTE du 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;
}
c'est le code du côté client (UWP) en utilisant une bibliothèque de clients REST. La bibliothèque client n'utilise que la sérialisation JSON de Newtonsoft bibliothèque - rien de compliqué.
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;
}
fondamentalement, vous pouvez envoyer un objet complexe sans faire quelque chose de fantaisie supplémentaire. Ou sans modifications sur le site Web de l'Api. Je veux dire pourquoi devrions-nous faire des changements à L'Api Web, alors que la faute est dans notre code qui appelle L'Api Web.
Tout ce que vous avez à faire utilisez la bibliothèque JSON de NewtonSoft comme suit.
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);
ici j'ai trouvé une solution pour passer plusieurs objets génériques (comme json) de jquery à une API WEB en utilisant JObject, puis renvoyez à votre type d'objet spécifique requis dans le contrôleur api. Cet objet fournit un type de béton spécialement conçu pour travailler avec JSON.
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);
});
et ensuite, vous pouvez obtenir cet objet dans votre controller
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*;
}
j'ai fait une fonction générique pour déballer l'objet complexe, donc il n'y a pas de limitation de nombre d'objets lors de l'envoi et du déballage. On peut même envoyer plus de deux objets
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;
}
}
j'ai posté la solution sur mon blog avec un peu plus de description avec du code plus simple à intégrer facilement.
passer plusieurs objets complexes à L'API Web
j'espère que ça aiderait quelqu'un. Je serais intéressé d'entendre les experts sur les avantages et les inconvénients de l'utilisation de cette méthodologie.
réponse tardive, mais vous pouvez profiter du fait que vous pouvez desérialiser plusieurs objets à partir d'une chaîne JSON, aussi longtemps que les objets ne partagent aucun nom de propriété commun,
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);
}
La meilleure façon de passer plusieurs objets complexes aux services webapi est d'utiliser tuple autre que dynamic, JSON string, custom class.
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();
}
pas besoin de sérialiser et de desérialiser l'objet passant en utilisant tuple. Si vous voulez envoyer plus de sept objets complexes, créez un objet tuple interne pour le dernier argument tuple.