Web API Return OAuth Token as XML - asp.net

Using the default Visual Studio 2013 Web API project template with individual user accounts, and posting to the /token endpoint with an Accept header of application/xml, the server still returns the response in JSON:
{"access_token":"...","token_type":"bearer","expires_in":1209599}
Is there a way to get the token back as XML?

According to RFC6749 the response format should be JSON and Microsoft implemented it accordingly. I found out that JSON formatting is implemented in Microsoft.Owin.Security.OAuth.OAuthAuthorizationServerHandler internal class with no means of extension.
I also encountered the need to have token response in XML.
The best solution I came up with was to implement HttpModule converting JSON to XML when stated in Accept header.
public class OAuthTokenXmlResponseHttpModule : IHttpModule
{
private static readonly string FilterKey = typeof(OAuthTokenXmlResponseHttpModule).Name + typeof(MemoryStreamFilter).Name;
public void Init(HttpApplication application)
{
application.BeginRequest += ApplicationOnBeginRequest;
application.EndRequest += ApplicationOnEndRequest;
}
private static void ApplicationOnBeginRequest(object sender, EventArgs eventArgs)
{
var application = (HttpApplication)sender;
if (ShouldConvertToXml(application.Context.Request) == false) return;
var filter = new MemoryStreamFilter(application.Response.Filter);
application.Response.Filter = filter;
application.Context.Items[FilterKey] = filter;
}
private static bool ShouldConvertToXml(HttpRequest request)
{
var isTokenPath = string.Equals("/token", request.Path, StringComparison.InvariantCultureIgnoreCase);
var header = request.Headers["Accept"];
return isTokenPath && (header == "text/xml" || header == "application/xml");
}
private static void ApplicationOnEndRequest(object sender, EventArgs eventArgs)
{
var context = ((HttpApplication) sender).Context;
var filter = context.Items[FilterKey] as MemoryStreamFilter;
if (filter == null) return;
var jsonResponse = filter.ToString();
var xDocument = JsonConvert.DeserializeXNode(jsonResponse, "oauth");
var xmlResponse = xDocument.ToString(SaveOptions.DisableFormatting);
WriteResponse(context.Response, xmlResponse);
}
private static void WriteResponse(HttpResponse response, string xmlResponse)
{
response.Clear();
response.ContentType = "application/xml;charset=UTF-8";
response.Write(xmlResponse);
}
public void Dispose()
{
}
}
public class MemoryStreamFilter : Stream
{
private readonly Stream _stream;
private readonly MemoryStream _memoryStream = new MemoryStream();
public MemoryStreamFilter(Stream stream)
{
_stream = stream;
}
public override void Flush()
{
_stream.Flush();
}
public override int Read(byte[] buffer, int offset, int count)
{
return _stream.Read(buffer, offset, count);
}
public override void Write(byte[] buffer, int offset, int count)
{
_memoryStream.Write(buffer, offset, count);
_stream.Write(buffer, offset, count);
}
public override string ToString()
{
return Encoding.UTF8.GetString(_memoryStream.ToArray());
}
#region Rest of the overrides
public override bool CanRead
{
get { throw new NotImplementedException(); }
}
public override bool CanSeek
{
get { throw new NotImplementedException(); }
}
public override bool CanWrite
{
get { throw new NotImplementedException(); }
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotImplementedException();
}
public override void SetLength(long value)
{
throw new NotImplementedException();
}
public override long Length
{
get { throw new NotImplementedException(); }
}
public override long Position
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
#endregion
}

