Can't set ISO-8859-1 as charset of web-api response - asp.net

I need to set charset ISO-8859-1 for the responses of my web api controllers, and not UTF-8.
The controller for testing returns a POCO object like this:
public class StudyCaseController : ApiController
{
...
// GET: api/StudyCase/5
public Study Get(int id)
{
...
}
}
I've tried to set <globalization requestEncoding="iso-8859-1" responseEncoding="iso-8859-1"/> in the Web.config, but testing with a fiddler request like this:
GET http://localhost:45988/api/StudyCase/1 HTTP/1.1
User-Agent: Fiddler
Host: localhost:45988
Accept: text/xml
I've got a response like this:
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: text/xml; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B? QzpcTUVESE9NRVxEZXNhcnJvbGxvQ1xQcm95ZWN0b3NcVmlld0NhcE1hblxWaWV3Q2FwTWFuXGFwaVxT dHVkeUNhc2VcMQ==?=
X-Powered-By: ASP.NET
Date: Tue, 24 Mar 2015 11:36:13 GMT
Content-Length: 1072
<?xml version="1.0"?>
... etc...
I've also tried to specify the charset at the request with Accept-Charset: iso-8859-1 but the same result.
For more info, i've tested it with IIS Express and IIS Server.
Thanks.

You can set supported encodings for formatters in HttpConfiguration class. 28591 is codepage for ISO-8859-1 (Latin 1).
config.Formatters.Add(new XmlMediaTypeFormatter());
config.Formatters[0].SupportedEncodings.Clear();
config.Formatters[0].SupportedEncodings.Add(Encoding.GetEncoding(28591));

Dealing with the problem my self, I found out that the problem was indeed the XML MediaTypeFormatter.
It does not support ISO-8859-1 and without the ability to change the server-side code I was forced to use another MediaTypeFormatter
https://www.nuget.org/packages/NetBike.Xml.Formatting/
or in nuget console
Install-Package NetBike.Xml.Formatting
This solved my problem (in a project using Web API 2).
Here is a demonstration of one way to set this using the HttpClient
private static HttpClient httpClient;
private static MediaTypeFormatter formatter;
private static List<MediaTypeFormatter> formatters;
public static void loadSettings()
{
//httpClient settings are set here
formatters = new List<MediaTypeFormatter>();
formatter = new NetBike.Xml.Formatting.NetBikeXmlMediaTypeFormatter();
formatters.Add(formatter);
}
public static async Task<HttpResponseMessage> GetContent(string requestMethod)
{
try
{
var returnValue = await httpClient.GetAsync(requestMethod);
return returnValue;
}
catch (Exception ex)
{
Debug.WriteLine("Communicator.GetXml: " + ex.Message);
}
return null;
}
public static async void testStuff()
{
var httpResponse = await GetContent("http://someDomain.com/someMethod");
MyModelObject myModel = await httpResponse.Content.ReadAsAsync<MyModelObject>(formatters);
}
What you want to do, is to set this as a default formatter for the whole project.
-EDIT-
Microsoft argues that this behavior is intended.
https://connect.microsoft.com/VisualStudio/feedback/details/958121
I have experienced issues with the NetBike Formatter, when formatting advanced XML objects, and even have a case where it writes BOM to the output.
Therefore I do not recommend it above the default MediaTypeSerializer, instead the solution falls back to the old "it depends"
That looks like this in code
List<MediaTypeFormatter> formatters = new List<MediaTypeFormatter>();
MediaTypeFormatter badencodingFormatter = new NetBike.Xml.Formatting.NetBikeXmlMediaTypeFormatter();
badencodingFormatter.SupportedEncodings.Clear();
badencodingFormatter.SupportedEncodings.Add(Encoding.GetEncoding("ISO-8859-1"));
MediaTypeFormatter defaultFormatter = new CustomXmlMediaTypeFormatter();
formatters.Add(badencodingFormatter);
formatters.Add(defaultFormatter);
This makes sure that anoying encoding is handled by NetBike, but only in those (hopefully rare) cases

