Posting Audio (byte array) to MVC.NET Web Api - c#

The Problem
I am trying to send audio recorded by my android device to a MVC.NET Web Api. I confirmed the connection by passing simple string parameters. However, when I try to pass the byte array generated from the audio, I get a 500 from the server every time. I have tried multiple configurations, but here's what I currently have:
MVC Web API
public class PostParms
{
public string AudioSource { get; set; }
public string ExtraInfo { get; set; }
}
public class MediaController : ApiController
{
[HttpPost]
public string Post([FromBody]PostParms parms)
{
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(parms.AudioSource);
return "success";
}
}
Android Code
public class WebServiceTask extends AsyncTask<String, Void, Long>
{
#Override
protected Long doInBackground(String... parms)
{
long totalsize = 0;
String filepath = parms[0];
byte[] fileByte = convertAudioFileToByte(new File(filepath));
//Tried base64 below with Convert.FromBase64 on the C# side
//String bytesstring = Base64.encodeToString(fileByte, Base64.DEFAULT);
String bytesstring = "";
try
{
String t = new String(fileByte,"UTF-8");
bytesstring = t;
} catch (UnsupportedEncodingException e1)
{
// TODO Auto-generated catch block
e1.printStackTrace();
}
URL url;
HttpURLConnection urlConnection = null;
try {
url = new URL("http://my.webservice.com:8185/api/media");
//setup for connection
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setUseCaches(false);
urlConnection.setRequestMethod("POST");
urlConnection.setDoOutput(true);
urlConnection.setRequestProperty("Content-Type","application/json");
urlConnection.setRequestProperty("Accept","application/json");
urlConnection.connect();
JSONObject json = new JSONObject();
json.put("AudioSource", bytesstring);
json.put("ExtraInfo", "none");
DataOutputStream output = new DataOutputStream(urlConnection.getOutputStream());
output.writeBytes(URLEncoder.encode(json.toString(),"UTF-8"));
output.flush();
output.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally {
urlConnection.disconnect();
}
return totalsize;
}
protected void onPreExecute(){
}
protected void onPostExecute(){
}
private byte[] convertAudioFileToByte(File file) {
byte[] bFile = new byte[(int) file.length()];
try {
// convert file into array of bytes
FileInputStream fileInputStream = new FileInputStream(file);
fileInputStream.read(bFile);
fileInputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
return bFile;
}
}
I can get a .NET application to work with the same API sending an audio stream, but not Android. Like I said, I've tried a number of configurations found on the internet with encoding and the output stream, but continue to get 500. I need help, as I am at a loss.
Thanks in advance

I used a ByteArrayEntity instead of a StringEntity.
// ByteArrayEntity
HttpPost request = new HttpPost(getString(R.string.server_url) + "SendMessage");
ByteArrayEntity be = new ByteArrayEntity(msg);
be.setContentType(new BasicHeader(HTTP.CONTENT_TYPE, "application/x-www-form-urlencoded"));
request.setEntity(be);
HttpResponse response = client.execute(request);
On the server side, I used this web API code to get the byte array:
[HttpPost]
public async Task<string> SendMessage()
{
byte[] arrBytes = await Request.Content.ReadAsByteArrayAsync();
return "";
}

I just posted a question on Stack Overflow at Uploading/Downloading Byte Arrays with AngularJS and ASP.NET Web API that is relevant (in part) to your question here. You might want to read that post to set the context for my response. It is about sending and receiving byte[]’s via Web API. I have one issue with the approach, but it has to do with the JavaScript client incorrectly handling the server response.
I would make the following changes to your Server-Side code as follows:
public class PostParms
{
public string ExtraInfo { get; set; }
}
public class MediaController : ApiController
{
[HttpPost]// byte[] is sent in body and parms sent in url
public string Post([FromBody] byte[] audioSource, PostParms parms)
{
byte[] bytes = audioSource;
return "success";
}
}
Read the post at http://byterot.blogspot.com/2012/04/aspnet-web-api-series-part-5.html
Use/add Byte Rot’s asynchronous version of the Media Formatter for byte[]’s.
I had to tweak the formatter as shown below for a newer version of ASP.NET.
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Threading.Tasks;
namespace YOUR_NAMESPACE
{
public class BinaryMediaTypeFormatter : MediaTypeFormatter
{
/****************** Asynchronous Version ************************/
private static Type _supportedType = typeof(byte[]);
private bool _isAsync = true;
public BinaryMediaTypeFormatter() : this(false){
}
public BinaryMediaTypeFormatter(bool isAsync) {
SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/octet-stream"));
IsAsync = isAsync;
}
public bool IsAsync {
get { return _isAsync; }
set { _isAsync = value; }
}
public override bool CanReadType(Type type){
return type == _supportedType;
}
public override bool CanWriteType(Type type){
return type == _supportedType;
}
public override Task<object> ReadFromStreamAsync(Type type, Stream stream,
HttpContent contentHeaders, IFormatterLogger formatterLogger){// Changed to HttpContent
Task<object> readTask = GetReadTask(stream);
if (_isAsync){
readTask.Start();
}
else{
readTask.RunSynchronously();
}
return readTask;
}
private Task<object> GetReadTask(Stream stream){
return new Task<object>(() =>{
var ms = new MemoryStream();
stream.CopyTo(ms);
return ms.ToArray();
});
}
private Task GetWriteTask(Stream stream, byte[] data){
return new Task(() => {
var ms = new MemoryStream(data);
ms.CopyTo(stream);
});
}
public override Task WriteToStreamAsync(Type type, object value, Stream stream,
HttpContent contentHeaders, TransportContext transportContext){ // Changed to HttpContent
if (value == null)
value = new byte[0];
Task writeTask = GetWriteTask(stream, (byte[])value);
if (_isAsync){
writeTask.Start();
}
else{
writeTask.RunSynchronously();
}
return writeTask;
}
}
}
I also had to add the following line to the config file.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// This line added to activate your binary formatter.
config.Formatters.Add(new BinaryMediaTypeFormatter());
/*********************************** Client-Code *************************************/
You will need to use 'Content-Type': 'application/octet-stream' on the client.
You can send the byte[] as binary, but it has been too long since I have coded in C# on the client.
I think that the line: output.writeBytes(URLEncoder.encode(json.toString(),"UTF-8")); will have to be changed as a minimum.
I will have a look and see if I can find some C# client code snippets that might be relevant.
Edit You might have a look at the C# client code of the following post. It may help you to tweak your code to work with the approach that I suggested.
How to Get byte array properly from an Web Api Method in C#?

Related

ASP.NET MVC: Why my custom ActionFilter changes my HTTP response code?

I've developed a custom action filter in order to use it for logging response of my web-service in ASP.NET MVC.
However I don't know why when I add this action filter to my method, HTTP status response of my controller changes to 500 and it returns the message: 500 Intenal Server Error. I put all logic inside try catch block but still problem persists.
Here is my custom ActionFilter:
public class LogActionFilter : System.Web.Http.Filters.ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
base.OnActionExecuted(actionExecutedContext);
try
{
Log("OnActionExecuting", actionExecutedContext);
}
catch (Exception)
{
}
}
private void Log(string methodName, HttpActionExecutedContext context)
{
try
{
string resopnseBody = getBodyFromResponse(context);
HttpResponseMessage response = context.Response;
var headers = response.Headers;
var content = response.Content;
var actionName = response.ToString();
var message = "";
message = String.Format("response:{0}", resopnseBody);
Debug.WriteLine(message, "");
}
catch (Exception e)
{
}
}
private string getBodyFromResponse(HttpActionExecutedContext context)
{
string data;
using (var stream = context.Response.Content.ReadAsStreamAsync().Result)
{
if (stream.CanSeek)
{
stream.Position = 0;
}
data = context.Response.Content.ReadAsStringAsync().Result;
}
return data;
}
}
Update:
Furthur investigating my code I found that calling getBodyFromResponse leads to this error. I myself suspect to part which I will try to read stream .Result twice however since I copied! this code from elsewhere I don't understand its logic clearly.
Update2:
Here is a sample method in my controller:
[LogActionFilter]
[System.Web.Http.HttpPost]
public async Task<IHttpActionResult> Test()
{
return Ok(new WebServiceResult { responseCode = 0, responseMessage = null });
}
Update 3:
replacing
resopnseBody = getBodyFromResponse(context);
with below line fixed issue but I don't know why!
resopnseBody = context.Response.Content.ReadAsStringAsync().Result;
I got it to run by removing some lines from getBodyFromResponseAsync
private string getBodyFromResponseAsync(HttpActionExecutedContext context)
{
return context.Response.Content.ReadAsStringAsync().Result;
}
I hope the result is what you need.