Ok I had such a fun time trying to figure this out using OWIN I thought I would share my solution with the community, I borrowed some insight from other posts https://stackoverflow.com/a/26216511/1148288 and https://stackoverflow.com/a/29105880/1148288 along with the concepts Alexei describs in his post. Nothing fancy doing with implementation but I had a requirement for my STS to return an XML formatted response, I wanted to keep with the paradigm of honoring the Accept header, so my end point would examine that to determine if it needed to run the XML swap or not. This is what I am current using:
private void ConfigureXMLResponseSwap(IAppBuilder app)
{
app.Use(async (context, next) =>
{
if (context.Request != null &&
context.Request.Headers != null &&
context.Request.Headers.ContainsKey("Accept") &&
context.Request.Headers.Get("Accept").Contains("xml"))
{
//Set a reference to the original body stream
using (var stream = context.Response.Body)
{
//New up and set the response body as a memory stream which implements the ability to read and set length
using (var buffer = new MemoryStream())
{
context.Response.Body = buffer;
//Allow other middlewares to process
await next.Invoke();
//On the way out, reset the buffer and read the response body into a string
buffer.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(buffer))
{
string responsebody = await reader.ReadToEndAsync();
//Using our responsebody string, parse out the XML and add a declaration
var xmlVersion = JsonConvert.DeserializeXNode(responsebody, "oauth");
xmlVersion.Declaration = new XDeclaration("1.0", "UTF-8", "yes");
//Convert the XML to a byte array
var bytes = Encoding.UTF8.GetBytes(xmlVersion.Declaration + xmlVersion.ToString());
//Clear the buffer bits and write out our new byte array
buffer.SetLength(0);
buffer.Write(bytes, 0, bytes.Length);
buffer.Seek(0, SeekOrigin.Begin);
//Set the content length to the new buffer length and the type to an xml type
context.Response.ContentLength = buffer.Length;
context.Response.ContentType = "application/xml;charset=UTF-8";
//Copy our memory stream buffer to the output stream for the client application
await buffer.CopyToAsync(stream);
}
}
}
}
else
await next.Invoke();
});
}
Of course you would then wire this up during startup config like so:
public void Configuration(IAppBuilder app)
{
HttpConfiguration httpConfig = new HttpConfiguration();
//Highly recommend this is first...
ConfigureXMLResponseSwap(app);
...more config stuff...
}
Hope that helps any other lost souls that find there way to the this post seeking to do something like this!

take a look here i hope it can help how to set a Web API REST service to always return XML not JSON

Could you retry by doing the following steps:
In the WebApiConfig.Register(), specify
config.Formatters.XmlFormatter.UseXmlSerializer = true;
var supportedMediaTypes = config.Formatters.XmlFormatter.SupportedMediaTypes;
if (supportedMediaTypes.Any(it => it.MediaType.IndexOf("application/xml", StringComparison.InvariantCultureIgnoreCase) >= 0) ==false)
{
supportedMediaTypes.Insert(0,new MediaTypeHeaderValue("application/xml"));
}

I normally just remove the XmlFormatter altogether.
// Remove the XML formatter
config.Formatters.Remove(config.Formatters.XmlFormatter);
Add the line above in your WebApiConfig class...
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// Web API routes
config.MapHttpAttributeRoutes();
// Remove the XML formatter
config.Formatters.Remove(config.Formatters.XmlFormatter);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}

Related

How to send a zipped file to S3 bucket from Apex?

