Set default Media Formatter for a WebAPI action - asp.net

I have implemented a custom media formatter and it works great when the client specifically requests "csv" format.
I have tested my api controller with this code:
HttpClient client = new HttpClient();
// Add the Accept header
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/csv"));
However, when I open the same URL from a web browser it returns JSON not CSV. This is probably due to standard ASP.NET WebAPI configuration that sets JSON as the default media formatter unless otherwise specified by the caller. I want this default behavior on every other web service I have but NOT on this single operation that returns CSV. I want the default media handler to be the CSV handler that I implemented. How do I configure the Controller's endpoint such that it returns CSV by default and only returns JSON/XML if requested by the client?

Which version of Web API are you using?
If you are using 5.0 version, you could use the new IHttpActionResult based logic like below:
public IHttpActionResult Get()
{
MyData someData = new MyData();
// creating a new list here as I would like CSVFormatter to come first. This way the DefaultContentNegotiator
// will behave as before where it can consider CSVFormatter to be the default one.
List<MediaTypeFormatter> respFormatters = new List<MediaTypeFormatter>();
respFormatters.Add(new MyCsvFormatter());
respFormatters.AddRange(Configuration.Formatters);
return new NegotiatedContentResult<MyData>(HttpStatusCode.OK, someData,
Configuration.Services.GetContentNegotiator(), Request, respFormatters);
}
If you are using 4.0 version of Web API, then you could the following:
public HttpResponseMessage Get()
{
MyData someData = new MyData();
HttpResponseMessage response = new HttpResponseMessage();
List<MediaTypeFormatter> respFormatters = new List<MediaTypeFormatter>();
respFormatters.Add(new MyCsvFormatter());
respFormatters.AddRange(Configuration.Formatters);
IContentNegotiator negotiator = Configuration.Services.GetContentNegotiator();
ContentNegotiationResult negotiationResult = negotiator.Negotiate(typeof(MyData), Request, respFormatters);
if (negotiationResult.Formatter == null)
{
response.StatusCode = HttpStatusCode.NotAcceptable;
return response;
}
response.Content = new ObjectContent<MyData>(someData, negotiationResult.Formatter, negotiationResult.MediaType);
return response;
}

Related

POSTing data while redirecting to a third-party URL using Response.Redirect()

