How IIS/Owin converts IHttpActionResult to application/json down the pipeline? - c#

I am exploring how IIS/Owin pipeline works. I am trying to find the library/method used by IIS/Owin down the pipeline to convert IHttpActionResult (returned from controller) into the correct content-type like application/json as present in the request.
Controller -
[Route("")]
public IHttpActionResult Get()
{
IEnumerable<Product> productList = ProductService.GetAllProducts();
if (!productList.Any())
return Ok();
return Json(productList, new JsonSerializerSettings
{
ContractResolver = new WebContractResolver(),
Converters = new List<JsonConverter> { new TrimStringDataConverter() }
});
}
Data received by API consumer -
[
{
"code": "prod101",
"title": "LAPTOP"
},
{
"code": "prod102",
"title": "MOBILE"
}
]
When this conversion from IHttpActionResult to application/json takes place ?

IHttpActionResult has a method ExecuteAsync which returns HttpResponseMessage
public interface IHttpActionResult
{
Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken);
}
When you use Json() in your controller it will create a new JsonResult(link)
And this is the method which create a HttpResponseMessage which is a json.(link)
public virtual Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
return Task.FromResult(Execute());
}
private HttpResponseMessage Execute()
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
try
{
ArraySegment<byte> segment = Serialize();
response.Content = new ByteArrayContent(segment.Array, segment.Offset, segment.Count);
MediaTypeHeaderValue contentType = new MediaTypeHeaderValue("application/json");
contentType.CharSet = _encoding.WebName;
response.Content.Headers.ContentType = contentType;
response.RequestMessage = _dependencies.Request;
}
catch
{
response.Dispose();
throw;
}
return response;
}
in Owin there is a HttpMessageHandlerAdapter class which has a SendResponseMessageAsync method that return HttpResponseMessage to client. Here's the source from github: HttpMessageHandlerAdapter
private Task SendResponseMessageAsync(HttpRequestMessage request, HttpResponseMessage response,
IOwinResponse owinResponse, CancellationToken cancellationToken)
{
owinResponse.StatusCode = (int)response.StatusCode;
owinResponse.ReasonPhrase = response.ReasonPhrase;
// Copy non-content headers
IDictionary<string, string[]> responseHeaders = owinResponse.Headers;
foreach (KeyValuePair<string, IEnumerable<string>> header in response.Headers)
{
responseHeaders[header.Key] = header.Value.AsArray();
}
HttpContent responseContent = response.Content;
if (responseContent == null)
{
SetHeadersForEmptyResponse(responseHeaders);
return TaskHelpers.Completed();
}
else
{
// Copy content headers
foreach (KeyValuePair<string, IEnumerable<string>> contentHeader in responseContent.Headers)
{
responseHeaders[contentHeader.Key] = contentHeader.Value.AsArray();
}
// Copy body
return SendResponseContentAsync(request, response, owinResponse, cancellationToken);
}
}

Related

How to return an html document from WebApi 2 action using IHttpActionResult interface?

I need to build an html document and return it in my web api. All available answers on the net and the forum suggest using HttpResponseMessage. I would like to achieve this by IHttpActionResult. Below is what I have thus far:
[ResponseType(typeof(HttpResponseMessage))]
public async Task<IHttpActionResult> GetNotesViewModels()
{
var note = await GetHtmlText();
var response = new HttpResponseMessage();
response.Content = new StringContent(note);
response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/html");
return Ok(ResponseMessage(response));
}
I am not receiving what I would like. What is missing here?
You could implement your own HtmlResult, like following (free-handed):
public class HtmlActionResult : IHttpActionResult
{
public HtmlActionResult (HttpRequestMessage request, string content)
{
Request = request;
Content= content;
}
public string Content { get; private set; }
public HttpRequestMessage Request { get; private set; }
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
return Task.FromResult(ExecuteResult());
}
public HttpResponseMessage ExecuteResult()
{
var response = new HttpResponseMessage();
if (!string.IsNullOrWhiteSpace(Content))
response.Content = new StringContent(Content, Encoding.UTF8, "text/html");
response.RequestMessage = Request;
return response;
}
}
And use it like this:
public async Task<IHttpActionResult> GetNotesViewModels()
{
var note = await GetHtmlText();
return new HtmlActionResult(Request, note);
}

webapi parameter is null