ASP.NET Core MVC 2: NameValueCollection instance is empty on the server side

This is client code:
public class Class1
{
static void Main()
{
string data = "Some big string...";
Work(data);
Console.WriteLine("Press any key for exit...");
Console.ReadKey();
}
async static void Work(string data)
{
var guid = Guid.NewGuid();
Uri uri = new Uri("http://localhost:61698/Home/Task/" + guid.ToString());
using (Stream s = StringToStream(data))
{
var report = await PostStream(uri, guid, s);
}
}
public static Stream StringToStream(string s)
{
MemoryStream stream = new MemoryStream();
StreamWriter writer = new StreamWriter(stream);
writer.Write(s);
writer.Flush();
stream.Position = 0;
return stream;
}
async static Task<byte[]> PostStream(Uri uri, Guid guid, Stream stream)
{
try
{
using (StreamReader sr = new StreamReader(stream))
{
string text = await sr.ReadToEndAsync();
var host = Dns.GetHostEntry(Dns.GetHostName());
IPAddress ip = null;
foreach (var item in host.AddressList)
{
if (item.AddressFamily == AddressFamily.InterNetwork)
{
ip = item;
}
}
var client = new WebClient();
var values = new NameValueCollection();
values.Add("Guid", guid.ToString());
values.Add("IP", ip.ToString());
values.Add("Data", text);
var bytes = await client.UploadValuesTaskAsync(uri, values);
return bytes;
}
}
catch(Exception err)
{
Console.Error.WriteLine(err.Message);
return null;
}
}
}
This is controller on the server side:
public class HomeController : Controller
{
[HttpGet]
public IActionResult Task(Guid id)
{
return Ok();
}
[HttpPost]
public IActionResult Task(NameValueCollection data)
{
if (ModelState.IsValid)
{
var count = data.Count; // 0
return Ok();
}
else
{
return BadRequest();
}
}
}
In debug mode I see that NameValueCollection on the server side is empty. Why does it happen & how can I fix it?
UploadValuesTaskAsync() sends request in form-urlencoded format:
Content-Type: application/x-www-form-urlencoded
Guid=660d9902-2293-43eb-906f-374adf77a9d6&IP=192.168.100.3&Data=Some+big+string...
Form data is bound to action parameters matched by name. So if your controller has following signature:
[HttpPost]
public IActionResult Task(Guid guid, string ip, string data)
guid, ip and data will be bound correctly to values from request.
If you want to change this behavior and deserialize request data to NameValueCollection, you could try marking action parameter with FromBody attribute:
[HttpPost]
public IActionResult Task([FromBody] NameValueCollection data)
Ufortunatelly, this will give you 415 Unsupported Media Type response. This happens because ASP.NET Core supports only JSON and XML media type formatters out of the box.
You have to add custom media type formatter to make this work. Quick search brought this project on Github. I haven't tried it but at least you could get the idea how to implement it.