Related

Adding Connection: keep-alive header is not returned to client in ASP.net

Short Version
I'm adding the response header:
Connection: keep-alive
but it's not in the resposne.
Long Version
I am trying to add a header to an HttpResponse in ASP.net:
public void ProcessRequest(HttpContext context)
{
context.Response.CacheControl = "no-cache";
context.Response.AppendHeader("Connection", "keep-alive");
context.Response.AppendHeader("AreTheseWorking", "yes");
context.Response.Flush();
}
And when the response comes back to the client (e.g. Chrome, Edge, Internet Explorer, Postman), the Connection header is missing:
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Transfer-Encoding: chunked
Expires: -1
Server: Microsoft-IIS/10.0
AreTheseWorking: yes
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Sat, 26 Feb 2022 16:29:17 GMT
What am I doing wrong?
Bonus Chatter
In addition to trying AppendHeader:
context.Response.AppendHeader("Connection", "keep-alive"); //preferred
I also tried AddHeader (which exists "for compatibility with earlier versions of ASP"):
context.Response.AddHeader("Connection", "keep-alive"); // legacy
I also tried Headers.Add:
context.Response.Headers.Add("Connection", "keep-alive"); //requires IIS 7 and integrated pipeline
What am i doing wrong?
Bonus: hypothetical motivation for the question
By default keep-alive is not allowed in ASP.net.
In order to allow it, you need to add an option to your web.config:
web.config:
<configuration>
<system.webServer>
<httpProtocol allowKeepAlive="true" />
</system.webServer>
</configuration>
This is especially important for Server-Send Events:
public void ProcessRequest(HttpContext context)
{
if (context.Request.AcceptTypes.Any("text/event-stream".Contains))
{
//Startup the HTTP Server Send Event - broadcasting values every 1 second.
SendSSE(context);
return;
}
}
private void SendSSE(HttpContext context)
{
//Don't worry about it.
string sessionId = context.Session.SessionID; //https://stackoverflow.com/a/1966562/12597
//Setup the response the way SSE needs to be
context.Response.ContentType = "text/event-stream";
context.Response.CacheControl = "no-cache";
context.Response.AppendHeader("Connection", "keep-alive");
context.Response.Flush();
while (context.Response.IsClientConnected)
{
System.Threading.Thread.Sleep(1000);
String data = DateTime.Now.ToString();
context.Response.Write("data: " + data + "\n\n");
context.Response.Flush();
}
}

Display content string from HttpResponseMessage