I am starting WebApi tutorial but I just faced a problem that parameter in action is a null.
Below is the tutorial code.
WebApi
[AllowAnonymous]
[Route("Register")]
public async Task<IHttpActionResult> Register(RegisterBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var raw = Request.Content.ReadAsStringAsync();
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
IdentityResult result = await UserManager.CreateAsync(user, Result.Password);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
return Ok();
}
WebApi Client
static string Register(string email, string password)
{
var Result = new RegisterBindingModel()
{
Email = email,
Password = password,
ConfirmPassword = password
};
using (var client = new HttpClient())
{
var response = client.PostAsJsonAsync(
"http://localhost:7399/api/Account/Register",
Result).Result;
return response.StatusCode.ToString();
}
}
Register action receives http request but model is always null. So the raw variable shows like this
{"Email":"test#gmail.com","Password":"Test#123","ConfirmPassword":"Test#123"}
But when I tried sending http request using Postman it worked. As request body was read for model binding. The raw variable was empty. I don't know what's wrong with my client. I followed exactly tutorial code. Should I specify content-type?
make variable name same i.e. in Register method change var Result to var model and have a try.
it should be frombody , i.e. get parameter value from post body
[HttpPost]
public async Task<IHttpActionResult> Register([FromBody]RegisterBindingModel model)
Add HttpPost And Use below Methods
[AllowAnonymous]
[HttpPost]
[Route("Register")]
public async Task<IHttpActionResult> Register(RegisterBindingModel model)
{
//your code
}
Use this Methods:-
public static async Task<HttpResponseMessage> PostAsJsonAsync(this HttpClient client,
string requestUri, object requestObject, Dictionary<string, string> requestHeaders = null,
int? timeoutMilliSeconds = null)
{
var jsonContent = JsonConvert.SerializeObject(requestObject);
var requestContent = new StringContent(jsonContent, Encoding.UTF8, "application/json");
return await SendAsync(client, requestUri, HttpMethod.Post,
requestContent, requestHeaders, timeoutMilliSeconds);
}
public static async Task<HttpResponseMessage> SendAsync(
this HttpClient client,
string requestUri, HttpMethod httpMethod, HttpContent requestContent = null,
Dictionary<string, string> requestHeaders = null, int? timeoutMilliSeconds = null)
{
var httpRequestMessage = new HttpRequestMessage
{
RequestUri = new Uri(requestUri),
Method = httpMethod,
Content = requestContent
};
if (requestHeaders != null)
{
foreach (var requestHeader in requestHeaders)
{
httpRequestMessage.Headers.Add(requestHeader.Key, requestHeader.Value);
}
}
if (timeoutMilliSeconds.HasValue)
{
var cts = new CancellationTokenSource();
cts.CancelAfter(new TimeSpan(0, 0, 0, 0, timeoutMilliSeconds.Value));
return await client.SendAsync(httpRequestMessage, cts.Token);
}
else
{
return await client.SendAsync(httpRequestMessage);
}
}

Web API 2.0 IHttpActionResult cache

I'm using this code to return an object content, but I would like to cache the response adding the Cache-Control headers.
[AllowAnonymous]
[Route("GetPublicContent")]
[HttpGet]
public IHttpActionResult GetPublicContent([FromUri]UpdateContentDto dto)
{
if (dto == null)
return BadRequest();
var content = _contentService.GetPublicContent(dto);
if (content == null)
return BadRequest();
return new Ok(content);
}
Just that! Thanks!!
Make a new class that inherits from OkNegotiatedContentResult<T>:
public class CachedOkResult<T> : OkNegotiatedContentResult<T>
{
public CachedOkResult(T content, TimeSpan howLong, ApiController controller) : base(content, controller)
{
HowLong = howLong;
}
public CachedOkResult(T content, IContentNegotiator contentNegotiator, HttpRequestMessage request, IEnumerable<MediaTypeFormatter> formatters)
: base(content, contentNegotiator, request, formatters) { }
public TimeSpan HowLong { get; private set; }
public override async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = await base.ExecuteAsync(cancellationToken);
response.Headers.CacheControl = new CacheControlHeaderValue() {
Public = false,
MaxAge = HowLong
};
return response;
}
}
To use this in your controller, just return a new instance of the CachedOkResult class:
public async Task<IHttpActionResult> GetSomething(string id)
{
var value = await GetAsyncResult(id);
// cache result for 60 seconds
return new CachedOkResult<string>(value, TimeSpan.FromSeconds(60), this);
}
The headers will come across the wire like this:
Cache-Control:max-age=60
Content-Length:551
Content-Type:application/json; charset=utf-8
... other headers snipped ...
You can set it like this
public HttpResponseMessage GetFoo(int id) {
var foo = _FooRepository.GetFoo(id);
var response = Request.CreateResponse(HttpStatusCode.OK, foo);
response.Headers.CacheControl = new CacheControlHeaderValue()
{
Public = true,
MaxAge = new TimeSpan(1, 0, 0, 0)
};
return response;
}
Update
Or try this question

Access Web API deserialization/serialization methods within controller body