In ASP.Net Core 2.0, how can I POST data while redirecting to a third-party URL using Response.Redirect()?
Note: I have to POST this data without using the query-string.
Response.Redirect triggers a GET request which means that the only option is using a query string.
Can you trigger the redirection from the client (if any) in order to make a POST request?
You must use object if you want post data without using query string.
[HttpPost]
public IActionResult Search([FromBody] CustomerSearchRequestApiModel request)
{
if (request == null)
{
return BadRequest();
}
return Ok(request);
}
It is impossible to use Response.Redirect() to send Post request.
For a workaround, you could try HttpClient to send Post request and then return the reponse to the web browser with ContentResult as text/html.
Here is a demo code:
public async Task<ContentResult> HtmlView()
{
using (var formDataContent = new MultipartFormDataContent())
{
HttpClient client = new HttpClient();
Article article = new Article { ArticleName = "AN" };
formDataContent.Add(new StringContent("AN", Encoding.UTF8, "application/json"), "ArticleName");
using (HttpClient httpClient = new HttpClient())
{
HttpResponseMessage response = await httpClient.PostAsync(#"https://localhost:44393/Articles/Create", formDataContent);
return new ContentResult
{
ContentType = "text/html",
StatusCode = (int)response.StatusCode,
Content = await response.Content.ReadAsStringAsync()
};
}
}
}
Note
Change the HttpClient part to send the right request to your own third party url with validate parameters.

dotnet core webapi calling .net webapi2

I am calling a .NET WebApi2 endpoint from a dotnet core webapi. When I debug into the .NET WebApi2 POST endpoint, my value is always null. Is this not possible to do?
When I call the GET endpoint with an ID, the ID is passed with no issues.
I have used both Postman and Fiddler to debug. Whenever I pass my JSON object from Postman to the .NET WebApi2 POST endpoint, my value is populated.
Beyond frustrated as this seems pretty simple. :,(
Updated to include code
dotnet core web api (calling from Postman)
[HttpPost]
public async Task PostAsync([FromBody] string value)
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var jsonObject = new JObject();
jsonObject.Add("text", "Rich");
var response = await client.PostAsJsonAsync("http://localhost:54732/api/Rich", jsonObject);
var responseResult = response.Content.ReadAsStringAsync().Result;
}
.NET WebApi2 (JObject is always null)
// POST: api/Rich
public void Post(JObject value)
{
}
This boils down to using JObject basically. For your older Web Api action, JObject works merely because you're posting JSON, and JObject is a dynamic. However, that is an entirely incorrect approach. You should be binding to a concrete class that represents the JSON being posted. That said, you may or may not be able to change anything there, and its not technically the source of your current issue.
The actual source is that you're attempting to send a JObject, which is not doing what you think it is. Again, JObject is a dynamic. It has accessors to parse and access the underlying JSON, but it does not actually expose the members of that JSON object directly. As a result, if you attempt to serialize it, you won't get anything usable from it. Passing it to PostAsJsonAsync causes it to be serialized.
What you actually need is something like:
var jsonObject = new { text = "Rich" };
Then, what you're passing to PostAsJsonAsync will be an anonymous object with actual members that can be serialized.
My "REAL" issue turned out to be Transfer-Encoding: chunked was being sent in the request header.
Here is my corrected code (dotnet core web api):
public async Task PostAsync([FromBody] JObject value)
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new
MediaTypeWithQualityHeaderValue("application/json"));
var jsonObject = new { variable1 = "Rich" };
var json = JsonConvert.SerializeObject(jsonObject);
var content = new StringContent(json, Encoding.UTF8, "application/json");
content.Headers.ContentLength = json.Length;
var response = await client.PostAsync("http://localhost:54732/api/Rich", content);
var responseResult = response.Content.ReadAsStringAsync().Result;
}
Here is my .NET WebApi2 code:
public IHttpActionResult Post([FromBody]RichTest value)
{
return Ok(value.variable1 + " done");
}
public class RichTest
{
public string variable1 { get; set; }
}
When I set the content.Headers.ContentLength, the Transfer-Encoding: chunked is removed. Now my code is working!!
I am still curious why the original PostAsJsonAsync does not work...

Can I disable model binding and use the raw request body in an action in dotnet core?

I want to setup an endpoint for testing webhooks from third parties. Their documentation is uniformly poor and there is no way ahead of time to tell exactly what I will be getting. What I've done is setup an ApiController that will just take a request and add a row to a table with what they are sending. This lets me at least verify they are calling the webhook, and to see the data so I can program to it.
// ANY api/webook/*
[Route("{*path}")]
public ActionResult Any(string path)
{
string method = Request.Method;
string name = "path";
string apiUrl = Request.Path;
string apiQuery = Request.QueryString.ToString();
string apiHeaders = JsonConvert.SerializeObject(Request.Headers);
string apiBody = null;
using (StreamReader reader = new StreamReader(Request.Body))
{
apiBody = reader.ReadToEnd();
}
Add(method, name, apiUrl, apiQuery, apiHeaders, apiBody);
return new JsonResult(new { }, JsonSettings.Default);
}
This works great, except for this new webhook I am usign that posts as form data so some middleware is reading the body and it ends up null in my code. Is there any way to disable the model processing so I can get at the request body?
You could actually use model binding to your advantage and skip all that stream reading, using the FromBody attribute. Try this:
[Route("{*path}")]
[HttpPost]
public ActionResult Any(string path, [FromBody] string apiBody)

A simple POST request to Web API not hitting the API at all

