How to send complex object to Web API?

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 the following, but I can not access:

WebApiConfig Route:

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

Location 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"));
}

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 can not be null. Parameter name: entity "

    
asked by anonymous 14.10.2015 / 22:00

1 answer

4

You are passing an arbitrary value on the request URL - there may be a character that is not supported, or it will change the semantics of the URL (eg: . , : , / ). / p>

This is probably a symptom of a major problem - do not use [HttpGet] (or the GET verb in general) for HTTP operations that are not idempotent, need to pass complex parameters, or will make any changes to the server. Register Terminal, by name, should do this, so it should be used with POST (or PUT , depending on the semantics of the application). These operations accept a request body ( request body ), where you can pass the (terminal) parameter without any problems.

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 C # code:

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 HttpStringContent in your case) - you must pass Content-Type to that point. You're using TryAddWithoutValidation to set 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 recognized by Controller , and this causes the 415 Unsupported Media Type response you are receiving.

Another alternative is, after the creation of HttpStringContent , you define your Content-Type:

HttpStringContent content = ...;
content.Headers.ContentType = new HttpMediaTypeHeaderValue("application/json");
    
14.10.2015 / 23:12