Folks,
I am trying to move data to s3 from Salesforce using apex class. I have been told by the data manager to send the data in zip/gzip format to the S3 bucket for storage cost savings.
I have simply tried to do a request.setCompressed(true); as I've read it compresses the body before sending it to the endpoint. Code below:
HttpRequest request = new HttpRequest();
request.setEndpoint('callout:'+DATA_NAMED_CRED+'/'+URL+'/'+generateUniqueTimeStampforSuffix());
request.setMethod('PUT');
request.setBody(JSON.serialize(data));
request.setCompressed(true);
request.setHeader('Content-Type','application/json');
But no matter what I always receive this:
<Error><Code>XAmzContentSHA256Mismatch</Code><Message>The provided 'x-amz-content-sha256' header does not match what was computed.</Message><ClientComputedContentSHA256>fd31b2b9115ef77e8076b896cb336d21d8f66947210ffcc9c4d1971b2be3bbbc</ClientComputedContentSHA256><S3ComputedContentSHA256>1e7f2115e60132afed9e61132aa41c3224c6e305ad9f820e6893364d7257ab8d</S3ComputedContentSHA256>
I have tried multiple headers too, like setting the content type to gzip/zip, etc.
Any pointers in the right direction would be appreciated.
I had a good amount of headaches attempting to do a similar thing. I feel your pain.
The following code has worked for us using lambda functions; you can try modifying it and see what happens.
public class AwsApiGateway {
// Things we need to know about the service. Set these values in init()
String host, payloadSha256;
String resource;
String service = 'execute-api';
String region;
public Url endpoint;
String accessKey;
String stage;
string secretKey;
HttpMethod method = HttpMethod.XGET;
// Remember to set "payload" here if you need to specify a body
// payload = Blob.valueOf('some-text-i-want-to-send');
// This method helps prevent leaking secret key,
// as it is never serialized
// Url endpoint;
// HttpMethod method;
Blob payload;
// Not used externally, so we hide these values
Blob signingKey;
DateTime requestTime;
Map<String, String> queryParams = new map<string,string>(), headerParams = new map<string,string>();
void init(){
if (payload == null) payload = Blob.valueOf('');
requestTime = DateTime.now();
createSigningKey(secretKey);
}
public AwsApiGateway(String resource){
this.stage = AWS_LAMBDA_STAGE
this.resource = '/' + stage + '/' + resource;
this.region = AWS_REGION;
this.endpoint = new Url(AWS_ENDPOINT);
this.accessKey = AWS_ACCESS_KEY;
this.secretKey = AWS_SECRET_KEY;
}
// Make sure we can't misspell methods
public enum HttpMethod { XGET, XPUT, XHEAD, XOPTIONS, XDELETE, XPOST }
public void setMethod (HttpMethod method){
this.method = method;
}
public void setPayload (string payload){
this.payload = Blob.valueOf(payload);
}
// Add a header
public void setHeader(String key, String value) {
headerParams.put(key.toLowerCase(), value);
}
// Add a query param
public void setQueryParam(String key, String value) {
queryParams.put(key.toLowerCase(), uriEncode(value));
}
// Create a canonical query string (used during signing)
String createCanonicalQueryString() {
String[] results = new String[0], keys = new List<String>(queryParams.keySet());
keys.sort();
for(String key: keys) {
results.add(key+'='+queryParams.get(key));
}
return String.join(results, '&');
}
// Create the canonical headers (used for signing)
String createCanonicalHeaders(String[] keys) {
keys.addAll(headerParams.keySet());
keys.sort();
String[] results = new String[0];
for(String key: keys) {
results.add(key+':'+headerParams.get(key));
}
return String.join(results, '\n')+'\n';
}
// Create the entire canonical request
String createCanonicalRequest(String[] headerKeys) {
return String.join(
new String[] {
method.name().removeStart('X'), // METHOD
new Url(endPoint, resource).getPath(), // RESOURCE
createCanonicalQueryString(), // CANONICAL QUERY STRING
createCanonicalHeaders(headerKeys), // CANONICAL HEADERS
String.join(headerKeys, ';'), // SIGNED HEADERS
payloadSha256 // SHA256 PAYLOAD
},
'\n'
);
}
// We have to replace ~ and " " correctly, or we'll break AWS on those two characters
string uriEncode(String value) {
return value==null? null: EncodingUtil.urlEncode(value, 'utf-8').replaceAll('%7E','~').replaceAll('\\+','%20');
}
// Create the entire string to sign
String createStringToSign(String[] signedHeaders) {
String result = createCanonicalRequest(signedHeaders);
return String.join(
new String[] {
'AWS4-HMAC-SHA256',
headerParams.get('date'),
String.join(new String[] { requestTime.formatGMT('yyyyMMdd'), region, service, 'aws4_request' },'/'),
EncodingUtil.convertToHex(Crypto.generateDigest('sha256', Blob.valueof(result)))
},
'\n'
);
}
// Create our signing key
void createSigningKey(String secretKey) {
signingKey = Crypto.generateMac('hmacSHA256', Blob.valueOf('aws4_request'),
Crypto.generateMac('hmacSHA256', Blob.valueOf(service),
Crypto.generateMac('hmacSHA256', Blob.valueOf(region),
Crypto.generateMac('hmacSHA256', Blob.valueOf(requestTime.formatGMT('yyyyMMdd')), Blob.valueOf('AWS4'+secretKey))
)
)
);
}
// Create all of the bits and pieces using all utility functions above
public HttpRequest createRequest() {
init();
payloadSha256 = EncodingUtil.convertToHex(Crypto.generateDigest('sha-256', payload));
setHeader('date', requestTime.formatGMT('yyyyMMdd\'T\'HHmmss\'Z\''));
if(host == null) {
host = endpoint.getHost();
}
setHeader('host', host);
HttpRequest request = new HttpRequest();
request.setMethod(method.name().removeStart('X'));
if(payload.size() > 0) {
setHeader('Content-Length', String.valueOf(payload.size()));
request.setBodyAsBlob(payload);
}
String finalEndpoint = new Url(endpoint, resource).toExternalForm(),
queryString = createCanonicalQueryString();
if(queryString != '') {
finalEndpoint += '?'+queryString;
}
request.setEndpoint(finalEndpoint);
for(String key: headerParams.keySet()) {
request.setHeader(key, headerParams.get(key));
}
String[] headerKeys = new String[0];
String stringToSign = createStringToSign(headerKeys);
request.setHeader(
'Authorization',
String.format(
'AWS4-HMAC-SHA256 Credential={0}, SignedHeaders={1},Signature={2}',
new String[] {
String.join(new String[] { accessKey, requestTime.formatGMT('yyyyMMdd'), region, service, 'aws4_request' },'/'),
String.join(headerKeys,';'), EncodingUtil.convertToHex(Crypto.generateMac('hmacSHA256', Blob.valueOf(stringToSign), signingKey))}
));
system.debug(json.serializePretty(request.getEndpoint()));
return request;
}
// Actually perform the request, and throw exception if response code is not valid
public HttpResponse sendRequest(Set<Integer> validCodes) {
HttpResponse response = new Http().send(createRequest());
if(!validCodes.contains(response.getStatusCode())) {
system.debug(json.deserializeUntyped(response.getBody()));
}
return response;
}
// Same as above, but assume that only 200 is valid
// This method exists because most of the time, 200 is what we expect
public HttpResponse sendRequest() {
return sendRequest(new Set<Integer> { 200 });
}
// TEST METHODS
public static string getEndpoint(string attribute){
AwsApiGateway api = new AwsApiGateway(attribute);
return api.createRequest().getEndpoint();
}
public static string getEndpoint(string attribute, map<string, string> params){
AwsApiGateway api = new AwsApiGateway(attribute);
for (string key: params.keySet()){
api.setQueryParam(key, params.get(key));
}
return api.createRequest().getEndpoint();
}
public class EndpointConfig {
string resource;
string attribute;
list<object> items;
map<string,string> params;
public EndpointConfig(string resource, string attribute, list<object> items){
this.items = items;
this.resource = resource;
this.attribute = attribute;
}
public EndpointConfig setQueryParams(map<string,string> parameters){
params = parameters;
return this;
}
public string endpoint(){
if (params == null){
return getEndpoint(resource);
} else return getEndpoint(resource + '/' + attribute, params);
}
public SingleRequestMock mockResponse(){
return new SingleRequestMock(200, 'OK', json.serialize(items), null);
}
}
}