Is there a way to deserialize/serialize JSON POST content/response within the body of a Web API controller in the same exact way that the controller would deserialize/serialize the JSON content/response had it been an argument/explicit type?
My motivation is that I am trying to build a "magic" API controller that can provide access to my service commands quickly if developers do not have enough time to build a strongly-typed action to access them. Say I have the following actions:
public Brownie Brownie(BrownieInputModel inputModel)
{
...
var brownie = brownieService.GetBrownie(inputModel);
return brownie;
}
public Pizza Pizza(PizzaInputModel inputModel)
{
...
var pizza = pizzaService.GetPizza(inputModel);
return pizza;
}
Would it be possible to do something like this (in psuedo-code):
public object FoodStuff(string methodName)
{
var inputModel = WebApi.Deserialize(Request.JsonContent); // <-- pseudo
var serviceMethod = GetServiceMethodFromMethodName(methodName);
var result = serviceMethod.Execute();
return WebApi.Serialize(result); // <-- pseudo
}
I think HttpRequestMessage and HttpResponseMessage classes can help you solve this problem. Using them you code can look like that:
public async Task<HttpResponseMessage> Post(
string methodName, HttpRequestMessage request)
{
HttpResponseMessage response;
switch (methodName)
{
case "brownie":
response = await HandleBrownieAsync(request);
break;
case "pizza":
response = await HandlePizzaAsync(request);
break;
default:
throw new NotSupportedException();
}
return response;
}
private async Task<HttpResponseMessage> HandleBrownieAsync(HttpRequestMessage request)
{
var brownie = await GetRequestContentAsync<Brownie>(request);
return await CreateJsonResponseAsync(brownie);
}
private async Task<HttpResponseMessage> HandlePizzaAsync(HttpRequestMessage request)
{
var pizza = await GetRequestContentAsync<Pizza>(request);
return await CreateJsonResponseAsync(pizza);
}
private async Task<T> GetRequestContentAsync<T>(HttpRequestMessage request)
{
var contentString = await request.Content.ReadAsStringAsync();
return await JsonConvert.DeserializeObjectAsync<T>(contentString);
}
private async Task<HttpResponseMessage> CreateJsonResponseAsync<T>(T content)
{
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(
await JsonConvert.SerializeObjectAsync(content),
Encoding.UTF8,
"application/json")
};
return response;
}
And if you use these simplified models:
public class Brownie
{
public bool HasNuts { get; set; }
}
public class Pizza
{
public string CheeseType { get; set; }
}
Then you can make POST requests:
URL: http://api.url?methodName=brownie,
Body: {"hasNuts":true}
or
URL: http://api.url?methodName=pizza,
Body: {"cheeseType":"Mozzarella"}

Validating Request Content-Type

I'm trying to validate the value of Content-Type in POST, PUT and PATCH requests, but the current code is only working when I forget the content-type clause or when I use a content-type like: "Content-Type: Foo".
When I send "Content-Type: text/css" I get this:
500 Internal Server Error
No MediaTypeFormatter is available to read an object of type 'MyClassDto' from content with media type 'text/css'.
This is my code:
public class ContentTypeFilter : IActionFilter
{
private readonly List<MediaTypeHeaderValue> _suport;
/// <summary />
public ContentTypeFilterAttribute()
{
_suport = new List<MediaTypeHeaderValue>();
foreach (var formatter in GlobalConfiguration.Configuration.Formatters.ToArray())
{
_suport.AddRange(formatter.SupportedMediaTypes);
}
}
public bool AllowMultiple { get { return false; } }
public Task<HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
var metodos = new List<string> { "POST", "PUT", "PATCH" };
if (actionContext.Request.Content != null)
{
if (metodos.Contains(actionContext.Request.Method.Method.ToUpperInvariant()))
{
MediaTypeHeaderValue contentType = actionContext.Request.Content.Headers.ContentType;
if (contentType == null || !_suport.Any(x => x.MediaType.Equals(contentType.MediaType)))
{
return CreateResponse(actionContext.Request, "Invalid Content-Type");
}
}
}
return continuation();
}
private static Task<HttpResponseMessage> CreateResponse(HttpRequestMessage request, string mensagem)
{
var tsc = new TaskCompletionSource<HttpResponseMessage>();
var response = request.CreateResponse(HttpStatusCode.UnsupportedMediaType);
response.ReasonPhrase = mensagem;
response.Content = new StringContent(mensagem);
tsc.SetResult(response);
return tsc.Task;
}
Is there another way to validate content-type and return error 415 if the content isn't XML or JSON?
I've found a good solution here.
With some changes to get what I want:
public class ContentTypeFilter : DelegatingHandler
{
private readonly List<MediaTypeHeaderValue> _suport;
/// <summary />
public ContentTypeFilter()
{
_suport = new List<MediaTypeHeaderValue>();
foreach (var formatter in GlobalConfiguration.Configuration.Formatters.ToArray())
{
_suport.AddRange(formatter.SupportedMediaTypes);
}
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var metodos = new List<string> { "POST", "PUT", "PATCH" };
if (request.Content != null)
{
if (metodos.Contains(request.Method.Method.ToUpperInvariant()))
{
MediaTypeHeaderValue contentType = request.Content.Headers.ContentType;
// Nas configurações não possui o Charset aceito.
if (contentType == null || !_suport.Any(x => x.MediaType.Equals(contentType.MediaType)))
{
return Task<HttpResponseMessage>.Factory.StartNew(() => CreateResponse(request, "Suported content-types: " + string.Join(", ", _suport.Select(x => x.ToString()))));
}
}
}
return base.SendAsync(request, cancellationToken);
}
private static HttpResponseMessage CreateResponse(HttpRequestMessage request, string mensagem)
{
var response = request.CreateResponse(HttpStatusCode.UnsupportedMediaType);
response.ReasonPhrase = mensagem;
response.Content = new StringContent(mensagem);
return response;
}
}

Categories