How to send complex object to Web API?

Asked

Viewed 3,982 times

2

I need to pass a parameter to my Controller in the Web API which is a complex object, this object has some normal properties (the most complex is a Datetime). I’m doing it this way, but I can’t access it:

Webapiconfig Route:

config.Routes.MapHttpRoute("DefaultApiWithActionAndSkip",
                "api/{controller}/{action}/{skip}",
                defaults: new { skip = RouteParameter.Optional });

Place where I make the request:

private CargaInicialHelper()
    {
        _client = new HttpClient();
        _client.DefaultRequestHeaders.Clear();
        _client.DefaultRequestHeaders.Accept.Add(new Windows.Web.Http.Headers.HttpMediaTypeWithQualityHeaderValue("application/xml"));
    }
    _client.DefaultRequestHeaders.Accept.Add(new Windows.Web.Http.Headers.HttpMediaTypeWithQualityHeaderValue("application/json"));
}

Apicontroller:

public async Task<bool> RegistrarTerminal(Terminal terminal)
{
    return await ObterRespostaServicoAsync<bool>("RegistrarTerminal",
                                                    new HttpStringContent(JsonConvert.SerializeObject(terminal),
                                                    Windows.Storage.Streams.UnicodeEncoding.Utf8,
                                                    "application/xml"));
}

Where I get the response sent by the Web API (I know it works because in this place where I do the request I have several other requests being made (but all with normal variables) and they all work):

private async Task<T> ObterRespostaServicoAsync<T>(string relativePath, HttpStringContent content = null)
    {
        try
        {
            var request = new HttpRequestMessage();
            if (content == null)
            {
                request = new HttpRequestMessage(HttpMethod.Get, new Uri(string.Format(URL, relativePath ?? String.Empty)));
            }
            else
            {
                request = new HttpRequestMessage(HttpMethod.Post, new Uri(string.Format(URL, relativePath ?? String.Empty)));
                request.Content = content;
                var teste = await _client.PostAsync(request.RequestUri, content);
            }

            request.Headers.TryAppendWithoutValidation("Content-Type", "application/xml");
            var response = await _client.GetAsync(request.RequestUri);

            response.EnsureSuccessStatusCode();
            string xml = Utils.RemoveAllXmlNamespaces(await response.Content.ReadAsStringAsync());
            reader = new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(xml)));

            XmlSerializer serializer = new XmlSerializer(typeof(T));
            return (T)serializer.Deserialize(reader);
        }
        catch (Exception e)
        {
            return default(T);
        }
    }

Error:

Bad request (500). "Value cannot be null. Parameter name: Entity"

  • I’m not familiar with c# but in java if you sent a certain content-type , in your case application/json should not you "note" the method that is receiving this content-type? In your case the RegistrarTerminal

  • @Jorgecampos no, it’s not necessary.

  • Have you tried a requisition with a payload in handmade JSON using this?

  • @Ciganomorrisonmendez vc says to pass a string as parameter that would be JSON? If it is, yes.

  • Another question: are you deserializing an XML or a JSON? It’s not clear to me.

  • @Ciganomorrisonmendez basically xml. was trying with json as well but I couldn’t get it to work on either. I now saw a possible problem, where I mount Stringcontent put 2 parameters in the constructor, encondig.utf-8 and also a content-type application/xml. Changed the error, now says "Value cannot be null. Parameter name: Entity"

  • Check your route on WebApiConfig.cs. Another thing, the terminal is coming null?

  • config.Routes.Maphttproute("Defaultapiwithactionandskip", "api/{controller}/{action}/{Skip}", defaults: new { Skip = Routeparameter.Optional }); Terminal is not null.

  • insert route up there.

Show 4 more comments

1 answer

4

You are passing an arbitrary value in the request URL - it is possible that there is a character that is not supported, or that you will change the semantics of the URL (e. g.: ., :, /).

This is probably a symptom of a major problem - do not use [HttpGet] (or the GET verb in general) in HTTP operations that are not idempotented, or that need to pass complex parameters, or that will perform some change in the server. RegisterTerminal, by name, should do this, so should be used with a POST (or PUT, depending on the application semantics). These operations accept a body of the request (request body), where you can pass the parameter (terminal) smoothly.

For example, given this controller:

public class ServicoController : ApiController
{
    [HttpPost, Route("RegistrarTerminal/{nome}")]
    public bool RegistrarTerminal(string nome, Terminal terminal)
    {
        return terminal.Nome == terminal.Codigo;
    }
}

public class Terminal
{
    public string Nome { get; set; }
    public string Codigo { get; set; }
    public int Valor { get; set; }
}

You can access it via the following HTTP request:

POST http://localhost:50381/RegistrarTerminal/foo HTTP/1.1
User-Agent: Fiddler
Host: localhost:50381
Content-Length: 82
Content-Type: application/json

{
  "nome":"Nome do terminal",
  "codigo":"TA:12/13&14",
  "valor": 3
}

Or with the following code C#:

class Program
{
    static void Main(string[] args)
    {
        HttpClient c = new HttpClient();
        c.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        c.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));

        var req = new HttpRequestMessage(HttpMethod.Post, "http://localhost:50381/RegistrarTerminal/foo");
        var reqBody = @"{
          'nome':'Nome do terminal',
          'codigo':'TA:12/13&14',
          'valor': 3
        }".Replace('\'', '\"');
        req.Content = new StringContent(reqBody, Encoding.UTF8, "application/json");
        var resp = c.SendAsync(req).Result;
        Console.WriteLine(resp);
        Console.WriteLine(resp.Content.ReadAsStringAsync().Result);
    }
}

Note the creation of StringContent (or of HttpStringContent in your case) - you should pass the Content-Type at that point. You are using the TryAddWithoutValidation to set the Content-Type, and I’m pretty sure that this call is returning false (i.e., the operation is not being performed). This causes the request to exit with a Content-Type: text/plain, which is not recognised by Controller, and that causes the answer 415 Unsupported Media Type that you are receiving.

Another Alternative is, after the creation of the HttpStringContent, you set your Content-Type:

HttpStringContent content = ...;
content.Headers.ContentType = new HttpMediaTypeHeaderValue("application/json");
  • Is it possible to pass an example with those functions adapted to the way you spoke? The problem that is the Get I already know, but I tried numerous other ways and I could not make it work.

  • I’m unable to configure content... I need an example.

  • I added an example from Controller.

  • need an example in C#, it is possible?

  • Your example is a string and not a complex type. I will change my code, I have made some changes but it still doesn’t work, this giving error 415.

  • Terminal is a complex type (a class with 3 properties). The code, as it is in your question, comes to compile? ObterRespostaServicoAsync receives a string and a HttpStringContent, and you’re calling with two strings...

  • Another thing - the code above uses the class HttpClient from System.Net.Http (Nuget Microsoft.Net.Http), and it looks like you are using Windows.Web.Http; you would have to make some changes (e. g., new HttpStringContent(reqBody, UnicodeEncoding.Utf8, "application/json")), but you can get an idea.

  • Yes, this was done. Even before you post. What would these changes be? My problem now, as I said in the comments above explaining to the Gypsy, is the following "Value cannot be null. Parameter name: Entity".

  • Where is that mistake?

  • When you go by "Postasync".

  • You can post one complete example that reproduces your problem? Your code has a call to PostAsync, but that uses a parameter, and the question does not show how it is created.

  • I don’t understand what you need to know. But I will change the code again.

Show 8 more comments

Browser other questions tagged

You are not signed in. Login or sign up in order to post.