Content Negotiation in ASP.NET Web API - c#

I'm migrating a web service to ASP.NET Web Api 2, and hitting trouble at almost the first hurdle.
I want to do this:
public class SomeController : ApiController
{
[Route("some\url")]
public object Get()
{
return { Message = "Hello" };
}
}
And be able to ask the service for either "application/json" or "application/xml" (or indeed any other potential format, such as Message Pack), and get a serialized response. But it seems it only works for JSON.
I've read this and seen the documentation which states clearly that the framework cannot handle serialization of anonymous types into XML (seriously) and that the solution is to not use XML (seriously).
When I attempt to call this and request XML as response type, I get
The 'ObjectContent`1' type failed to serialize the response body for content type 'application/xml; charset=utf-8'.
I'm not removing support for clients wanting to ask for XML - but I genuinely can't find a work around for this - what can I do?
Edit
I've added these:
System.Web.Http.GlobalConfiguration.Configuration.Formatters.XmlFormatter.SupportedMediaTypes.Clear();
config.Formatters.Insert(0, new System.Net.Http.Formatting.JsonMediaTypeFormatter());
config.Formatters.Insert(0, new System.Net.Http.Formatting.XmlMediaTypeFormatter());
as per Dalorzo's answer, but it made no difference.
For clarification, the service works absolutely fine when I call it using an accept header of application/json, but bombs when I call it with an accept header of application/xml.

You have 3 options:
Create a class with a proper name and return the object instead of an anonymous type.
Or if you want to return the anonymous instance, you should remove XML formatter, because anonymous types are not supported by XML Formatter
Create your own formatter inheriting from MediaTypeFormatter or BufferedMediaTypeFormatter

You can do it by following code :
public HttpResponseMessage GetTestData()
{
var testdata = (from u in context.TestRepository.Get().ToList()
select
new Message
{
msgText = u.msgText
});
return ActionContext.Request.CreateResponse(HttpStatusCode.OK, testdata);
}

// This Code Is Used To Change Contents In Api
public HttpResponseMessage GetAllcarDetails( string formate)
{
CarModel ST = new CarModel();
CarModel ST1 = new CarModel();
List<CarModel> li = new List<CarModel>();
ST.CarName = "Maruti Waganor";
ST.CarPrice = 400000;
ST.CarModeles = "VXI";
ST.CarColor = "Brown";
ST1.CarName = "Maruti Swift";
ST1.CarPrice = 500000;
ST1.CarModeles = "VXI";
ST1.CarColor = "RED";
li.Add(ST);
li.Add(ST1);
// return li;
this.Request.Headers.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/xml"));
//For Json Use "application/json"
IContentNegotiator negotiator =
this.Configuration.Services.GetContentNegotiator();
ContentNegotiationResult result = negotiator.Negotiate(
typeof(List<CarModel>), this.Request, this.Configuration.Formatters);
if (result == null) {
var response = new HttpResponseMessage(HttpStatusCode.NotAcceptable);
throw new HttpResponseException(response);
}
return new HttpResponseMessage() {
Content = new ObjectContent<List<CarModel>>(
li, // What we are serializing
result.Formatter, // The media formatter
result.MediaType.MediaType // The MIME type
)
};
}

Please browse your API route on Chrome. Chrome, by default shows output in XML format. If that doesn't happen, it means that your service is preventing XML format using media formatting.
And in that case, you should search your WebApiConfig. If nothing is present there, add this file to your project
using System.Net.Http.Formatting;
using System.Collections.Generic;
using System.Net.Http;
using System;
using System.Linq;
using System.Net.Http.Headers;
namespace ExampleApp.Infrastructure
{
public class CustomNegotiator : DefaultContentNegotiator
{
public override ContentNegotiationResult Negotiate(Type type, HttpRequestMessage request, IEnumerable<MediaTypeFormatter> formatters)
{
if(request.Headers.UserAgent.Where(x=>x.Product!=null&& x.Product.Name.ToLower().Equals("chrome")).Count() > 0)
{
return new ContentNegotiationResult(new JsonMediaTypeFormatter(), new MediaTypeHeaderValue("application/xml"));
}
else
{
return base.Negotiate(type, request, formatters);
}
}
}
}
and, in WebApiConfig.cs, add:
config.Services.Replace(typeof(IContentNegotiator), new CustomNegotiator());