From my MVC application, I am trying to make a POST request to these sample end-points (actions) in an API controller named MembershipController:
[HttpPost]
public string GetFoo([FromBody]string foo)
{
return string.Concat("This is foo: ", foo);
}
[HttpPost]
public string GetBar([FromBody]int bar)
{
return string.Concat("This is bar: ", bar.ToString());
}
[HttpPost]
public IUser CreateNew([FromBody]NewUserAccountInfo newUserAccountInfo)
{
return new User();
}
Here's the client code:
var num = new WebAPIClient().PostAsXmlAsync<int, string>("api/membership/GetBar", 4).Result;
And here's the code for my WebAPIClient class:
public class WebAPIClient
{
private string _baseUri = null;
public WebAPIClient()
{
// TO DO: Make this configurable
_baseUri = "http://localhost:54488/";
}
public async Task<R> PostAsXmlAsync<T, R>(string uri, T value)
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(_baseUri);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
var requestUri = new Uri(client.BaseAddress, uri);
var response = await client.PostAsXmlAsync<T>(requestUri, value);
response.EnsureSuccessStatusCode();
var taskOfR = await response.Content.ReadAsAsync<R>();
return taskOfR;
}
}
}
I have the following default route defined for the Web API:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
UPDATE
My code breaks into the debugger until the time the PostAsXmlAsync method on the System.Net.HttpClient code is called. However, no request shows up in Fiddler.
However, if I try to compose a POST request in Fiddler or try to fire a GET request via the browser to one of the API end-points, the POST request composed via Fiddler tells me that I am not sending any data and that I must. The browser sent GET request rightly tells me that the action does not support a GET request.
It just seems like the System.Net.HttpClient class is not sending the POST request properly.
One of the most usual problems is that you don't use the appropriate attribute.
Take into account that there are attributes for ASP.NET MVC and ASP.NET Web API with the same name, but which live in different namespaces:
For Web API you must use the one in System.Web.Http
For MVC, the one in System.Web.MVc
This is a very very usual error, and it affects to allkind of things that exist for both MVC and Web API. So you must be very careful when using something which can exists in bith worlds (for example filters, attributes, or dependency injection registration).
I experienced a similar problem (may not be same one though). In my case, I hadn't given name attribute to the input element. I only figured that out when fiddler showed no post data being sent to the server (just like your case)
<input id="test" name="xyz" type="text" />
Adding the name attribute in the input tag fixed my problem.
However, there is one more thing to note. WebAPI does not put form data into parameters directly. Either you have to create an object with those properties and put that object in the parameter of the post controller. Or you could put no parameters at all like this:
[Route("name/add")]
public async Task Post()
{
if (!Request.Content.IsMimeMultipartContent())
{
return;
}
var provider = PostHelper.GetMultipartProvider();
var result = await Request.Content.ReadAsMultipartAsync(provider);
var clientId = result.FormData["xyz"];
...
Try changing the FromBody to FromUri.
If the parameter is a "simple" type, Web API tries to get the value from the URI. Simple types include the .NET primitive types (int, bool, double, and so forth), plus TimeSpan, DateTime, Guid, decimal, and string, plus any type with a type converter that can convert from a string.
For complex types, Web API tries to read the value from the message body, using a media-type formatter.
Remove FromBody at all and don't make any restrictions in passing parameters (it can be passed at this time either in uri, query string or form submissions (which is kinda a similar to query strings)
[HttpPost]
public string GetFoo(string foo){...}
It will be implicitly parsed and passed.

Returning binary file from controller in ASP.NET Web API

I'm working on a web service using ASP.NET MVC's new WebAPI that will serve up binary files, mostly .cab and .exe files.
The following controller method seems to work, meaning that it returns a file, but it's setting the content type to application/json:
public HttpResponseMessage<Stream> Post(string version, string environment, string filetype)
{
var path = #"C:\Temp\test.exe";
var stream = new FileStream(path, FileMode.Open);
return new HttpResponseMessage<Stream>(stream, new MediaTypeHeaderValue("application/octet-stream"));
}
Is there a better way to do this?
Try using a simple HttpResponseMessage with its Content property set to a StreamContent:
// using System.IO;
// using System.Net.Http;
// using System.Net.Http.Headers;
public HttpResponseMessage Post(string version, string environment,
string filetype)
{
var path = #"C:\Temp\test.exe";
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
result.Content = new StreamContent(stream);
result.Content.Headers.ContentType =
new MediaTypeHeaderValue("application/octet-stream");
return result;
}
A few things to note about the stream used:
You must not call stream.Dispose(), since Web API still needs to be able to access it when it processes the controller method's result to send data back to the client. Therefore, do not use a using (var stream = …) block. Web API will dispose the stream for you.
Make sure that the stream has its current position set to 0 (i.e. the beginning of the stream's data). In the above example, this is a given since you've only just opened the file. However, in other scenarios (such as when you first write some binary data to a MemoryStream), make sure to stream.Seek(0, SeekOrigin.Begin); or set stream.Position = 0;
With file streams, explicitly specifying FileAccess.Read permission can help prevent access rights issues on web servers; IIS application pool accounts are often given only read / list / execute access rights to the wwwroot.
For Web API 2, you can implement IHttpActionResult. Here's mine:
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
class FileResult : IHttpActionResult
{
private readonly string _filePath;
private readonly string _contentType;
public FileResult(string filePath, string contentType = null)
{
if (filePath == null) throw new ArgumentNullException("filePath");
_filePath = filePath;
_contentType = contentType;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StreamContent(File.OpenRead(_filePath))
};
var contentType = _contentType ?? MimeMapping.GetMimeMapping(Path.GetExtension(_filePath));
response.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
return Task.FromResult(response);
}
}
Then something like this in your controller:
[Route("Images/{*imagePath}")]
public IHttpActionResult GetImage(string imagePath)
{
var serverPath = Path.Combine(_rootPath, imagePath);
var fileInfo = new FileInfo(serverPath);
return !fileInfo.Exists
? (IHttpActionResult) NotFound()
: new FileResult(fileInfo.FullName);
}
And here's one way you can tell IIS to ignore requests with an extension so that the request will make it to the controller:
<!-- web.config -->
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
For those using .NET Core:
You can make use of the IActionResult interface in an API controller method, like so.
[HttpGet("GetReportData/{year}")]
public async Task<IActionResult> GetReportData(int year)
{
// Render Excel document in memory and return as Byte[]
Byte[] file = await this._reportDao.RenderReportAsExcel(year);
return File(file, "application/vnd.openxmlformats", "fileName.xlsx");
}
This example is simplified, but should get the point across. In .NET Core this process is so much simpler than in previous versions of .NET - i.e. no setting response type, content, headers, etc.
Also, of course the MIME type for the file and the extension will depend on individual needs.
Reference: SO Post Answer by #NKosi
While the suggested solution works fine, there is another way to return a byte array from the controller, with response stream properly formatted :
In the request, set header "Accept: application/octet-stream".
Server-side, add a media type formatter to support this mime type.
Unfortunately, WebApi does not include any formatter for "application/octet-stream". There is an implementation here on GitHub: BinaryMediaTypeFormatter (there are minor adaptations to make it work for webapi 2, method signatures changed).
You can add this formatter into your global config :
HttpConfiguration config;
// ...
config.Formatters.Add(new BinaryMediaTypeFormatter(false));
WebApi should now use BinaryMediaTypeFormatter if the request specifies the correct Accept header.
I prefer this solution because an action controller returning byte[] is more comfortable to test. Though, the other solution allows you more control if you want to return another content-type than "application/octet-stream" (for example "image/gif").
For anyone having the problem of the API being called more than once while downloading a fairly large file using the method in the accepted answer, please set response buffering to true
System.Web.HttpContext.Current.Response.Buffer = true;
This makes sure that the entire binary content is buffered on the server side before it is sent to the client. Otherwise you will see multiple request being sent to the controller and if you do not handle it properly, the file will become corrupt.
The overload that you're using sets the enumeration of serialization formatters. You need to specify the content type explicitly like:
httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
You could try
httpResponseMessage.Content.Headers.Add("Content-Type", "application/octet-stream");
You can try the following code snippet
httpResponseMessage.Content.Headers.Add("Content-Type", "application/octet-stream");
Hope it will work for you.

Resources