OpenRasta Unit Test Response HTTP Error 415 - c#

I have inherited a project from my predecessor which uses OpenRasta to host a webservice for my OpenSource colleagues to access for their applications. This is my first foray into OpenRasta
I've added a lot of additional features all of which is working via manual browser requests, although not 100% reliably but that's perhaps another question later. So I have embarked on creating a set of Unit Tests to test the functionality, which I should be doing anyway. I have successfully created a unit test or two for each GET request all of which are passing, but I am stuck on the test for the single POST I have in the project.
I'm getting a HTTP 415 Error '8-[2012-12-07 11:23:19Z] Information(0) Executing OperationResult OperationResult: type=RequestM ediaTypeUnsupported, statusCode=415.' from the output window. I've taken inspiration from a post by Nate Taylor http://taylonr.com/integration-testing-openrasta and have asked him the same question, which he has kindly replied to. I'm still trying to decipher his answer, and perhaps someone might be able to expand and fill in the gaps in my understanding?
Here is the code which I have been trying:
[Test]
public void AuthenticateUserJSONPOSTTest()
{
object content = new AuthenticationStructure { Username = "matthew.radford", Password = "obviously not going to tell you that bit and will replace with a domain test user when the time comes", AppId = 4 };
OpenRastaJSONTestMehods.POST<AuthenticationResult, AuthenticationStructure>("http://localhost/user", content);
}
[Test]
public static void POST<T, U>(string uri, object content)
{
const string LocalHost = "http://localhost/";
if (uri.Contains(LocalHost))
POST<T, U>(new Uri(uri), content);
else
throw new UriFormatException(string.Format("The uri doesn't contain {0}", LocalHost));
}
[Test, WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
public static void POST<T,U>(Uri serviceuri, object content)
{
using (var host = new InMemoryHost(new Configuration()))
{
var request = new InMemoryRequest()
{
Uri = serviceuri,
HttpMethod = "POST"
};
request.Entity.ContentType = MediaType.Json;
request.Entity.Headers["Accept"] = "application/json";
var serializer = new DataContractJsonSerializer(typeof(T), new [] { typeof(AuthenticationStructure) });
serializer.WriteObject(request.Entity.Stream, content);
request.Entity.Stream.Seek(0, SeekOrigin.Begin);
request.Entity.ContentLength = request.Entity.Stream.Length;
//Just a read test, not necessary for the output
byte[] readbyte = new byte[(int)request.Entity.ContentLength];
request.Entity.Stream.Read(readbyte, 0, (int)request.Entity.ContentLength);
request.Entity.Stream.Seek(0, SeekOrigin.Begin);
U readObject = (U)serializer.ReadObject(request.Entity.Stream);
request.Entity.Stream.Seek(0, SeekOrigin.Begin);
NUnit.Framework.Assert.AreEqual(content, readObject);
var response = new InMemoryResponse();
response.Entity.ContentType = MediaType.Json;
response.Entity.Headers["Accept"] = "application/json";
response = (InMemoryResponse)host.ProcessRequest(request);
int statusCode = response.StatusCode;
//this is where the test fails because the above response isn't correct and gives the 415 statusCode
NUnit.Framework.Assert.AreEqual(201, statusCode, string.Format("Http StatusCode Error: {0}", statusCode));
object returnedObject;
if (response.Entity.ContentLength > 0)
{
response.Entity.Stream.Seek(0, SeekOrigin.Begin);
//Just a read test, not necessary for the output
readbyte = new byte[(int)response.Entity.ContentLength];
response.Entity.Stream.Read(readbyte, 0, (int)response.Entity.ContentLength);
response.Entity.Stream.Seek(0, SeekOrigin.Begin);
returnedObject = serializer.ReadObject(response.Entity.Stream);
//return returnedObject;
}
}
}
Thanks in advance.

I've tried so many different things this morning to try and get this working. The first good step forward I made by trying to read the JSON stream as a string to actually see what the object was being serialized as.
To do that I found How to convert an Stream into a byte[] in C#? this set me off in the right direction to read the stream out to a string. I therefore came up with this line to write it to the output window:
Debug.WriteLine(Encoding.UTF8.GetString(StreamHelper.ReadToEnd(request.Entity.Stream), 0, (int)request.Entity.Stream.Length).ToString());
This was the result:
{"__type":"AuthenticationStructure","username":"matthew.radford","appid":4,"password":"###########"}
I realised there were two problems in this output. Firstly and simply, the password should be before the appid, which was easily fixed in the AuthenticationStructure class, where I had made a mistake. DataMember Order needed to equal 3 for AppId
[DataMember(Name="appid", Order=3)]
public int AppId { get; set; }
Secondly though, the default serialization includes a '__type' member at the beginning of the notation. This obviously then doesn't match my parameters on the POST method of my Handler:
[HttpOperation(HttpMethod.POST)]
public OperationResult Post(string username, string password, int appid)
At this point I looked at trying to remove the type notation from the JSON string. I found a good sites Deserialize array values to .NET properties using DataContractJsonSerializer which showed me both how to write the constructor to include alwaysEmitTypeInformation which they had set to false, but I wanted to Emit the type information, so changed it to true. And it also showed me how to create an Surrogate based on IDataContractSurrogate, which I called AuthenticationTypeSurrogate.
public class AuthenticationTypeSurrogate : IDataContractSurrogate
{
public Type GetDataContractType(Type type)
{
// "Book" will be serialized as an object array
// This method is called during serialization, deserialization, and schema export.
if (typeof(AuthenticationStructure).IsAssignableFrom(type))
{
return typeof(object[]);
}
return type;
}
public object GetObjectToSerialize(object obj, Type targetType)
{
// This method is called on serialization.
if (obj is AuthenticationStructure)
{
AuthenticationStructure authenticationStructure = (AuthenticationStructure)obj;
return new object[] { authenticationStructure.Username, authenticationStructure.Password, authenticationStructure.AppId };
}
return obj;
}
public object GetDeserializedObject(object obj, Type targetType)
{
// This method is called on deserialization.
if (obj is object[])
{
object[] arr = (object[])obj;
AuthenticationStructure authenticationStructure = new AuthenticationStructure { Username = (string)arr[0], Password = (string)arr[1], AppId = (int)arr[2] };
return authenticationStructure;
}
return obj;
}
public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
{
return null; // not used
}
public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit)
{
return typeDeclaration; // Not used
}
public object GetCustomDataToExport(Type clrType, Type dataContractType)
{
return null; // not used
}
public object GetCustomDataToExport(System.Reflection.MemberInfo memberInfo, Type dataContractType)
{
return null; // not used
}
public void GetKnownCustomDataTypes(Collection<Type> customDataTypes)
{
return; // not used
}
}
The serialization worked for this but now the Deserialization, which I never bothered to debug and get right because it still wasn't working because instead of creating a JSON it was serializing a object array, which you would expected because that was what the method GetObjectoSerialize method is returning. So I hit another brick wall unless I could work out how to change this into JSON.
So finally I thought well I'll just add the __type parameter into an overloaded POST method and pass on the other parameters to the original POST method.
[HttpOperation(HttpMethod.POST)]
public OperationResult Post(Type __type, string username, string password, int appid)
{
return Post(username, password, appid);
}
This was so nearly right but it still didn't work, so finally I created another overloaded method and passed the whole type:
[HttpOperation(HttpMethod.POST)]
public OperationResult Post(AuthenticationStructure astruct)
{
return Post(astruct.Username, astruct.Password, astruct.AppId);
}
And finally this worked. I'm not perfectly happy with it and would have liked to link straight into my existing method, but it works.
Thanks to everyone who looked and especially Nate, thanks for your original responses, and hopefully this will help people in the future.