Related

REST Api Put Request Problem (RestSharp) to Netapp REST Api. Getting always "Invalid JSON input"

In my C# app I access a NetApp REST API. I am using RestSharp to access the API. No problem with Get requests. However when I try a Put request I alwas get: "Invalid JSON input. Unexpected character in stream: r around 0:0."
Added: I'm stuck with RestSharp version 105.2.3 because of other dependencies. Its a huge application and moving to a newer version of RestSharp would need changes in several modules as well as moving to another .Net version. AddStringBody method would maybe help, but is not present in the version I am using.
The method called, prepares and sends the request, as well as checks the result.
internal bool CreateQtree(Pool pool, BaseStorageConf storageConf, RestClientCustom server) {
bool isOk = false;
// Prepare request query parameters.
var request = new RestRequest("storage/qtrees");
request.Method = Method.POST;
request.RequestFormat = DataFormat.Json;
request.AddParameter("return_records", "false");
request.AddParameter("return_timeout", 15);
// Prepare body.
PostCreateQTree CreateQTreePost = new PostCreateQTree();
CreateQTreePost.Name = pool.Name;
CreateQTreePost.SecurityStyle = "unix";
CreateQTreePost.Svm = new Svm();
CreateQTreePost.Svm.Name = storageConf.FileServer;
CreateQTreePost.Volume = new Volume();
CreateQTreePost.Volume.Name = storageConf.FileSystem;
// Assure that null values are not serialized.
JsonSerializerSettings setting = new JsonSerializerSettings();
setting.NullValueHandling = NullValueHandling.Ignore;
// Using this serializer makes sure that the JsonProperty are honored.
string body = JsonConvert.SerializeObject(CreateQTreePost, setting);
request.AddParameter("application/json", body, ParameterType.RequestBody);
// Execute request.
var response = server.Execute(request);
// Handle results.
if (response.StatusCode == HttpStatusCode.OK) {
// Creation of the qtree was successfull.
TheLogger.Instance.Error(string.Format("Qtree could be created. Pool: {0}", pool.Name));
isOk = true;
} else {
// There was an error, dump.
ResponseError ErrorResponse = JsonConvert.DeserializeObject<ResponseError>(response.Content);
TheLogger.Instance.Error(string.Format("Qtree could not be created. Pool: {0}, code: {1}, message: {2}", pool.Name, ErrorResponse.Error.Code, ErrorResponse.Error.Message));
}
return isOk;
}
The RestClientCustom is only to allow debugging, when working against the productive system.
using Logging;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace Devices.NetAppREST {
internal class RestClientCustom:RestClient {
private readonly bool mIsDebugMode;
internal RestClientCustom(string baseUrl, bool isDebugMode): base(baseUrl) {
mIsDebugMode = isDebugMode;
}
internal RestClientCustom(Uri baseUrl, bool isDebugMode) : base(baseUrl) {
mIsDebugMode = isDebugMode;
}
public IRestResponse Execute(RestRequest request) {
IRestResponse response;
TheLogger.Instance.Debug(string.Format("REST URI:\r\n{0}", base.BuildUri(request)));
TheLogger.Instance.Debug(string.Format("Parameters:\r\n{0}", ParameterToString(request)));
if (mIsDebugMode) {
response = new RestResponse();
response.StatusCode = HttpStatusCode.OK;
return response;
} else {
response = base.Execute(request);
}
TheLogger.Instance.Debug(string.Format("StatusCode:\r\n{0}", response.StatusCode));
TheLogger.Instance.Debug(string.Format("Content:\r\n{0}", response.Content));
TheLogger.Instance.Debug(string.Format("Headers:\r\n{0}", response.Headers));
TheLogger.Instance.Debug(string.Format("ResponseURI:\r\n{0}", response.ResponseUri));
TheLogger.Instance.Debug(string.Format("ErrorMessage:\r\n{0}", response.ErrorMessage));
return response;
}
internal String ParameterToString(RestRequest request) {
var sb = new StringBuilder();
foreach (var param in request.Parameters) {
sb.AppendFormat("Name: {0}, Value: {1}, Type: {2}\r\n", param.Name, param.Value, param.Type.ToString());
}
return sb.ToString();
}
}
}
The Post JSON is created with the following class.
using Newtonsoft.Json;
namespace Devices.NetAppREST {
public class PostCreateQTree {
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("security_style")]
public string SecurityStyle { get; set; }
[JsonProperty("svm")]
public Svm Svm { get; set; }
[JsonProperty("volume")]
public Volume Volume { get; set; }
}
}
I use a Newtonsoft.Json serializer in order to get the correct naming in the body JSON, as well als filter null values. An example looks as follows ...
{"name":"G-IPH-TestPoolCreate","security_style":"unix","svm":{"name":"sy-fs-301"},"volume":{"name":"g"}}
I can take this JSON string and post it via curl, powershell or test it on the target system directly and it works. However when I use the RestSharp implementation, I get above error.
I tried the normal serializer. But it does not generate the correct JSON (capitals). I added the JSON string directly as parameter in the code, no luck. Now I use the Newtonsoft.Json serializer. But I do not think the serialization is the problem. In the debugger I see the parameters and the URI passed in the request object. They seem ok.
I suppose I am missing something. A hint would be appreciated.
If found the solution ... In the method CreateQtree I used to add to query parameters.
request.AddParameter("return_records", "false");
request.AddParameter("return_timeout", 15);
When I add them more sprecific ...
request.AddParameter("return_records", "false", ParameterType.QueryString);
request.AddParameter("return_timeout", 15, ParameterType.QueryString);
It works. It seems the two parameters are not added correctly if I do not specify them as query parameters.

