WebAPI OData and skip/paging in DocumentDB/CosmosDB - asp.net-web-api

This question was previously posted here but I am repeating the question and a summary of the discussion here to have a complete question.
I have set up a ODataController in ASP.NET WebAPI with DocumentDB/CosmosDB as a backend. It works quite ok, I return the result of CreateDocumentQuery and $select,$top,$orderby and $filter work fine.
However, $skip does not. I know that this is a planned feature (Vote here) but I would like to know if there is any temporary workaround.
My plan was to have a "continuationToken" parameter to my OData controller action. I would then pass this using FeedOptions.RequestContinuation to CreateDocumentQuery. However, CreateDocumentQuery does not return a response token. I then tried with ReadDocumentFeedAsync, which does return a response token, but it does not return a IQueryable.
This is an example of api code inside a ODataController:
public IQueryable<Result> FindResults(Uri collectionUri, string continuationToken)
{
FeedOptions feedOptions = new FeedOptions();
feedOptions.MaxItemCount = 100;
feedOptions.RequestContinuation = continuationToken;
var query = client.CreateDocumentQuery<Result>(collectionUri, queryString, feedOptions).AsDocumentQuery();
return query as IQueryable<Result>;
}
However, what would the client code look like? I.e. what is the http-request required to keep the IQueryable alive to be able to use the continuationToken when paging?
I guess I could store the result of ReadDocumentFeedAsync in memory and return a IQueryable to that, but is there a better solution?

Did you look at the PageResult object?
For non-OData formats, it is still possible to support next-page links and inline count, by wrapping the query results in a PageResult object. However, it requires a bit more code. Here is an example:
public PageResult<Product> Get(ODataQueryOptions<Product> options)
{
ODataQuerySettings settings = new ODataQuerySettings()
{
PageSize = 5
};
IQueryable results = options.ApplyTo(_products.AsQueryable(), settings);
return new PageResult<Product>(
results as IEnumerable<Product>,
Request.GetNextPageLink(),
Request.GetInlineCount());
}
https://learn.microsoft.com/en-us/aspnet/web-api/overview/odata-support-in-aspnet-web-api/supporting-odata-query-options

Related

Compression response filter fails on breeze.js Metadata call

I have an http module where I'm adding a response filter below for compression. This works for all API calls except for 1, the call to MetaData. If I remove the [BreezeController] decoration it works fine. I think it has to do with action filter attribute that converts the string return type into an HttpResponse return type with string content.
The error I'm getting is " Exception message: The stream state of the underlying compression routine is inconsistent."
I've done some testing where a method thats defined to return an HttpResponse works fine. So I think its the scenario where the method is defined to return string, and then the action filter changes it to HttpResponse at runtime.
Any ideas how I can get this to work?
Here's the response filter being added in BeginRequest:
HttpApplication app = (HttpApplication)sender;
// Check the header to see if it can accept compressed output
string encodings = app.Request.Headers.Get("Accept-Encoding");
if (encodings == null)
return;
Stream s = app.Response.Filter;
encodings = encodings.ToLower();
if (encodings.Contains("gzip"))
{
app.Response.Filter = new GZipStream(s, CompressionMode.Compress);
app.Response.AppendHeader("Content-Encoding", "gzip");
}
Don't know the specifics of what you're doing but I know that the [BreezeController] attribute strips out filters and adds back just the ones that breeze wants.
One approach might be to define a separate controller (ModelMetadataController) that only serves the metadata. This controller doesn't have the [BreezeController] attribute; it's a plain old Web API controller.
Then you create a "Breeze controller" (ModelController) with all of the usual methods except the Metadata method.
You call the metadata controller from the client during app launch via MetadataStore.fetchMetadata just to get metadata.
Once you have populated a metadataStore in this fashion, you use it in your EntityManager which sends query and save requests to the "real" Web API data controller.
The client code might look something like this:
var ds = new breeze.DataService({
serviceName: 'breeze/Model' // the breeze query & save controller
});
var ms = new MetadataStore({
namingConvention: breeze.NamingConvention.camelCase, // assuming that's what you want
});
ms.addDataService(ds); // associate the metadata-to-come with the "real" dataService
var manager = new breeze.EntityManager({
dataService: ds,
metadataStore: ms
});
// the fun bit: fetch the metadata from a different controller
var promise = ms.fetchMetadata('breeze/ModelMetadata') // the metadata-only controller!
return promise; // wait on it appropriately

servicestack - caching a service response using redis