Related

Return XML From Web Api

I've done hours of reading on this and tried many different solutions I've found on SO and else where and still haven't been able to get this working.
The situation is, I've written a web api rest service to provide an interface to a 3rd party application via COM. It's been in production now for a number of months and works flawless. I've been tasked by a new consumer to provide XML responses, up until now it's been working exclusively with JSON perfectly fine.
No matter what I try I can not get an XML response using Postman for testing.
In Post man I have both the content-type and accept header tags set to "application/xml"
So my controller is pretty simple
public class SampleController : ApiController
{
[HttpGet]
[Route("api/getobject")]
public HttpResponseMessage GetByGSTNumber(string token, string key)
{
DataConnection connection = null; new DataConnection();//Set up COM reference
DataObject dataObj = null;
try
{
connection = new DataConnection();
connection.login(token);
dataObj = new DataObject(connection);
Request.CreateResponse(HttpStatusCode.OK, dataObj);
}
catch (Exception ex)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex);
}
finally
{
if (connection != null) { Request.RegisterForDispose(connection); }
if (dataObj != null) { Request.RegisterForDispose(dataObj); }
}
}
}
The Definition of my data object, again don't think overly complicated. It does do a bit of parsing under the hood to convert non CLR types to CLR types.
public class DataObject : IDisposable
{
internal DataConnection dbConnection;
internal dynamic _dataRecord;
public string Key
{
get { return _dataRecord.KeyField.ToString(); }
}
public string Property1
{
get { return _dataRecord.Property1Feild.ToString(); }
}
public DataObject(DataConnection connection)
{
dbConnection = connection;
}
public void OpenRecord(string key)
{
if (!_dataRecord.Seek(key))
{
throw new Exception("Record not found");
}
}
#region IDisposable Support
}
So what I've tried so far is change
return Request.CreateResponse(HttpStatusCode.OK, dataObj);
//Returns JSON even if specifying XML in request
to
return Request.CreateResponse(HttpStatusCode.OK, dataObj "application/xml");
// returns <ExceptionMessage>Could not find a formatter matching the media type 'application/xml' that can write an instance of 'DataObject'.</ExceptionMessage>
I've decorated my data object with [DataContract] and properties with [DataMember] tags but that resulted in no change.
I've created a parameterless constructor, which got me this, but no properties/values are in the xml
I've tried setting the XML Serializer in my Global.asax file but this had no noticeable effect.
What am I missing?
Update
Included application start where I've tried different combos of UseXMLSerializer = true/false with all the above changes
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
xml.UseXmlSerializer = true;
}
I've also tried putting an XML serialize method on my data object and changing my create response call
public string XMLSerialize()
{
XmlSerializer xsSubmit = new XmlSerializer(typeof(DataObject));
using (var sww = new StringWriter())
{
using (XmlWriter writer = XmlWriter.Create(sww))
{
xsSubmit.Serialize(writer, this);
return sww.ToString(); // Your XML
}
}
}
return Request.CreateResponse(HttpStatusCode.OK, creditor.XMLSerialize(), "application/xml");
This results in a
There was an error generating the XML document.
System.InvalidOperationException
Exception aside, I'm not 100% sure that this would produce a correctly formatted result
Update
Have changed the properties on my business class to have an empty setter and now getting XML with properties in my response. Visual studio is complaining about empty setters but compiling, I'm guessing it's not the norm.
public string Key
{
get { return _dataRecord.KeyField.ToString(); }
set {} VS give me a wiggly green line on this but no info
}
For all those that helped. Thanks a ton.
Credit goes to Steve16351 for giving me the hint that got me going in the right direction.
So the problem was in the way I had defined my DataObject class. I needed two changes.
Firstly a paramaterless constructor.
Secondly, I needed to specify property setters. Initially didn't think I needed setters since I would never need to Post data using this class as it's underlying data source is read only.