Converting the content of HttpResponseMessage to object

My Question: How do I do this?
So, I hadn't touched anything .Net in about 6 years until this week. There's a lot that I've forgotten and even more that I never knew and while I love the idea of the async/await keywords, I'm having a slight problem implementing the following requirements for a client's API implementation:
The ServerAPI class has a method for each of the API methods, taking appropriate input parameters (e.g. the method Login takes in an id and a password, makes the API call and returns the result to the caller).
I want to abstract away the JSON so that my API methods return the actual object you're fetching (e.g. the Login method above returns a User object with your auth token, uid, etc.)
Some API methods return a 204 on success or no meaningful content (not meaningful in my usecase maybe I only care about success/failure), for these I'd like to return either a bool (true = success) or the status code.
I'd like to keep the async/await (or equivalent) design, because it seems to really work well so far.
For some methods, I might need to just return the HttpResponseMessage object and let the caller deal with it.
This is roughly what I have so far and I'm not sure how to make it compliant with the above OR whether I'm even doing this right. Any guidance is appreciated (flaming, however, is not).
// 200 (+User JSON) = success, otherwise APIError JSON
internal async Task<User> Login (string id, string password)
{
LoginPayload payload = new LoginPayload() { LoginId = id, Password = password};
var request = NewRequest(HttpMethod.Post, "login");
JsonPayload<LoginPayload>(payload, ref request);
return await Execute<Account>(request, false);
}
// 204: success, anything else failure
internal async Task<Boolean> LogOut ()
{
return await Execute<Boolean>(NewRequest(HttpMethod.Delete, "login"), true);
}
internal async Task<HttpResponseMessage> GetRawResponse ()
{
return await Execute<HttpResponseMessage>(NewRequest(HttpMethod.Get, "raw/something"), true);
}
internal async Task<Int32> GetMeStatusCode ()
{
return await Execute<Int32>(NewRequest(HttpMethod.Get, "some/intstatus"), true);
}
private async Task<RESULT> Execute<RESULT>(HttpRequestMessage request, bool authenticate)
{
if (authenticate)
AuthenticateRequest(ref request); // add auth token to request
var tcs = new TaskCompletionSource<RESULT>();
var response = await client.SendAsync(request);
// TODO: If the RESULT is just HTTPResponseMessage, the rest is unnecessary
if (response.IsSuccessStatusCode)
{
try
{
// TryParse needs to handle Boolean differently than other types
RESULT result = await TryParse<RESULT>(response);
tcs.SetResult(result);
}
catch (Exception e)
{
tcs.SetException(e);
}
}
else
{
try
{
APIError error = await TryParse<APIError>(response);
tcs.SetException(new APIException(error));
}
catch (Exception e)
{
tcs.SetException(new APIException("Unknown error"));
}
}
return tcs.Task.Result;
}
This is the APIError JSON structure (it's the status code + a custom error code).
{
"status": 404,
"code":216,
"msg":"User not found"
}
I would prefer to stay with System.Net, but that's mostly because I don't want to switch all my code over. If what I want is easier done in other ways then it's obviously worth the extra work.
Thanks.
Here is an example of how I've done it using MVC API 2 as backend. My backend returns a json result if the credentials are correct. UserCredentials class is the exact same model as the json result. You will have to use System.Net.Http.Formatting which can be found in the Microsoft.AspNet.WebApi.Client NugetPackage
public static async Task<UserCredentials> Login(string username, string password)
{
string baseAddress = "127.0.0.1/";
HttpClient client = new HttpClient();
var authorizationHeader = Convert.ToBase64String(Encoding.UTF8.GetBytes("xyz:secretKey"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authorizationHeader);
var form = new Dictionary<string, string>
{
{ "grant_type", "password" },
{ "username", username },
{ "password", password },
};
var Response = await client.PostAsync(baseAddress + "oauth/token", new FormUrlEncodedContent(form));
if (Response.StatusCode == HttpStatusCode.OK)
{
return await Response.Content.ReadAsAsync<UserCredentials>(new[] { new JsonMediaTypeFormatter() });
}
else
{
return null;
}
}
and you also need Newtonsoft.Json package.
public class UserCredentials
{
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("token_type")]
public string TokenType { get; set; }
[JsonProperty("expires_in")]
public int ExpiresIn { get; set; }
//more properties...
}
i would use a Deserializer.
HttpResponseMessage response = await client.GetAsync("your http here");
var responseString = await response.Content.ReadAsStringAsync();
[Your Class] object= JsonConvert.DeserializeObject<[Your Class]>(responseString.Body.ToString());
So, first to address the you need Newtonsoft.Json comments, I really haven't felt the need yet. I've found the built in support to work well so far (using the APIError Json in my original question:
[DataContract]
internal class APIError
{
[DataMember (Name = "status")]
public int StatusCode { get; set; }
[DataMember (Name = "code")]
public int ErrorCode { get; set; }
}
I have also defined a JsonHelper class to (de)serialize:
public class JsonHelper
{
public static T fromJson<T> (string json)
{
var bytes = Encoding.Unicode.GetBytes (json);
using (MemoryStream mst = new MemoryStream(bytes))
{
var serializer = new DataContractJsonSerializer (typeof (T));
return (T)serializer.ReadObject (mst);
}
}
public static string toJson (object instance)
{
using (MemoryStream mst = new MemoryStream())
{
var serializer = new DataContractJsonSerializer (instance.GetType());
serializer.WriteObject (mst, instance);
mst.Position = 0;
using (StreamReader r = new StreamReader(mst))
{
return r.ReadToEnd();
}
}
}
}
The above bits I already had working. As for a single method that would handle each request execution based on the type of result expected while it makes it easier to change how I handle things (like errors, etc), it also adds to the complexity and thus readability of my code. I ended up creating separate methods (all variants of the Execute method in the original question:
// execute and return response.StatusCode
private static async Task<HttpStatusCode> ExecuteForStatusCode (HttpRequestMessage request, bool authenticate = true)
// execute and return response without processing
private static async Task<HttpResponseMessage> ExecuteForRawResponse(HttpRequestMessage request, bool authenticate = true)
// execute and return response.IsSuccessStatusCode
private static async Task<Boolean> ExecuteForBoolean (HttpRequestMessage request, bool authenticate = true)
// execute and extract JSON payload from response content and convert to RESULT
private static async Task<RESULT> Execute<RESULT>(HttpRequestMessage request, bool authenticate = true)
I can move the unauthorized responses (which my current code isn't handling right now anyway) into a new method CheckResponse that will (for example) log the user out if a 401 is received.

Is it ok to use generic method for web api?

I am getting data from a web api by making httpclient calls from various MVC controllers. Because I have to do it many times, I made a generic method that I can reuse by just passing in the api url and the model return type. It works fine, but I am concerned I am loosing the oppurtunity to have different methods, like GetPeople, GetPersonById, etc. Is there a downside to what I am doing?
Utilities.cs:
public static T GetDataFromWebService<T>(T model, string svcEndPoint)
{
HttpClient client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = client.GetAsync(svcEndPoint).Result;
var result = response.Content.ReadAsAsync<T>().Result;
return result;
}
Controller:
string svc = appSettings.GetPeopleApiUrl;
var model = new List<Person>();
var people = Utilities.GetDataFromWebService <IEnumerable<Person>>(model, svc);
You can still have specialized methods such as GetPeople, GetPersonById by layering them on top:
PeopleModel GetPeople(...) {
return GetDataFromWebService<PeopleModel>(...);
}
No downsides, it is good that you have all boilerplate code in a shared utility method.
Well, there is definitely, better way of doing the overall implementation, but if I have to stick to the question, I would say any attempt of reducing coupling is a good step for future directions. In your situation, since you are abstracting away the responsibility of making service calls to a utility method, it would help you in the long run.
Though I would suggest that instead of having this stuffed together in Utility class you should make the connectivity it's own class, something like this
public delegate T ParseToObject<T>(string response);
public class ServiceConnector : IServiceConnector
{
public string LogoffUrl { get; set; }
public bool SupportRetry { get; set; }
private WebClient _client;
public ServiceConnector()
{
}
public T GetResponse<T>(string requestUrl, ParseToObject<T> parsingMethod)
{
string response = __getResponse(requestUrl);
return parsingMethod(response);
}
private string __getResponse(string requestUrl)
{
string serviceResponse = string.Empty;
try
{
__initializeWebClient();
Logger.Current.LogInfo(string.Format("Sending request with URL {0}", requestUrl));
serviceResponse = _client.DownloadString(requestUrl);
}
catch (Exception ex)
{
if (ex.Message != null)
{
Logger.Current.LogException(string.Format("Exception during OvidWS request {0} ", requestUrl), ex);
_client = null;
}
//Sample implementation only, you could throw the exception up based on your domain needs
}
return serviceResponse;
}
private void __initializeWebClient()
{
if (_client == null)
_client = new WebClient();
}
}
With this in place, tomorrow, let's say you want to add support to log off, support cookies, support credentials, support retries, this is the only place where you can be and comfortably make changes. Similarly if you want to use Webclient over something else, you can also do that better here.
Try with that helper:
public static class WebClientExtension
{
public static T DownloadSerializedJsonData<T>(string url) where T : new()
{
var contentType = ConfigurationManager.AppSettings["ContentType"];//content type in app config or web config
using (var webClient = new WebClient())
{
webClient.Headers.Add("Content-Type", contentType);
var jsonData = string.Empty;
try
{
jsonData = webClient.DownloadString(url);
}
catch (Exception ex)
{
throw ex;
}
return !string.IsNullOrEmpty(jsonData) ? JsonConvert.DeserializeObject<T>(jsonData) : new T();
}
}
public static T AuthorizationContentSerializedJsonData<T>(string url) where T : new()
{
string jsonData = null;
try
{
var httpRequest = (HttpWebRequest)WebRequest.Create(url);
//ClientBase.AuthorizeRequest(httpRequest, Authorization.AccessToken);
var response = httpRequest.GetResponse();
Stream receiveStream = response.GetResponseStream();
var readStream = new StreamReader(receiveStream, Encoding.UTF8);
jsonData = readStream.ReadToEnd();
response.Close();
}
catch (Exception ex)
{
throw ex;
}
return !string.IsNullOrEmpty(jsonData) ? JsonConvert.DeserializeObject<T>(jsonData) : new T();
}
}
App confing / Web config example for content type
<add key="ContentType" value="application/hal+json; charset=UTF-8" />

How to inspect MVC response stream using OWIN middleware component?

This question has been asked before in a few forms but I cannot get any of the answers to work, I'm losing my hair and unsure if the problem is just that the solutions were from 2 years ago and things have changed.
How can I safely intercept the Response stream in a custom Owin Middleware - I based my code on this, it looks like it should work, but it doesn't
OWIN OnSendingHeaders Callback - Reading Response Body - seems to be a different OWIN version, because method signature doesn't work
What I want to do is write an OMC that can inspect the response stream from MVC.
What I did (amongst several other attempts), is to add an OMC that sets context.Response.Body to a MemoryStream, so I can rewind it and inspect what was written by downstream components:
public async Task Invoke(IDictionary<string, object> env)
{
IOwinContext context = new OwinContext(env);
// Buffer the response
var stream = context.Response.Body;
var buffer = new MemoryStream();
context.Response.Body = buffer;
.......
What I find is that the MemoryStream is always empty, unless I write to it from another OMC. So it seems that downstream OMCs are using my MemoryStream, but MVC responses are not, as if the OWIN pipeline completes before the request goes to MVC, but that's not right is it?
Complete code:
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
app.Use(new ResponseExaminerMiddleware());
// Specify the stage for the OMC
//app.UseStageMarker(PipelineStage.Authenticate);
}
}
public class ResponseExaminerMiddleware
{
private AppFunc next;
public void Initialize(AppFunc next)
{
this.next = next;
}
public async Task Invoke(IDictionary<string, object> env)
{
IOwinContext context = new OwinContext(env);
// Buffer the response
var stream = context.Response.Body;
var buffer = new MemoryStream();
context.Response.Body = buffer;
await this.next(env);
buffer.Seek(0, SeekOrigin.Begin);
var reader = new StreamReader(buffer);
string responseBody = await reader.ReadToEndAsync();
// Now, you can access response body.
System.Diagnostics.Debug.WriteLine(responseBody);
// You need to do this so that the response we buffered
// is flushed out to the client application.
buffer.Seek(0, SeekOrigin.Begin);
await buffer.CopyToAsync(stream);
}
}
For what it's worth I also tried a suggestion where the response.Body stream is set to a Stream subclass, just so I could monitor what is written to the stream and bizarrely the Stream.Write method is called, but with an empty byte array, never any actual content...
MVC does not pass its request through OWIN pipeline. To capture MVC response we need to make custom response filter that captures response data
/// <summary>
/// Stream capturing the data going to another stream
/// </summary>
internal class OutputCaptureStream : Stream
{
private Stream InnerStream;
public MemoryStream CapturedData { get; private set; }
public OutputCaptureStream(Stream inner)
{
InnerStream = inner;
CapturedData = new MemoryStream();
}
public override bool CanRead
{
get { return InnerStream.CanRead; }
}
public override bool CanSeek
{
get { return InnerStream.CanSeek; }
}
public override bool CanWrite
{
get { return InnerStream.CanWrite; }
}
public override void Flush()
{
InnerStream.Flush();
}
public override long Length
{
get { return InnerStream.Length; }
}
public override long Position
{
get { return InnerStream.Position; }
set { CapturedData.Position = InnerStream.Position = value; }
}
public override int Read(byte[] buffer, int offset, int count)
{
return InnerStream.Read(buffer, offset, count);
}
public override long Seek(long offset, SeekOrigin origin)
{
CapturedData.Seek(offset, origin);
return InnerStream.Seek(offset, origin);
}
public override void SetLength(long value)
{
CapturedData.SetLength(value);
InnerStream.SetLength(value);
}
public override void Write(byte[] buffer, int offset, int count)
{
CapturedData.Write(buffer, offset, count);
InnerStream.Write(buffer, offset, count);
}
}
And then we make a logging middleware that can log both kinds of responses properly
public class LoggerMiddleware : OwinMiddleware
{
public LoggerMiddleware(OwinMiddleware next): base(next)
{
}
public async override Task Invoke(IOwinContext context)
{
//to intercept MVC responses, because they don't go through OWIN
HttpResponse httpResponse = HttpContext.Current.Response;
OutputCaptureStream outputCapture = new OutputCaptureStream(httpResponse.Filter);
httpResponse.Filter = outputCapture;
IOwinResponse owinResponse = context.Response;
//buffer the response stream in order to intercept downstream writes
Stream owinResponseStream = owinResponse.Body;
owinResponse.Body = new MemoryStream();
await Next.Invoke(context);
if (outputCapture.CapturedData.Length == 0) {
//response is formed by OWIN
//make sure the response we buffered is flushed to the client
owinResponse.Body.Position = 0;
await owinResponse.Body.CopyToAsync(owinResponseStream);
} else {
//response by MVC
//write captured data to response body as if it was written by OWIN
outputCapture.CapturedData.Position = 0;
outputCapture.CapturedData.CopyTo(owinResponse.Body);
}
LogResponse(owinResponse);
}
}

Categories

Resources