I have a servicestack service which when called via the browser (restful) Url ex:http://localhost:1616/myproducts, it works fine.
The service method has RedisCaching enabled. So first time it hits the data repository and caches it for subsequent use.
My problem is when I try calling it from a c# client via Soap12ServiceClient. It returns the below error:
Error in line 1 position 183. Expecting element '<target response>'
from namespace 'http://schemas.datacontract.org/2004/07/<target namespace>'..
Encountered 'Element' with name 'base64Binary',
namespace 'http://schemas.microsoft.com/2003/10/Serialization/'.
Below is my Client code:
var endpointURI = "http://mydevelopmentapi.serverhostingservices.com:1616/";
using (IServiceClient client = new Soap12ServiceClient(endpointURI))
{
var request = new ProductRequest { Param1 = "xy23432"};
client.Send<ProductResponse>(request);
}
It seems that the soapwsdl used is giving the problem, but I appear to have used the defaults as generated by servicestack..
Any help will be much appreciated.
Update
I was able over come this error by changing the cache code at the service end:
Code that returned error at client end:
return RequestContext.ToOptimizedResultUsingCache(this.CacheClient, cacheKey,
() =>
new ProductResponse(){CreateDate = DateTime.UtcNow,
products = new productRepository().Getproducts(request)
});
Code that works now:
var result = this.CacheClient.Get<ProductResponse>(cacheKey);
if (result == null)
{
this.CacheClient.Set<ProductResponse>(cacheKey, productResult);
result = productResult;
}
return result;
But I am still curious to know why the first method (RequestContext.ToOptimizedResultUsingCache) returned error at c# client?
But I am still curious to know why the first method (RequestContext.ToOptimizedResultUsingCache) returned error at c# client?
From what I can tell, the ToOptimizedResultUsingCache is trying to pull a specific format (xml, html, json, etc) out of the cache based on the RequestContext's ResponseContentType (see code here and here). When using the Soap12ServiceClient the ResponseContentType is text/html (not sure if this is correct/intentional within ServiceStack). So what ToOptimizedResultUsingCache is pulling out of the cache is a string of html. The html string is being returned to the Soap12ServiceClient and causing an exception.
By pulling directly out of the cache you are bypassing ToOptimizedResultUsingCache's 'format check' and returning something the Soap12ServiceClient can handle.
** If you are using Redis and creating your key with UrnId.Create method you should see a key like urn:ProductResponse:{yourkey}.html
Thanks for your response paaschpa.
I revisited the code and I was able to fix it. Since your response gave me the direction, I have accepted your answer. Below is my fix.
I moved the return statement from RequestContext to the response DTO.
Code which throws error when used via c# client (code was returning entire requestcontext):
return RequestContext.ToOptimizedResultUsingCache(this.CacheClient, cacheKey,
() =>
new ProductResponse(){CreateDate = DateTime.UtcNow,
products = new productRepository().Getproducts(request)
});
Fixed Code (return moved to response DTO):
RequestContext.ToOptimizedResultUsingCache(this.CacheClient, cacheKey,
() => {
return new ProductResponse(){CreateDate = DateTime.UtcNow,
products = new productRepository().Getproducts(request)
}
});

WebService ASP.NET MVC 3 Send and Receive

I've been racking my brain for a couple of days now on how to approach a new requirement.
I have two websites. The first one lets the user fill out an application. The second website is an internal website use to manage the users applications. I need to develop a "web service" that sends the application data from website 1 to website 2 and return a response to website 2 of success or failure. I have never done a web service before and I'm a bit confused on where to start. I've been reading various examples online but they all seem to be just a starting point for building a webservice... no specific examples.
So for posting the data website 1, what would my controller method look like? Do I use Json to post the data to website 2? What would and example of that look like? Is there some form of redirect in the method that points to website 2?
So for posting the response back to website 2 what would that controller method look like? I assume I would use Json again to send the response back to website 1? Is there some form of redirect in the method that points back to website 1?
I would use JSON and POST the application to the web service.
First I am assuming the application data is contained in some type of object. Use JSON.Net to serialize the object into JSON. It will look something like the following code.
var application = new Application();
string serializedApplication = JsonConvert.Serialize(application);
Second is to POST the code your endpoint(webservice, mvc action). To this you'll need to make a HTTPRequest to the endpoint. The following code is what I use to make to POST the code.
public bool Post(string url, string body)
{
//Make the post
ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, errors) => true;
var bytes = Encoding.Default.GetBytes(body);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
Stream stream = null;
try
{
request.KeepAlive = false;
request.ContentLength = bytes.Length;
request.ContentType = "application/x-www-form-urlencoded";
request.Timeout = -1;
request.Method = "POST";
stream = request.GetRequestStream();
stream.Write(bytes, 0, bytes.Length);
}
finally
{
if (stream != null)
{
stream.Flush();
stream.Close();
}
}
bool success = GetResponse(request);
return success;
}
public bool GetResponse(HttpWebRequest request)
{
bool success;
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
using (Stream responseStream = response.GetResponseStream())
{
if (response.StatusCode != HttpStatusCode.OK && response.StatusCode != HttpStatusCode.Created)
{
throw new HttpException((int)response.StatusCode, response.StatusDescription);
}
var end = string.Empty;
using (StreamReader reader = new StreamReader(responseStream))
{
end = reader.ReadToEnd();
reader.Close();
success = JsonConvert.DeserializeObject<bool>(end);
}
response.Close();
}
}
return success;
}
So now you have can POST JSON to an endpoint and receive a response the next step is to create the endpoint. The following code will get you started on an endpoint in mvc that will receive an application and process it.
[HttpPost]
public ActionResult SubmitApplication()
{
//Retrieve the POSTed payload
string body;
using (StreamReader reader = new StreamReader(Request.InputStream))
{
body = reader.ReadToEnd();
reader.Close();
}
var application = JsonConvert.Deserialize<Application>(body);
//Save the application
bool success = SaveApplication(application);
//Send the server a response of success or failure.
return Json(success);
}
The above code is a good start. Please note, I have not tested this code.
You have obviously more than one client for the data & operations. so a service is what you are looking for.
ASP.NET MVC is a good candidate for developing RESTful services. If you (and your Manager) are ready to use beta version, Then Checkout ASP.NET-Web API.
If you want to stay with a stable product, Go for MVC3. you may need to write some custom code to return the data in XML as well as JSON to server different kind of clients. There are some tutorials out there.
So create a Service (ASP.NET MVC / WCF Service) .You may then create 2 client apps, one for the external clients and another for the Internal users. Both of this apps can call methods in the Service to Create/ Read the user accounts / or whatever operation you want to do.
To make the apps more interactive and lively , you may conside including a wonderful thing called SiganalR, which helps you to get some real time data without continuosly polling the data base/ middle tier very in every n seconds !