HttpContent Headers inconsistent enumeration

I am transforming HttpContent into the following dto:
public class ContentDto
{
public string ContentType {get; set;}
public string Headers {get; set; }
public object Data { get; set; }
public ContentDto(HttpContent content)
{
Headers = content.Headers.Flatten();
// rest of the setup
}
}
And am running some unit tests on it:
[Fact]
public void CanBuild()
{
var content = new StringContent("some json", Enconding.UTF8, "application/json");
var dto = new ContentDto(content);
var contentHeaders = content.Headers.Flatten();
Assert.Equal(contentHeaders, dto.Headers);
}
And that test fails since the Content-Length header is not being captured on my dto. However if I do:
[Fact]
public void CanBuild()
{
var content = new StringContent("some json", Enconding.UTF8, "application/json");
var contentHeaders = content.Headers.Flatten();
var dto = new ContentDto(content);
Assert.Equal(contentHeaders, dto.Headers);
}
The test passes and all headers are captured. Even more I also tried this:
[Fact]
public void CanBuild()
{
var content = new StringContent("some json", Enconding.UTF8, "application/json");
var dto = new ContentDto(content);
var contentHeaders = content.Headers.Flatten();
var dto1 = new ContentDto(content);
Assert.Equal(contentHeaders, dto.Headers);
Assert.Equal(contentHeaders, dto1.Headers);
}
and it fails since dto doesn't have the Content-Length header, but dto1 does. I even tried getting the headers inside a Factory-like method like this:
public static ContentDto FromContent<T>(T content) where T : HttpContent
{
// same as the constructor
}
to see if there was something special about the StringContent class regarding the Content-Length headers, but it made no difference, no matter if I used the constructor (which uses the base class HttpContent) or the generic method FromContent (using the actual StringContent in this case) the result was the same.
So my questions are:
Is that the intended behavior of HttpContent.Headers?
Are there some headers specific to the actual HttpContent type?
What am I missing here?
Note: This is the code for the Flatten extension method:
public static string Flatten(this HttpHeaders headers)
{
var data = headers.ToDictionary(h => h.Key, h => string.Join("; ", h.Value))
.Select(kvp => $"{kvp.Key}: {kvp.Value}");
return string.Join(Environment.NewLine, data)
}
Your example is incomplete. I was only able to recreate your issue when I accessed the ContentLength property before calling the extension method. Somewhere in your code (most probably //rest of setup) you are either directly or indirectly calling that property which is most probably following a lazy loading pattern and it is then included in the header when next you call your extension method and it is included in the constructed string. They don't match because you are generating your manual string before accessing the content length property.
In the source code for HttpContentHeaders.ContentLength
public long? ContentLength
{
get
{
// 'Content-Length' can only hold one value. So either we get 'null' back or a boxed long value.
object storedValue = GetParsedValues(HttpKnownHeaderNames.ContentLength);
// Only try to calculate the length if the user didn't set the value explicitly using the setter.
if (!_contentLengthSet && (storedValue == null))
{
// If we don't have a value for Content-Length in the store, try to let the content calculate
// it's length. If the content object is able to calculate the length, we'll store it in the
// store.
long? calculatedLength = _calculateLengthFunc();
if (calculatedLength != null)
{
SetParsedValue(HttpKnownHeaderNames.ContentLength, (object)calculatedLength.Value);
}
return calculatedLength;
}
if (storedValue == null)
{
return null;
}
else
{
return (long)storedValue;
}
}
set
{
SetOrRemoveParsedValue(HttpKnownHeaderNames.ContentLength, value); // box long value
_contentLengthSet = true;
}
}
you can see that if you did not explicitly set a content length then it will add it (lazy load) to the headers when you first try to access it.
This proves my original theory about it being added after you generated/flatten your string and then accessed the ContentLength property and explains the inconsistent enumeration.
It seems that the HttpContent class has a pretty strange behavior with the headers properties. Somehow the content length seems to be computed as it is stated here. It does not address your issue specifically, but you can make a test with a new httpContent object similar to the initial one. I am pretty sure that you`ll be able to get the content length without a problem.

Can I manually hard code a JSON object to be returned by ASP.NET web API?

I'm used to doing this in Django (similar to Ruby on Rails) where in some cases I need to hard code a JSON response object for the client to be able to interpret, but I've been searching everywhere online on figuring out how to do this with ASP.NET web API and I can't find anything on this, ASP.NET web API seems to be forcing me to create a class to represent a JSON response for every URI controller.
For example, here's the only way I know for manually creating a JSON response:
1.) I first need to create the class to represent the response object
public class XYZ_JSON
{
public string PropertyName { get; set; }
public string PropertyValue { get; set; }
}
2.) Then I need to properly write up the URI controller that'll return an "XYZ_JSON" that I've just defined above:
// GET: api/ReturnJSON
public XYZ_JSON Get()
{
XYZ_JSON test = new XYZ_JSON { PropertyName = "Romulus", PropertyValue = "123123" };
return test;
}
Will result with an http response of something like:
200 OK
{"PropertyName":"Romulus", "PropertyValue":"123123"}
This whole class to JSON design pattern is cool and all, but it's not helpful and actually makes things much worse when trying to return a class as a JSON object with many classes within it such as:
public class XYZ_JSON
{
public string PropertyName { get; set; }
public string PropertyValue { get; set; }
public List<ComplexObject> objects { get; set; } // <- do not want
}
The JSON response object above isn't that complex, but for what I'm trying to accomplish I'll have to put a list of classes within a list of classes within a list of classes, and I can't develop it in this awkward way unless I spend a week on it which is just ridiculous.
I need to be able to return a JSON response in this kind of fashion:
// GET: api/ReturnJSON
public JSON_Response Get(string id)
{
// do some SQL querying here to grab the model or what have you.
if (somethingGoesWrong = true)
return {"result":"fail"}
else
return {"result":"success","value":"some value goes here"}
}
The design pattern above is what I'm trying to accomplish with ASP.NET web API, a very simply way to return a semi-hard coded JSON response object which would allow me to return very unique and dynamic responses from a single URI. There's going to be many use cases where a list of up to 8 completely unique Class objects will be returned.
Also, If what I'm trying to accomplish is the backwards way of doing things than that's fine. I've released a very successful and stable iOS application with a flawless Django backend server handling things this way perfectly without any issues.
Can someone explain to me how I can return a simple hard coded JSON response using the ASP.NET web API?
Thanks!
You can create anonymous types in C#, so you can use one of these to produce your hard-coded result. For example:
return new JsonResult
{
Data = new
{
result = "success",
value = "some value"
}
};
To clarify, the above code is for ASP.NET MVC. If you're using Web API, then you can just return the data object, or use an IHttpActionResult. The anonymous type part (the new {}) stays the same.
Use an anonymous object.
public object Get(string id)
{
// do some SQL querying here to grab the model or what have you.
if (somethingGoesWrong = true)
return new {result = "fail"}
else
return new {result = "success", value= "some value goes here"}
}
You can use a generic JObject to return your values without constructing a complete class structure as shown below
public JObject Get(int id)
{
return JsonConvert.DeserializeObject<JObject>(#"{""result"":""success"",""value"":""some value goes here""}");
}
For hard coded response, why not just do something like below. The JSON content will be returned without being surrounded by quotation marks.
public HttpResponseMessage Get()
{
string content = "Your JSON content";
return BuildResponseWithoutQuotationMarks(content);
}
private HttpResponseMessage BuildResponseWithoutQuotationMarks(string content)
{
var response = Request.CreateResponse(HttpStatusCode.OK);
response.Content = new StringContent(content);
return response;
}
private HttpResponseMessage BuildResponseWithQuotationMarks(string content)
{
var response = Request.CreateResponse(HttpStatusCode.OK, content);
return response;
}
// GET: api/ReturnJSON
public JsonResult Get()
{
return Json(new { Property1 = "Value1", Property2 = "Value2" });
}
You can return json using JsonResult class. and the Json() method takes anonymous object so you don't need to create a class.

Asana API C# Project Design

I am designing an API wrapper in C# for Asana, a project management solution. During the design process, I ran into a few roadblocks. I am wondering what a good way to design the API wrapper would be.
The Asana API I am integrating with works with REST. The requests return JSON.
There will be 6 data classes (User, Task, Project, etc), each containing a bunch of strings to hold the data returned from the REST requests. My first idea with these classes is to give them each factory Parse() constructors so I can easily pass in json and get a data object in return. I realize I can't extract the static factory methods into an interface.
I will have a REST request class that will manage sending and receiving data from the REST server. It will always return a JSON string.
Finally, I would like a AsanaAPI class that will contain methods to wrap those exposed on the REST server (i.e GetUser, GetAllUsers, GetTask). Every method either returns a specific data class or an array of data classes. Here are the two cases:
public User GetSingleUser(string userID = "me")
{
if(userID == "") throw new ArgumentException("UserID cannot be blank");
string url = string.Format("{0}/{1}{2}", userUrl, userID, "?opt_fields=id,name,email,workspaces,workspaces.id,workspaces.name");
JSONNode root = JSON.Parse(GetResponse(url))["data"];
return User.Parse(root);
}
public List<User> GetAllUsers()
{
List<User> users = new List<User>();
string url = string.Format("{0}{1}", userUrl, "?opt_fields=id,name,email,workspaces,workspaces.id,workspaces.name");
JSONArray root = JSON.Parse(GetResponse(url))["data"].AsArray;
foreach(JSONNode userRoot in root)
{
users.Add(User.Parse(userRoot));
}
return users;
}
Each method will have that same format, but the User type will be replaced with Project, Task, etc. I want to extract the logic in these two methods because there will be many more methods with almost the exact same format.
In summary, the roadblocks I ran into were the fact that
I can't extract the factory constructor method from the data class.
I can't extract the parsing logic from the request methods
Is there something I can do with generics or is there just a better way of designing this project?
So I created a Parsable interface containing only a Parse method. Each data type implements Parsable. I was able to extract the parsing logic using generic types. It isn't the prettiest solution, but it does work.
public User GetSingleUser(string userID = "me")
{
if(userID == "") throw new ArgumentException("UserID cannot be blank");
string url = "{baseUrl}/users/{userID}?{opt_fields}".FormatWith(
new { baseUrl = BASE_URL, userID = userID, opt_fields = "opt_fields=id,name,email,workspaces,workspaces.id,workspaces.name" });
return (User)ParseJson<User>(AsanaRequest.GetResponse(url));
}
public User[] GetAllUsers()
{
string url = "{baseUrl}/users?{opt_fields}".FormatWith(
new { baseUrl = BASE_URL, opt_fields = "opt_fields=id,name,email,workspaces,workspaces.id,workspaces.name" });
return (User[])ParseJsonArray<User>(AsanaRequest.GetResponse(url));
}
public T ParseJson<T>(string json) where T : Parsable, new()
{
JSONNode root = JSON.Parse(json)["data"];
T ret = new T();
ret.Parse(root);
return ret;
}
public T[] ParseJsonArray<T>(string json) where T : Parsable, new()
{
JSONArray root = JSON.Parse(json)["data"].AsArray;
T[] nodes = new T[root.Count];
for(int i = 0; i < root.Count; i++)
{
T newParsable = new T();
newParsable.Parse(root[i]);
nodes[i] = newParsable;
}
return nodes;
}

How to consume a file with a ServiceStack client

I am trying to use ServiceStack to return a file to a ServiceStack client in a RESTful manner.
I have read other questions on SO (here and here) which advise using HttpResult and a FileInfo object or MemoryStream to allow the ContentType header to be changed to the relevant file type.
This works for me when I call the service via a browser, the correct file automatically starts to download. How do I consume the file using one of the ServiceStack clients though?
I'm using a Request DTO and trying to return using something similar to
return new HttpResult(new FileInfo("file.xml"), asAttachment:true) {
ContentType = "text/xml"
};
How would I consume this with the JsonServiceClient for example?
I had a similar requirement which also required me to track progress of the streaming file download. I did it roughly like this:
server-side:
service:
public object Get(FooRequest request)
{
var stream = ...//some Stream
return new StreamedResult(stream);
}
StreamedResult class:
public class StreamedResult : IHasOptions, IStreamWriter
{
public IDictionary<string, string> Options { get; private set; }
Stream _responseStream;
public StreamedResult(Stream responseStream)
{
_responseStream = responseStream;
long length = -1;
try { length = _responseStream.Length; }
catch (NotSupportedException) { }
Options = new Dictionary<string, string>
{
{"Content-Type", "application/octet-stream"},
{ "X-Api-Length", length.ToString() }
};
}
public void WriteTo(Stream responseStream)
{
if (_responseStream == null)
return;
using (_responseStream)
{
_responseStream.WriteTo(responseStream);
responseStream.Flush();
}
}
}
client-side:
string path = Path.GetTempFileName();//in reality, wrap this in try... so as not to leave hanging tmp files
var response = client.Get<HttpWebResponse>("/foo/bar");
long length;
if (!long.TryParse(response.GetResponseHeader("X-Api-Length"), out length))
length = -1;
using (var fs = System.IO.File.OpenWrite(path))
fs.CopyFrom(response.GetResponseStream(), new CopyFromArguments(new ProgressChange((x, y) => { Console.WriteLine(">> {0} {1}".Fmt(x, y)); }), TimeSpan.FromMilliseconds(100), length));
The "CopyFrom" extension method was borrowed directly from the source code file "StreamHelper.cs" in this project here: Copy a Stream with Progress Reporting (Kudos to Henning Dieterichs)
And kudos to mythz and any contributor to ServiceStack. Great project!
You wouldn't consume files with the ServiceStack's .NET ServiceClients as they're mainly for sending DTO's.
You can just use any normal WebRequest to download files, in the v3.9.33 of ServiceStack introduced some handy WebRequest extensions HTTP Utils that make this easy, e.g:
For a text file:
var xmlFile = downloadUrl.GetXmlFromUrl(responseFilter: httpRes => {
var fileInfoHeaders = httpRes.Headers[HttpHeaders.ContentDisposition];
});
Where fileInfoHeaders contains the W3C ContentDisposition HTTP Header, e.g. when returning a FileInfo, ServiceStack returns:
attachment;filename="file.xml";size={bytesLen};
creation-date={date};modification-date={date};read-date={date};
To download a binary file you can use:
var rawBytes = downloadUrl.GetBytesFromUrl(httpRes => ...);
I have found mythz answer to work well, but it is also possible to use their built in JSonServiceClient to also process file requests as well, just in a slightly non-intuitive way because you can't actually use the return type you would expect.
For a model definition like this:
[Route("/filestorage/outgoing/{Name}.{Extension}", "GET")]
[Route("/filestorage/outgoing", "GET")]
public class GetFileStorageStream : IReturn<HttpResult>
{
public string Name { get; set; }
public string Extension { get; set; }
public bool ForDownload { get; set; }
}
You can define your service to return an HttpResult:
public class FileStorageService : Service
{
public HttpResult Get(GetFileStorageStream fileInformation)
{
var internalResult = GetFromFileStorage(fileInformation);
var fullFilePath = Path.Combine("C:\Temp", internalResult.FileName);
return new HttpResult(new FileInfo(fullFilePath), asAttachment: fileInformation.ForDownload);
}
}
Then on the client side you can use this Get template to properly get the web context:
var client = new JsonServiceClient("http://localhost:53842");
var httpResponse = client.Get<HttpWebResponse>("/filestorage/outgoing/test.jpg");
pictureBox1.Image = Image.FromStream(httpResponse.GetResponseStream());
I found it was not possible to use the new API Get methods as they would attempt to deserialize the HttpResult which isn't actually a true return type but a class representing the web context that service stack has created.
You can intercept the response prior to it being handled by using a response filter, like below:
ServiceClientBase.HttpWebResponseFilter = response =>
{
if (response.Headers["Content-Disposition"] != null)
{
var t = response.DownloadText();
Console.WriteLine(t);
}
};
However, this is not the best way to handle it, since the actual call to client.Method() will result in an ArgumentException when the client attempts to read the response stream (since it has been read previously by response.DownloadFile(...). I haven't yet figured out a way to handle it gracefully, but I 'll update my answer if I do.

Categories