API works fine but parameters are not accepted c#

I have my route prefix here:
[RoutePrefix("api/Adresses")]
public class AdressesController : ApiController
{
My function here:
[Route("{codeEtudiant}")]
// GET: api/Adresses/1
public IEnumerable<Object> getAdresseEtu(Int32 code)
{
Where I call my api:
using (var client2 = new HttpClient())
{
string getasync = "http://localhost:11144/api/Adresses/" + etu.Code;
var response2 = await client.GetAsync(getasync);
var json2 = await response.Content.ReadAsStringAsync();
int cpt2 = -1;
foreach (object tmp2 in JsonConvert.DeserializeObject<List<Object>>(json2))
{
My string getasync returns: http://localhost:11144/api/Adresses/1
With these methods I can call any function in my api that does not have parameters but as soon I have one it doesn't respond and give me a response:
404 reason(not found)
The parameter names have to match. Currently you have the route parameter named codeEtudiant but the parameter of the method named code. Give them both the same name.
[Route("{codeEtudiant}")]
public IEnumerable<Object> getAdresseEtu(Int32 codeEtudiant)
See also Attribute Routing in ASP.NET Web API 2.
The error is because the Route Attribute must have the same name of the parameter, use
[Route("{code}")]

Is it possible to pass 2 types of objects to Restsharp?

We have scenario where external API returns either User XML or Error XML based on whether request succeed or failed.
At the moment I'm passing User POCO to the restsharp and works fine. But if it fails, this object is NULL. And we won't know why it failed unless we parse the Error XML manually.
Is there a way to workaround this?
e.g.
var restClient = new RestClient(baseURL);
var request = new RestRequest(uri);
request.Method = Method.POST;
var response = restClient.Execute<User>(request);
On execution of above method the API can return Error xml object. How do I get Error object on fail and User on success?
This is possible, although the code is a little ugly. RestSharp allows you to specify your own XML deserializer, so we'll need to do that in order to make this work.
First, though, you need a data type that can store either an Error or a User (I made it generic so it works for more than just Users):
public class Result<T>
{
public T Data { get; set; }
public Error Error { get; set; }
}
So the idea is, now when you execute the request, you ask RestSharp for a Result<User> instead of just a User, i.e.:
var result = client.Execute<Result<User>>(request);
Now here's the magic required to deserialize as either an Error or a User. It's a custom deserializer that inherits from RestSharp's XmlDeserializer. Warning: this code is not tested at all, but it can hopefully point you in the right direction.
public class XmlResultDeserializer : XmlDeserializer
{
public override T Deserialize<T>(IRestResponse response)
{
if (!typeof(T).IsGenericType || typeof(T).GetGenericTypeDefinition() != typeof(Result<>))
return base.Deserialize<T>(response);
// Determine whether the response contains an error or normal data.
var doc = XDocument.Parse(response.Content);
var result = Activator.CreateInstance<T>();
if (doc.Root != null && doc.Root.Name == "Error")
{
// It's an error
var error = base.Deserialize<Error>(response);
var errorProperty = result.GetType().GetProperty("Error");
errorProperty.SetValue(result, error);
}
else
{
// It's just normal data
var innerType = typeof(T).GetGenericArguments()[0];
var deserializeMethod = typeof(XmlDeserializer)
.GetMethod("Deserialize", new[] { typeof(IRestResponse) })
.MakeGenericMethod(innerType);
var data = deserializeMethod.Invoke(this, new object[] { response });
var dataProperty = result.GetType().GetProperty("Data");
dataProperty.SetValue(result, data);
}
return result;
}
}
Then you would wire it all up like this:
var restClient = new RestClient(baseURL);
client.AddHandler("application/xml", new XmlResultDeserializer());
var request = new RestRequest(uri);
request.Method = Method.POST;
var result = restClient.Execute<Result<User>>(request);
if (response.Data.Data != null)
{
var user = response.Data.Data;
// Do something with the user...
}
else if (response.Data.Error != null)
{
var error = response.Data.Error;
// Handle error...
}

Amazon Product advertising c# api

Hi guys I'm having trouble fetching products from amazon web api.
I have used this code from the internet, adding all the neccessary references. I tried adding a view and chose itemsearchresponce as the model class but it does not display the product, I get the following error:
Unable to generate a temporary class (result=1).
error CS0029: Cannot implicitly convert type 'AmazonProduct.com.amazon.webservices.ImageSet' to
'AmazonProduct.com.amazon.webservices.ImageSet[]'
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using AmazonProduct.com.amazon.webservices;
namespace Forest.Controllers
{
public class AmazonController : Controller
{
private AmazonProduct.com.amazon.webservices.AWSECommerceService _Products;
public AmazonController()
{
_Products = new AmazonProduct.com.amazon.webservices.AWSECommerceService();
}
[HttpGet]
public ActionResult listProducts()
{
var searchIndex = "Shoes";
var keywords = "jordan";
// Create an ItemSearch wrapper
ItemSearch search = new ItemSearch();
search.AssociateTag = "[Your Associate ID]";
search.AWSAccessKeyId = "MyKey";
// search.Version= "2011-08-01";
// Create a request object
ItemSearchRequest request = new ItemSearchRequest();
// Fill the request object with request parameters
request.ResponseGroup = new string[] { "ItemAttributes" };
// Set SearchIndex and Keywords
request.SearchIndex = searchIndex;
request.Keywords = keywords;
// Set the request on the search wrapper
search.Request = new ItemSearchRequest[] { request };
ItemSearchResponse response = _Products.ItemSearch(search);
return View(response);
}
}
}
Go to the generated proxy and replace ImageSet[][] with ImageSet[].
Also take a look at Amazon Product Advertising API C# if you already haven't.

How to force ASP.NET Web API to return JSON or XML data based on my input?

I try to get the output XML or JSON data based on my input. I used the below WEB API code but not able to exact output.
public string Get(int id)
{
if (GlobalConfiguration.Configuration.Formatters.XmlFormatter == null)
{
GlobalConfiguration.Configuration.Formatters.Add(GlobalConfiguration.Configuration.Formatters.XmlFormatter);
}
if (GlobalConfiguration.Configuration.Formatters.JsonFormatter == null)
{
GlobalConfiguration.Configuration.Formatters.Add(GlobalConfiguration.Configuration.Formatters.JsonFormatter);
}
if (id == 1)
{
GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.JsonFormatter);
GlobalConfiguration.Configuration.Formatters.XmlFormatter.UseXmlSerializer = true;
}
else
{
GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);
GlobalConfiguration.Configuration.Formatters.JsonFormatter.UseDataContractJsonSerializer = true;
}
return "value";
}
Add the below code app_start event in global.asax file. In API Url add the query string:
GlobalConfiguration.Configuration.Formatters.JsonFormatter.MediaTypeMappings.Add(
new QueryStringMapping("type", "json", new MediaTypeHeaderValue("application/json")));
GlobalConfiguration.Configuration.Formatters.XmlFormatter.MediaTypeMappings.Add(
new QueryStringMapping("type", "xml", new MediaTypeHeaderValue("application/xml")));
e.g.:
for xml : http://localhost:49533/api/?type=xml
for json: http://localhost:49533/api/?type=json
What you are trying to do will not work in a multi-threaded environment. You cannot add to and remove from the formatters collection on a per-request basis. Here is a better way of accomplishing what you want.
public HttpResponseMessage Get(int id)
{
Foo foo = new Foo();
var content = new ObjectContent<Foo>(foo,
((id == 1) ? Configuration.Formatters.XmlFormatter :
Configuration.Formatters.JsonFormatter));
return new HttpResponseMessage()
{
Content = content
};
}
Looked into this a bit more, and found your answer in another post:
public HttpResponseMessage Get(int id)
{
string content = "value";
if (id == 1)
{
return Request.CreateResponse<string>(HttpStatusCode.OK, content, Configuration.Formatters.JsonFormatter);
}
return Request.CreateResponse<string>(HttpStatusCode.OK, content, Configuration.Formatters.XmlFormatter);
}
It also works to force the accept headers. Great option if you aren't always returning HttpResponseMessage's. I.e
Request.Headers.Add("Accept", "text/json");
return Request.CreateResponse(HttpStatusCode.OK, yourobject);
or
Request.Headers.Add("Accept", "application/xml");
return new Rss20FeedFormatter(feed);
If your request specifies the mime type, for example application/json, then web api will format the response appropriately.
If you are attempting to debug your web api manually, use a tool like Fiddler 2 to specify the type.
This article describes the concept.
QueryStringMapping` is nice solution but I need a default value for type.
for xml : localhost:49533/api/?type=xml
for json: localhost:49533/api/
I solve that situation like that:
GlobalConfiguration.Configuration.Formatters.XmlFormatter.SupportedMediaTypes.Clear();
var jSettings = new JsonSerializerSettings();
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings = jSettings;
GlobalConfiguration.Configuration.Formatters.XmlFormatter.MediaTypeMappings.Add(new QueryStringMapping("xml", "true", new MediaTypeHeaderValue("application/xml")));
While the accepted answer by vijayjan15 seems the best way to go for your specific situation (that is, using the MediaTypeMappings), you could alternatively have two different methods, one that returns XML and one that returns JSON. To do that, you can instantiate a controller-specific HttpConfiguration (to avoid modifying the one in GlobalConfiguration.Configuration):
public MyReturnType GetMyTypeAsXml() {
Configuration = new HttpConfiguration();
Configuration.Formatters.Clear();
Configuration.Formatters.Add(new XmlMediaTypeFormatter());
return new MyReturnType();
}
public MyReturnType GetMyTypeAsJson() {
Configuration = new HttpConfiguration();
Configuration.Formatters.Clear();
Configuration.Formatters.Add(new JsonMediaTypeFormatter());
return new MyReturnType();
}
I'm not sure how much overhead there is in spinning up a new instance of HttpConfiguration (I suspect not a lot), but the new instance comes with the Formatters collection filled by default, which is why you have to clear it right after instantiating it. Note that it if you don't use Configuration = new HttpConfiguration(), and instead modify Configuration directly, it modifies the GlobalConfiguration.Configuration property (so, it would impact all your other WebApi methods - bad!).

Categories