ActionScript 3.0 To Send POST and get data from .Net 4 using MVC

I am trying to get my flash application to send a request back to the webserver so that it can get some information. So far after reading on stackoverflow for a while and on the net I have some code written, but its not quite working right. I need just a little help tying it all together.
Here is the controller for my webserver
//
// POST: /Home/HoneyPot
[HttpPost]
public ActionResult HoneyPot(bool GetData)
{
//ViewBag.
return View();
}
Here is the ActionScript code that is supposed to be making the request.
// get dynamic page element information
var myData:URLRequest = new URLRequest("http://localhost:59418/HoneyPot");
myData.method = URLRequestMethod.POST;
var vars:URLVariables = new URLVariables();
vars.Input = "GetData=true";
myData.data = vars;
var loader:URLLoader = new URLLoader();
loader.dataFormat = URLLoaderDataFormat.VARIABLES;
loader.addEventListener(Event.COMPLETE, gotPostData_Spiral);
loader.load(myData);
function gotPostData_Spiral(anEvent:Event):void
{
var postData = anEvent.target.data.myVar;
}
Right now when I run the flash code I get this output back:
Error opening URL 'http://localhost:59418/HoneyPot'
Error: Error #2101: The String passed to URLVariables.decode() must be a URL-encoded query
string containing name/value pairs.
at Error$/throwError()
at flash.net::URLVariables/decode()
at flash.net::URLVariables()
at flash.net::URLLoader/onComplete()
Thank you for the help
Instead of:
vars.Input = "GetData=true";
try:
vars.GetData = "true";

Issue Retrieving a Screenshot from a database

I've got a bunch of screenshots and some screenshot meta data I'm trying to display in an ASP.NET MVC 3 web application, I'm trying to retrieve the data from my databse but I get this error:
LINQ to Entities does not recognize the method 'System.Drawing.Image
ByteArrayToImage(Byte[])' method, and this method cannot be translated
into a store expression.
Here's my code:
var screenshotData = (from screenshots in db.screenshots
where screenshots.projects_ID == projectID
select new ImageInformation
{
ID = screenshots.id,
Language = screenshots.language,
Screenshot = Utility.ByteArrayToImage(screenshots.screen_shot),
ProjectID = screenshots.projects_ID
});
foreach (ImageInformation info in screenshotData)
{
this.Add(info);
}
ImageInformation is just a simple class that contains the defintion the information stored (ID, Language, Screenshot, ProjectID).
Here's my ByteArrayToImage function:
public static Image ByteArrayToImage(byte[] byteArrayIn)
{
using (MemoryStream ms = new MemoryStream(byteArrayIn))
{
Image returnImage = Image.FromStream(ms);
return returnImage;
}
}
Can anybody tell me why I receive this error when this code runs?
Thanks.
I think it's because, with LINQ-to-Entities, the code is turned into server-side query and it can't do that in this case. I don't think you can mix client-side code like this directly with L2E.
I would suspect you will have to do the conversion from byte->image after you've retrieved the data from the database as a distinct step.
You can't do the function in a LINQ to Entities query... one option:
1) have a byte[] property on the object you are instantiating (ImageInformation) and copy the data in there along with another propery to read the image from this ImageInformation object.

Resources