I have a post controller in an MVC app returning this response:
return new HttpResponseMessage(HttpStatusCode.Accepted)
{
Content = new StringContent("test")
};
When I hit the post URL with this code:
using (WebClient client = new WebClient())
{
string result = client.UploadString(url, content);
}
result contains this response:
StatusCode: 202, ReasonPhrase: 'Accepted', Version: 1.1, Content: System.Net.Http.StringContent, Headers: { Content-Type: text/plain; charset=utf-8 }
Why isn't "test" appearing after Content:?
Thanks!
You should not return HttpResponseMessage from ASP.NET MVC action. In this case you'll get messy response like this:
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/html; charset=utf-8
Vary: Accept-Encoding
Server: Microsoft-IIS/10.0
X-AspNetMvc-Version: 5.2
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcRHJvcGJveFxwcm9nXFN0YWNrT3ZlcmZsb3dcZG90TmV0XE12Y0FwcGxpY2F0aW9u?=
X-Powered-By: ASP.NET
Date: Sun, 04 Feb 2018 10:18:38 GMT
Content-Length: 154
StatusCode: 202, ReasonPhrase: 'Accepted', Version: 1.1, Content: System.Net.Http.StringContent, Headers:
{
Content-Type: text/plain; charset=utf-8
}
As you see, you actually get 200 HTTP response with HttpResponseMessage details in response body. This messy body content is what you deserialize into result variable.
ASP.NET MVC actions should return an instance of the class derived from System.Web.Mvc.ActionResult. Unfortunately, there is no built-in action result that allows setting both return status code and body content.
There is ContentResult class that allows to set return string content with status code of 200. There is also HttpStatusCodeResult that allows setting arbitrary status code but the response body will be empty.
But you could implement your custom action result with settable status code and response body. For simplicity, you could base it on ContentResult class. Here is a sample:
public class ContentResultEx : ContentResult
{
private readonly HttpStatusCode statusCode;
public ContentResultEx(HttpStatusCode statusCode, string message)
{
this.statusCode = statusCode;
Content = message;
}
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
base.ExecuteResult(context);
HttpResponseBase response = context.HttpContext.Response;
response.StatusCode = (int)statusCode;
}
}
The action would look like:
public ActionResult SomeAction()
{
return new ContentResultEx(HttpStatusCode.Accepted, "test");
}
Another possible fix is to change your controller from MVC to WEB API controller. To make this - just change base class of controller from System.Web.Mvc.Controller to System.Web.Http.ApiController. In this case you could return HttpResponseMessage as in your answer.
In both cases you will get correct HTTP response with 202 status code and string in the body:
HTTP/1.1 202 Accepted
Cache-Control: private
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/10.0
X-AspNetMvc-Version: 5.2
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcRHJvcGJveFxwcm9nXFN0YWNrT3ZlcmZsb3dcZG90TmV0XE12Y0FwcGxpY2F0aW9u?=
X-Powered-By: ASP.NET
Date: Sun, 04 Feb 2018 10:35:24 GMT
Content-Length: 4
test

Accept zip file using postman

I have created api project in .Net Core application. I have also created Method to accept HttpRequestMessage as parameter. Now I am trying to call my api method using Postman with including file as body parameter, but my api method is not calling.
Here is code
[Route("api/[controller]")]
public class ValuesController : Controller
{
[HttpPost]
public HttpResponseMessage PostFiles()
{
HttpResponseMessage result = null;
var httpRequest = HttpContext.Request;
return result;
}}
Here is Postman request data
POST /api/values/PostFiles HTTP/1.1
Host: localhost:64226
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Cache-Control: no-cache
Postman-Token: 6adcc652-a4ab-3714-cc5e-770bd214ac7a
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="data"; filename=""
Content-Type:
------WebKitFormBoundary7MA4YWxkTrZu0gW--
Here is the screen shot of my api call using postman.
Request body with file as parameter
Is there anything that I am missing?
Can you please help me out to call my web api with accepting file as parameter using Postman?
I think there's a problem with your route. Try this :
public class ValuesController : Controller
{
[Route("api/values/postfiles")]
[HttpPost]
public HttpResponseMessage PostFiles()
{
HttpResponseMessage result = null;
var files = Request.Form.Files;
return result;
}
}
Then try accessing the API at http://localhost:yourport/api/values/postfiles .
replace "yourport" in your port no.
You'll get the file in "fileStream" if the file is available.
In an ASP.NET Core application use IHostingEnvironment. Then you can call ContentRootPath and WebRootPath

Dreaded CORS issue with WebAPI and token

