I attempting to read the raw input stream in a ServiceStack Service. I have marked the DTO with IRequiresRequestStream, and the code to read executes, but the content always shows as blank.
Using debug mode in IE9, I can see the raw HttpRequest contains text within the POST as delivered.
Here is my code from a minimal test service intended only to show reading of the content and query:
[Route("/qtest")]
public class QueryTestRequest : IReturn<string>, IRequiresRequestStream
{
public Stream RequestStream { get; set; }
}
public class QueryTestService : Service
{
public string Any(QueryTestRequest request)
{
var r = new StringBuilder();
r.Append("<p>This is the query test service:");
r.AppendFormat("<p>Parameter value={0}", base.Request.QueryString["value"]);
var postStream = new StreamReader(request.RequestStream);
var postContent = postStream.ReadToEnd();
r.AppendFormat("<p>Raw Content={0}", postContent);
return r.ToString();
}
}
What am I missing here?
Yes I find that weird as well, but maybe it's me who doesn't understand the nature of the HttpRequestStream.
Anyway... I managed to get hold of the file using:
var stream = Request.Files[0].InputStream;
And then you can handle that stream.
It appears that more than one file can be uploaded, but I guess that would be difficult to wrap into a REST-framework.
Related
I am trying to output a CSV file using an endpoint on a service in ServiceStack, using the HttpResult class.
The CSV string itself is being constructed via StringWriter and CsvHelper.
If the content type is set to "text/plain", the text appears on the browser screen fine when the endpoint URL is hit. However, if it is set to "text/csv", a CSV file is generated, but the information inside it is not correct.
For example:
Expected output:
Header 1, Header 2, Header 3
Actual output:
H,e,a,d,e,r, ,1,,, ,H,e,a,d,e,r, ,2,,, ,H,e,a,d,e,r, 3,"
Is there something I'm possibly missing?
Also, on a side note, how do I set the file name for the file itself? It appears I have to use HttpHeaders.ContentDisposition, but when I tried to set it I got an error along the lines of having multiple header elements.
EDIT: Sorry forgot to include code snippet.
string response = string.Empty;
using (var writer = new StringWriter())
{
using (var csv = new CsvWriter(writer))
{
csv.WriteHeader<TestClass>();
foreach (var element in elements)
{
csv.WriteField(elements.header1);
csv.WriteField(elements.header2);
csv.WriteField(elements.header3);
csv.NextRecord();
}
}
//apparently double quotes can cause the rendered CSV to go wrong in some parts, so I added this as part of trying
response = writer.ToString().Replace("\"", "");
}
return new HttpResult(response)
{
StatusCode = HttpStatusCode.OK,
ContentType = "test/csv"
};
And the info on TestClass:
public class TestClass
{
public string Header1 { get; set; }
public string Header2 { get; set; }
public string Header3 { get; set; }
}
From your description your HttpResult File Response may be serialized by the built-in CSV Format.
If you're not using it you can remove it with:
Plugins.RemoveAll(x => x is CsvFormat);
Otherwise if you are using it, you can circumvent its serialization by writing the CSV file in your Services implementation, e.g:
public class MyCsv : IReturn<string> {}
public async Task Any(MyCsv request)
{
var file = base.VirtualFileSources.GetFile("path/to/my.csv");
if (file == null)
throw HttpError.NotFound("no csv here");
Response.ContentType = MimeTypes.Csv;
Response.AddHeader(HttpHeaders.ContentDisposition,
$"attachment; filename=\"{file.Name}\";");
using (var stream = file.OpenRead())
{
await stream.CopyToAsync(Response.OutputStream);
await Response.OutputStream.FlushAsync();
Response.EndRequest(skipHeaders:true);
}
}
Edit since you're returning a raw CSV string you can write it to the response with:
Response.ContentType = MimeTypes.Csv;
await Response.WriteAsync(response);
Response.EndRequest(skipHeaders:true);
I am using Web API to receive XML data and convert it to an Object. Which is working fine.
public void Post([FromBody] trackermessages model)
{
try
{
How do I get the RAW XML data? Is there a way to get the XML data as the Request begins or inside this action?
I tried this:
public void Post([FromBody] trackermessages model, [FromBody] string rawText)
{
try
{
But this is not allowed.
I also tried this:
public void Post([FromBody] trackermessages model)
{
try
{
var strean = new StreamReader(HttpContext.Current.Request.InputStream).ReadToEnd();
But I get the Exception:
This method or property is not supported after
HttpRequest.GetBufferlessInputStream has been invoked.
EDIT:
I am getting the Exception:
var stream = await Request.Content.ReadAsStreamAsync();
stream.Seek(0, System.IO.SeekOrigin.Begin); // On this Line
StreamReader reader = new StreamReader(stream);
string text = reader.ReadToEnd();
This is how I did it, because I had to read the RAW data then convert to Object:
public void Post(HttpRequestMessage request)
{
// Reading data as XML string to log to files - In case message structure is changed
var xmlDoc = new XmlDocument();
xmlDoc.Load(request.Content.ReadAsStreamAsync().Result);
var str = xmlDoc.InnerXml;
// Convert to model
var model = XMLHelper.FromXml<trackermessages>(str);
}
And the XMLHelper was copied from another question on stackoverflow: https://stackoverflow.com/a/3187539/1910735
Yes, you can get the raw XML. You do need to seek back to the start of the stream since it will have been read to the end when processing the Model.
public async void Post([FromBody]TestModel value)
{
var stream = await this.Request.Content.ReadAsStreamAsync();
stream.Seek(0, System.IO.SeekOrigin.Begin);
StreamReader reader = new StreamReader(stream);
string text = reader.ReadToEnd();
Console.Write(text);
}
The problem then is that your application is using GetBufferlessInputStream to read uploads without buffering them. While that is good for memory usage on the server, it means that after you've read the stream once it will no longer be available.
Your stream is being read like this when populating your model. By default GetBufferedInputStream is used which is why it works for me.
I suggest that you take the raw XML as input into the action and then manually deserialize into the model. Alternatively you can switch back to accepting posted data into a buffer.
You probably followed something like this to turn it on : https://blogs.msdn.microsoft.com/kiranchalla/2012/09/04/receiving-request-file-or-data-in-streamed-mode-at-a-web-api-service/ and should undo that to turn it off.
I' creating a log and i need to retrieve the request body to save in db. i created a filter with HttpActionContext.
I tried recover via filterContext.Request.Content.ReadAsStringAsync().Result;
but it always return me an empty string.
LogFilter.cs
public override void OnActionExecuting(HttpActionContext filterContext)
{
try
{
Task<string> content = filterContext.Request.Content.ReadAsStringAsync();
string body = content.Result;
logModel.RequestLog rl = new logModel.RequestLog();
rl.IP = ((HttpContextWrapper)filterContext.Request.Properties["MS_HttpContext"]).Request.UserHostAddress;
rl.Type = filterContext.ControllerContext.RouteData.Values["controller"].ToString().ToUpper();
rl.URL = filterContext.Request.RequestUri.OriginalString;
rl.Operation = filterContext.Request.Method.Method;
rl.RequestDate = DateTime.Now;
filterContext.ControllerContext.RouteData.Values.Add("reqID", new deviceLog.RequestLog().Add(rl).ID.ToString());
}
catch { }
//return new deviceLog.RequestLog().Add(rl);
base.OnActionExecuting(filterContext);
}
Maybe request stream already reached to end. Try reset stream position to beginning:
public class MyAttribute:ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionContext actionContext)
{
string rawRequest;
using (var stream = new StreamReader(actionContext.Request.Content.ReadAsStreamAsync().Result))
{
stream.BaseStream.Position = 0;
rawRequest = stream.ReadToEnd();
}
}
}
almost 7 years late... I was tasked to create a filter for a legacy project - about 8 years old - with no patterns or architecture whatsoever.
I tried reading from stream with stream/string async but it doesn't let you read more than once, and it was already read before the action filter - Seek was false, position was readonly.
I tried reflection but it was too much for little gain and again I didn't like it.
I tried getting "MS_HttpContext" but it wasn't in the dictionary.
After 8 hours of research I compromised with getting all the action arguments from the request and just turn them into Json.
For 4.7 framework API:
private string requestBody = "";
public override void OnActionExecuting(HttpActionContext actionContext)
{
requestBody = JsonConvert.SerializeObject(actionContext.ActionArguments);
base.OnActionExecuting(actionContext);
}
For .NET 5.0:
I use this on OnResultExecuted method. The stream is already read so you need to reposition the request body at 0.
private static async Task<string> FormatRequestBody(HttpRequest request)
{
// we set the stream position to 0 to reset the pointer. If this is not done, the read stream will be incorrect and the Body will be empty
request.Body.Position = 0;
// 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);
// We convert the byte[] into a string using UTF8 encoding...
//... and send it back...
return Encoding.UTF8.GetString(buffer);
}
Hope this helps someone, because I stumbled on this post along with other 15 stackoverflow posts while doing my research.
I have a web service that needs to return a large text file for an AJAX call on the client. For starters, I have a valid path to the text file:
var fileName = <file on server>
I know the file name is valid, because I can open it on the server as a FileStream.
I have tried some of the different approaches recommended in ServiceStack and returning a stream, but I can't seem to get it to work.
What should I return? My best guess was:
var stream = File.Open(fileName, ...);
return HttpResult(stream, "text/plain"){ AllowPartialResponse = false };
But that doesn't work; the response is a simple JSON object. Using FileInfo with the asAttachment option didn't work either; it just returned a bunch of file information.
The goal is for the JavaScript client to be able to receive the content as a string:
api.getFile({...}).then( function (result) {
// result has the file contents
});
What is the correct way to do this?
Update:
I ended up using this stream approach to get it working:
using( var fs = File.OpenRead( fileName ) )
{
fs.CopyTo( Response.OutputStream );
}
I don't fully understand why some of the other approaches didn't work, but they appear to be related to 1) FileInfo and how it behaves on the server, 2) file permissions in my development environment, and 3) "partial content" problems and exceptions. File.Exists() returns false, but not in debug discusses one of the problems that was throwing me off.
I've just committed a Service that returns a text file and the HttpResult API's work as expected:
[Route("/textfile-test")]
public class TextFileTest
{
public bool AsAttachment { get; set; }
}
public class MyServices : Service
{
public object Any(TextFileTest request)
{
return new HttpResult(new FileInfo("~/textfile.txt".MapHostAbsolutePath()),
asAttachment:request.AsAttachment);
}
}
Which you can test out with or without the asAttachment option:
http://test.servicestack.net/textfile-test
http://test.servicestack.net/textfile-test?asAttachment=true
You can also access the text file directly (i.e. without a Service):
http://test.servicestack.net/textfile.txt
In addition, the different ways to return an Image response should also apply to text files as well.
I have a small issue accessing a byte[]:
I have a binary object (byte[] saved to mssql db) which I get from the db and I want to read it. Whenever I access it, for its length or for its Read() method, I get a Cannot access a closed Stream exception.
What's the best way to treat binaries if they have to be updated in the code and than saved again to the db?
Thanks.
Edit - code
In this application we convert a test object to a generic data object we've created to simplify, so this is the data object:
public class DataObject
{
public Stream Content { get; set; }
public Descriptor Descriptor { get; set; }
}
The descriptor contains metadata only (currently only name and description strings) and is not relevant, I think.
The test is more complicated, I'll start by adding the mapping into data object. The serializer mentioned is NetDataContractSerializer.
public DataObject Map(Test test)
{
using(var stream = new MemoryStream())
{
Serialize(test, stream);
return new DataObject { Content = stream, Descriptor = test.Descriptor };
}
}
private void Serialize(Test test, MemoryStream stream)
{
serializer.WriteObject(stream, test);
stream.Flush();
stream.Position = 0;
}
and vice versa:
public Test Build(DataObject data)
{
using (var stream = data.Content)
{
var test = Deserialize(stream);
test.Descriptor = data.Descriptor;
return test ;
}
}
private Test Deserialize(Stream stream)
{
return serializer.ReadObject(stream) as IPythonTest;
}
Edit II - trying to change the test's content:
This is my first attempt handling streams, I'm not sure I'm doing it right, so I'll explain first what I want to do: The information in data field should be saved into the test's data object.
private static void UpdateTestObject(DataObject data, Test test)
{
var testData = new byte[data.Content.Length];
data.Content.Read(testData, 0, (int) data.Content.Length);
test.TestObject = testData;
}
The exception is thrown in UpdateTestObject when accessing data.Content. I get it after creating some test, mapping it, and trying to save it.
data.Content.Read(testData, 0, (int) data.Content.Length);
Here we go. The data object has a Content stream that it closed.
Result: Error.
Reasno? TOTALLY (!) unrelated to your question. Basically find out why / what is the problem there in your data handling.
Could be a design fubar in which the stream is not available after a certain point and youru sage of the object is past this point.
So the problem is caused by the Map() method - as far as I could understand, since it used:
using (var stream = new MemoryStream())
{ ... }
The stream was disposed of at the end of the block. Changing it to declaring a MemoryStream and then using it afterwards worked.
Thanks to everyone who gave it a thought (not mentioning reading all this code)! :)