EnableRewind and leaveOpen on StreamReader are not stopping the request from being disposed

I'm using ApplicationInsights and I want to add the request, and after that the response, to the logging properties.
To achieve this I am implementing my own ITelemetryInitializer. It looks exactly like this.
public class MyInitializer : ITelemetryInitializer
{
private readonly IHttpContextAccessor _httpContextAccessor;
public MyInitializer(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void Initialize(ITelemetry telemetry)
{
var requestTelemetry = telemetry as RequestTelemetry;
if (requestTelemetry == null || _httpContextAccessor?.HttpContext?.Request == null
|| requestTelemetry.Properties.ContainsKey("RequestBody"))
{
return;
}
var request = _httpContextAccessor?.HttpContext?.Request;
request?.EnableRewind();
if (request.Method.Equals(HttpMethod.Post.ToString(), StringComparison.InvariantCultureIgnoreCase)
|| request.Method.Equals(HttpMethod.Put.ToString(), StringComparison.InvariantCultureIgnoreCase))
{
using (var reader = new StreamReader(request.Body, Encoding.UTF8, true, 1024, true))
{
var requestBody = reader.ReadToEnd();
requestTelemetry.Properties.Add("RequestBody", requestBody);
}
}
}
}
In startup I've added this
services.AddHttpContextAccessor();
services.AddSingleton<ITelemetryInitializer, MyInitializer>();
services.AddApplicationInsightsTelemetry();
The error I get is:
ObjectDisposedException: Cannot access a disposed object.
Object name: FileBufferingReadStream.
Microsoft.AspNetCore.WebUtilities.FileBufferingReadStream.ThrowIfDisposed()
I've used .EnableRewind as well as instructing the StreamReader to leave the file open. Despite this my request is still null when it actually hits my controller, or even when it hits my initializer again for a second pass (setting the response information).
Any suggestions are welcome.
Additionally I tried adding a piece of middleware to ensure .EnableRewind was on for everything, but this did nothing. I'd prefer not to have to add any additional middleware since I'd like there to be no other dependencies.
app.Use(async (context, next) =>
{
context.Request.EnableRewind();
await next();
});
Thanks.
As always the solution ends up being a single line of code. I owe Mr Gunnar Peipman a thanks for his blog post Reading request body in ASP.NET Core.
The line:
request.Body.Seek(0, SeekOrigin.Begin);
The code
public class MyInitializer : ITelemetryInitializer
{
private readonly IHttpContextAccessor _httpContextAccessor;
public MyInitializer(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void Initialize(ITelemetry telemetry)
{
var requestTelemetry = telemetry as RequestTelemetry;
if (requestTelemetry == null || _httpContextAccessor?.HttpContext?.Request == null
|| requestTelemetry.Properties.ContainsKey("RequestBody"))
{
return;
}
var request = _httpContextAccessor?.HttpContext?.Request;
request?.EnableRewind();
if (request.Method.Equals(HttpMethod.Post.ToString(), StringComparison.InvariantCultureIgnoreCase)
|| request.Method.Equals(HttpMethod.Put.ToString(), StringComparison.InvariantCultureIgnoreCase))
{
using (var reader = new StreamReader(request.Body, Encoding.UTF8, true, 1024, true))
{
var requestBody = reader.ReadToEnd();
request.Body.Seek(0, SeekOrigin.Begin);
requestTelemetry.Properties.Add("RequestBody", requestBody);
}
}
}
}

Read Asp.Net Core Response body in ActionFilterAttribute

I'm using Asp.Net Core as a Rest Api Service.
I need access to request and response in ActionFilter. Actually, I found the request in OnActionExcecuted but I can't read the response result.
I'm trying to return value as follow:
[HttpGet]
[ProducesResponseType(typeof(ResponseType), (int)HttpStatusCode.OK)]
[Route("[action]")]
public async Task<IActionResult> Get(CancellationToken cancellationToken)
{
var model = await _responseServices.Get(cancellationToken);
return Ok(model);
}
And in ActionFilter OnExcecuted method as follow:
_request = context.HttpContext.Request.ReadAsString().Result;
_response = context.HttpContext.Response.ReadAsString().Result; //?
I'm trying to get the response in ReadAsString as an Extension method as follow:
public static async Task<string> ReadAsString(this HttpResponse response)
{
var initialBody = response.Body;
var buffer = new byte[Convert.ToInt32(response.ContentLength)];
await response.Body.ReadAsync(buffer, 0, buffer.Length);
var body = Encoding.UTF8.GetString(buffer);
response.Body = initialBody;
return body;
}
But, there is no result!
How I can get the response in OnActionExcecuted?
Thanks, everyone for taking the time to try and help explain
If you're logging for json result/ view result , you don't need to read the whole response stream. Simply serialize the context.Result:
public class MyFilterAttribute : ActionFilterAttribute
{
private ILogger<MyFilterAttribute> logger;
public MyFilterAttribute(ILogger<MyFilterAttribute> logger){
this.logger = logger;
}
public override void OnActionExecuted(ActionExecutedContext context)
{
var result = context.Result;
if (result is JsonResult json)
{
var x = json.Value;
var status = json.StatusCode;
this.logger.LogInformation(JsonConvert.SerializeObject(x));
}
if(result is ViewResult view){
// I think it's better to log ViewData instead of the finally rendered template string
var status = view.StatusCode;
var x = view.ViewData;
var name = view.ViewName;
this.logger.LogInformation(JsonConvert.SerializeObject(x));
}
else{
this.logger.LogInformation("...");
}
}
I know there is already an answer but I want to also add that the problem is the MVC pipeline has not populated the Response.Body when running an ActionFilter so you cannot access it. The Response.Body is populated by the MVC middleware.
If you want to read Response.Body then you need to create your own custom middleware to intercept the call when the Response object has been populated. There are numerous websites that can show you how to do this. One example is here.
As discussed in the other answer, if you want to do it in an ActionFilter you can use the context.Result to access the information.
For logging whole request and response in the ASP.NET Core filter pipeline you can use Result filter attribute
public class LogRequestResponseAttribute : TypeFilterAttribute
{
public LogRequestResponseAttribute() : base(typeof(LogRequestResponseImplementation)) { }
private class LogRequestResponseImplementation : IAsyncResultFilter
{
public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
var requestHeadersText = CommonLoggingTools.SerializeHeaders(context.HttpContext.Request.Headers);
Log.Information("requestHeaders: " + requestHeadersText);
var requestBodyText = await CommonLoggingTools.FormatRequestBody(context.HttpContext.Request);
Log.Information("requestBody: " + requestBodyText);
await next();
var responseHeadersText = CommonLoggingTools.SerializeHeaders(context.HttpContext.Response.Headers);
Log.Information("responseHeaders: " + responseHeadersText);
var responseBodyText = await CommonLoggingTools.FormatResponseBody(context.HttpContext.Response);
Log.Information("responseBody: " + responseBodyText);
}
}
}
In Startup.cs add
app.UseMiddleware<ResponseRewindMiddleware>();
services.AddScoped<LogRequestResponseAttribute>();
Somewhere add static class
public static class CommonLoggingTools
{
public static async Task<string> FormatRequestBody(HttpRequest request)
{
//This line allows us to set the reader for the request back at the beginning of its stream.
request.EnableRewind();
//We now need to read the request stream. First, we create a new byte[] with the same length as the request stream...
var buffer = new byte[Convert.ToInt32(request.ContentLength)];
//...Then we copy the entire request stream into the new buffer.
await request.Body.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
//We convert the byte[] into a string using UTF8 encoding...
var bodyAsText = Encoding.UTF8.GetString(buffer);
//..and finally, assign the read body back to the request body, which is allowed because of EnableRewind()
request.Body.Position = 0;
return $"{request.Scheme} {request.Host}{request.Path} {request.QueryString} {bodyAsText}";
}
public static async Task<string> FormatResponseBody(HttpResponse response)
{
//We need to read the response stream from the beginning...
response.Body.Seek(0, SeekOrigin.Begin);
//...and copy it into a string
string text = await new StreamReader(response.Body).ReadToEndAsync();
//We need to reset the reader for the response so that the client can read it.
response.Body.Seek(0, SeekOrigin.Begin);
response.Body.Position = 0;
//Return the string for the response, including the status code (e.g. 200, 404, 401, etc.)
return $"{response.StatusCode}: {text}";
}
public static string SerializeHeaders(IHeaderDictionary headers)
{
var dict = new Dictionary<string, string>();
foreach (var item in headers.ToList())
{
//if (item.Value != null)
//{
var header = string.Empty;
foreach (var value in item.Value)
{
header += value + " ";
}
// Trim the trailing space and add item to the dictionary
header = header.TrimEnd(" ".ToCharArray());
dict.Add(item.Key, header);
//}
}
return JsonConvert.SerializeObject(dict, Formatting.Indented);
}
}
public class ResponseRewindMiddleware {
private readonly RequestDelegate next;
public ResponseRewindMiddleware(RequestDelegate next) {
this.next = next;
}
public async Task Invoke(HttpContext context) {
Stream originalBody = context.Response.Body;
try {
using (var memStream = new MemoryStream()) {
context.Response.Body = memStream;
await next(context);
//memStream.Position = 0;
//string responseBody = new StreamReader(memStream).ReadToEnd();
memStream.Position = 0;
await memStream.CopyToAsync(originalBody);
}
} finally {
context.Response.Body = originalBody;
}
}
You can also do...
string response = "Hello";
if (result is ObjectResult objectResult)
{
var status = objectResult.StatusCode;
var value = objectResult.Value;
var stringResult = objectResult.ToString();
responce = (JsonConvert.SerializeObject(value));
}
I used this in a .net core app.
Hope it helps.

Async video streaming in ASP.Net Core Web Api is not working

I have used http://www.strathweb.com/2013/01/asynchronously-streaming-video-with-asp-net-web-api/ this technique before and worked perfect for async video streaming.
But for ASP.NET Core this way is not working as expected.
By Video streaming class is:
public class VideoStream
{
private readonly string _filename;
public VideoStream(string filename)
{
_filename = filename;
}
public async Task WriteToStream(Stream outputStream, HttpContent content, TransportContext context)
{
try
{
var buffer = new byte[65536];
using (var video = File.Open(_filename, FileMode.Open, FileAccess.Read))
{
var length = (int)video.Length;
var bytesRead = 1;
while (length > 0 && bytesRead > 0)
{
bytesRead = video.Read(buffer, 0, Math.Min(length, buffer.Length));
await outputStream.WriteAsync(buffer, 0, bytesRead);
length -= bytesRead;
}
}
}
catch (Exception)
{ return; }
finally
{
outputStream.Flush();
outputStream.Dispose();
}
}
}
and I have the following Action for video streaming requests:
[HttpGet]
[Route("[action]")]
public IActionResult GetVideo(int id)
{
var fileName = GetVideoFileName(id);
var video = new VideoStream(fileName);
var response = new HttpResponseMessage
{
Content = new PushStreamContent(video.WriteToStream, new MediaTypeHeaderValue("video/mp4"))
};
var objectResult = new ObjectResult(response);
objectResult.ContentTypes.Add(new Microsoft.Net.Http.Headers.MediaTypeHeaderValue("video/mp4"));
return objectResult;
}
Since by default Asp.Net Core doesn't have built-in Media Formatter for video/mp4 I have created the following custom Media Formatter
public class VideoOutputFormatter : IOutputFormatter
{
public bool CanWriteResult(OutputFormatterCanWriteContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
return true;
}
public async Task WriteAsync(OutputFormatterWriteContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
var response = context.HttpContext.Response;
response.ContentType = "video/mp4";
How to impelemnt ???
}
}
and added the following line to Startup.cs
services.AddMvc(options =>
{
options.OutputFormatters.Add(new VideoOutputFormatter());
});
It actually calls my custom formatter.
I doesn't know how to implement this custom media formatter for video/mp4.
Anyone can help me ?
Looking at the source code for Asp.NET Core really helped me find the answer to this one. They have a StreamOutputFormatter class that's really close to what you want to use. I only had to modify it to look for PushStreamContent and it worked like a charm.
Here's my complete VideoOutputFormatter:
public class VideoOutputFormatter : IOutputFormatter
{
public bool CanWriteResult(OutputFormatterCanWriteContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
if (context.Object is PushStreamContent)
return true;
return false;
}
public async Task WriteAsync(OutputFormatterWriteContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
using (var stream = ((PushStreamContent)context.Object))
{
var response = context.HttpContext.Response;
if (context.ContentType != null)
{
response.ContentType = context.ContentType.ToString();
}
await stream.CopyToAsync(response.Body);
}
}
}
Instead of wrapping the HttpResponseMessage in the ObjectResult in your controller, you'll want to just shove the PushStreamContent object into the ObjectResult instead. You still need to set the MediaTypeHeaderValue on the ObjectResult.

Will JsonContent get serialised multiple times using Json.NET in Web Api 2

If I have this controller:
public class SomeController : ApiController
{
public SomeInfoDto Get()
{
return new SomeInfoDto();
}
}
When I call /api/Some with a get request I will get JSON in the Content Body because JSON.NET has been kind enough to serialise it for me.
However, if I wanted to send a 500 HTTP code and some JSON I thought I could do this:
public HttpResponseMessage Get()
{
return new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content = // not sure what to put here
}
}
So I googled to find out what to put for the Content and found this resource
public class JsonContent : HttpContent
{
private readonly JToken _value;
public JsonContent(JToken value)
{
_value = value;
Headers.ContentType = new MediaTypeHeaderValue("application/json");
}
protected override Task SerializeToStreamAsync(Stream stream,
TransportContext context)
{
var jw = new JsonTextWriter(new StreamWriter(stream))
{
Formatting = Formatting.Indented
};
_value.WriteTo(jw);
jw.Flush();
return Task.FromResult<object>(null);
}
protected override bool TryComputeLength(out long length)
{
length = -1;
return false;
}
}
Which I can easily modify to fit my purposes.
However, my question is, if I get SerializeToStreamAsync to use JsonConvert.SerializeObject(_value) further down the Web Api pipeline will it be serialised again?
I have set the GlobalConfiguration.Configuration like this:
var formatters = GlobalConfiguration.Configuration.Formatters;
var jsonFormatter = formatters.JsonFormatter;
var settings = jsonFormatter.SerializerSettings;
settings.Formatting = Formatting.Indented;
settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
My Implementation for the SerializeToStreamAsync is this:
protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
using (var streamWriter = new StreamWriter(stream))
{
streamWriter.WriteAsync(JsonConvert.SerializeObject(this.value)).Wait();
streamWriter.FlushAsync().Wait();
}
return Task.FromResult<object>(null);
}
EDIT: Providing a concrete sample with formatter.
If you want to send JSON content with Http Status Code 500, you can use the extension method HttpRequestMessage.CreateResponse. There is no need for any advanced serialization and formatting. More info on that here.
https://msdn.microsoft.com/en-us/library/system.net.http.httprequestmessageextensions.createresponse%28v=vs.118%29.aspx
Request.CreateResponse(HttpStatusCode.InternalServerError, new string[] { "value1", "value2" }, new JsonMediaTypeFormatter())
(Or)
Request.CreateResponse(HttpStatusCode.InternalServerError, new string[] { "value1", "value2" }, 'application/json')

Resources