I swear this has happened so many times to me that I actually hate CORS.
I have just split my application in two so that one handles just the API side of things and the other handles the client side stuff.
I have done this before, so I knew that I needed to make sure CORS was enabled and allowed all, so I set this up in WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
// Enable CORS
config.EnableCors(new EnableCorsAttribute("*", "*", "*"));
// Web API configuration and services
var formatters = config.Formatters;
var jsonFormatter = formatters.JsonFormatter;
var serializerSettings = jsonFormatter.SerializerSettings;
// Remove XML formatting
formatters.Remove(config.Formatters.XmlFormatter);
jsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
// Configure our JSON output
serializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
serializerSettings.Formatting = Formatting.Indented;
serializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
serializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.None;
// Configure the API route
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
As you can see, my first line Enables the CORS, so it should work.
If I open my client application and query the API, it does indeed work (without the EnableCors I get the expected CORS error.
The problem is my /token is still getting a CORS error. Now I am aware that /token endpoint is not part of the WebAPI, so I created my own OAuthProvider (which I must point out is being used in other places just fine) and that looks like this:
public class OAuthProvider<TUser> : OAuthAuthorizationServerProvider
where TUser : class, IUser
{
private readonly string publicClientId;
private readonly UserService<TUser> userService;
public OAuthProvider(string publicClientId, UserService<TUser> userService)
{
if (publicClientId == null)
throw new ArgumentNullException("publicClientId");
if (userService == null)
throw new ArgumentNullException("userService");
this.publicClientId = publicClientId;
this.userService = userService;
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
var user = await this.userService.FindByUserNameAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
var oAuthIdentity = this.userService.CreateIdentity(user, context.Options.AuthenticationType);
var cookiesIdentity = this.userService.CreateIdentity(user, CookieAuthenticationDefaults.AuthenticationType);
var properties = CreateProperties(user.UserName);
var ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
context.AdditionalResponseParameters.Add(property.Key, property.Value);
return Task.FromResult<object>(null);
}
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
// Resource owner password credentials does not provide a client ID.
if (context.ClientId == null)
{
context.Validated();
}
return Task.FromResult<object>(null);
}
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
if (context.ClientId == this.publicClientId)
{
var redirectUri = new Uri(context.RedirectUri);
var expectedRootUri = new Uri(context.Request.Uri, redirectUri.PathAndQuery);
if (expectedRootUri.AbsoluteUri == redirectUri.AbsoluteUri)
context.Validated();
}
return Task.FromResult<object>(null);
}
public static AuthenticationProperties CreateProperties(string userName)
{
IDictionary<string, string> data = new Dictionary<string, string>
{
{ "userName", userName }
};
return new AuthenticationProperties(data);
}
}
As you can see, In the GrantResourceOwnerCredentials method I enable CORS access to everything again. This should work for all requests to /token but it doesn't.
When I try to login from my client application I get a CORS error.
Chrome shows this:
XMLHttpRequest cannot load http://localhost:62605/token. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:50098' is therefore not allowed access. The response had HTTP status code 400.
and Firefox shows this:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:62605/token. (Reason: CORS header 'Access-Control-Allow-Origin' missing).
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:62605/token. (Reason: CORS request failed).
For testing purposes, I decided to use fiddler to see if I could see anything else that might give me a clue as to what is happening. When I try to login, FIddler shows a response code as 400 and if I look at the raw response I can see the error:
{"error":"unsupported_grant_type"}
which is strange, because the data I am sending has not changed and was working fine before the split.
I decided to use the Composer on fiddler and replicated what I expect the POST request to look like.
When I Execute it, it works fine and I get a response code of 200.
Does anyone have any idea why this might be happening?
Update 1
Just for reference, the request from my client app looks like this:
OPTIONS http://localhost:62605/token HTTP/1.1
Host: localhost:62605
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Access-Control-Request-Method: POST
Origin: http://localhost:50098
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 Safari/537.36
Access-Control-Request-Headers: accept, authorization, content-type
Accept: */*
Referer: http://localhost:50098/account/signin
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
from the composer, it looks like this:
POST http://localhost:62605/token HTTP/1.1
User-Agent: Fiddler
Content-Type: 'application/x-www-form-urlencoded'
Host: localhost:62605
Content-Length: 67
grant_type=password&userName=foo&password=bar
Inside of
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
Get rid of this:
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
Currently you are doing the CORS thing twice. Once with .EnableCors and also again by writing the header in your token endpoint.
For what it's worth, in my OWIN startup class I have this at the very top:
app.UseCors(CorsOptions.AllowAll);
I also do NOT have it in my WebAPI register method, as I'm letting the OWIN startup handle it.
Since OAuthAuthorizationServer runs as an Owin middleware you must use the appropriate package Microsoft.Owin.Cors to enable CORS that works with any middleware in the pipeline. Keep in mind that WebApi & Mvc are just middleware themselves in regards to the owin pipeline.
So remove config.EnableCors(new EnableCorsAttribute("*", "*", "*")); from your WebApiConfig and add the following to your startup class.
Note app.UseCors it must precede the app.UseOAuthAuthorizationServer
app.UseCors(CorsOptions.AllowAll)
#r3plica
I had this problem, and it is like Bill said.
Put the line "app.UseCors" at the very top in Configuration method()
(before ConfigureOAuth(app) is enough)
Example:
public void Configuration(IAppBuilder app)
{
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
HttpConfiguration config = new HttpConfiguration();
ConfigureWebApi(config);
ConfigureOAuth(app);
app.UseWebApi(config);
}
We ran into a similar situation and ended up specifying some CORS data in the system.webServer node of the web.config in order to pass the preflight check. Your situation is slightly different than ours but maybe that would help you as well.
Here's what we added:
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Access-Control-Allow-Headers" value="Content-Type" />
<add name="Access-Control-Allow-Credentials" value="true" />
<add name="Access-Control-Allow-Methods" value="GET, POST, OPTIONS" />
</customHeaders>
</httpProtocol>
It turns out that there was no issue with CORS at all. I had an interceptor class that was modifying the headers incorrectly. I suggest for future reference, anyone else having these issues, if you have your CORS set up either in WebConfig.cs or your Startup class or even the web.config then you need to check that nothing is modifying your headers. If it is, disable it and test again.

Cannot post object from .net Client web-api web service

I have one .net client which tries to make http request to web api service
here is my Request:
public List<Category> GetCategories()
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:54558/");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
Task<string> response = client.GetStringAsync("api/CategoryApi/");
List<Category> lstCategory = JsonConvert.DeserializeObjectAsync<List<Category>>(response.Result).Result;
return lstCategory;
}
public void Create(Category category)
{
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var stringContent = new StringContent(category.ToString());
HttpResponseMessage responseMessage = client.PostAsync("api/CategoryApi/", stringContent).Result;
}
and in my webapi
public IEnumerable<Category> GetCategories()
{
return categoryRepository.data;
}
public string PostCategory(Category category)
{
categoryRepository.add(category);
return "MessageOk";
}
SO when I make request to my GetCategories action of the web-api everything is OK.
and no matter what I do it seems that .net application cannot find the Post action of the web-api and I never actually see entering in Postcategory method
as I have also put breakpoints here.
I only get the error
atusCode: 500, ReasonPhrase: 'Internal Server Error', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
Pragma: no-cache
X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNccG9zdGdyZXNcRGVza3RvcFxXZWJBcGlTZXJ2aWNlXFdlYkFwaVNlcnZpY2VcYXBpXENhdGVnb3J5QXBpXA==?=
Cache-Control: no-cache
Date: Sat, 13 Jun 2015 17:55:16 GMT
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Content-Length: 1022
Content-Type: application/json; charset=utf-8
Expires: -1
}}
What may be the issue. Thak you in advance
You are posting just a Category.ToString which if you didn't override ToString to be a Json or XML string, it will fail on the server side because there is no way to deserialize the content into a Category object. You should serialize the Category on the client before posting it. Also make sure your request headers include the proper Content-Type of application/json. By posting StringContent, the Content-Type won't be application/json. You are setting the Accept header, but that only describes the data coming back to your client, not the data you are posting. One last thing, I would not use the same HttpClient for both the get and the post request. Each method should use it's own HttpClient so you don't have any extra headers depending on the call.

Resources