I needed to create a .NET WebApi server project that could support calling the API directly with linq. Note that the server would have been using entity framework, so the linq calls made to the api would have been directly converted into Linq2Sql
On the client side i needed not to use the standard integration via the service references. I needed a more "personal" and manageable way, to add headers and to enrich the request with special authentication informations.
This follows more or less the approach given by Microsoft on the subject
Several packages must be added on the server side via nuget
On Global.asax this must be added to return json data usable by others than microsoft.
protected void Application_Start() { var jSettings = new JsonSerializerSettings(); GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings = jSettings; GlobalConfiguration.Configuration.AddODataQueryFilter(); }
The controller methods supporting IQueryable must be changed.
From
[HttpGet] public IEnumerable<CustomerContract> ListItems(string sessionId,int skip = 10,int take = 10,string name = null) { if(_sessionService.IsSessionValid(sessionId)){ using(var context = new ContosoDbContext()){ if(!string.IsNullOrWhiteSpace(name)){ return context.Customers.Where(c=>c.Name==name).Skip(skip).Take(10); }else{ return context.Customers.Skip(skip).Take(10); } } }else{ throw new InvalidSessionException(); } }
Will be changed
[HttpGet] [EnableQuery] public IQueryable<CustomerContract> ListItems(string sessionId) { if(_sessionService.IsSessionValid(sessionId)){ using(var context = new ContosoDbContext()){ return context.Customers; } }else{ throw new InvalidSessionException(); } }
...returning something with "ToList" or "ToArray" will remove the performance benefits or IQueryable: calling them will actually EXECUTE the query and get all results, that will be then filtered through Linq2Objects.
You will gain always in simplicity since the WebApi will take care of pagination and filtering.
We should take 2 approaches, the standard from Microsoft with Service references or we can manage directly the queries via Linq2Rest
Unluckily it is not explained how could we insert additional headers (like for...authentication) or cookies and i did a bit of reverse engineering on the subject obtaining this result
Several packages must be added on the client side via nuget
Then we should mimic the basic infrastructure of Linq2Rest
public class MyHttpWebRequestAdapter : IHttpRequest { private readonly HttpWebRequest _httpWebRequest; public MyHttpWebRequestAdapter(HttpWebRequest httpWebRequest) { _httpWebRequest = httpWebRequest; } public static HttpWebRequest CreateHttpWebRequest(Uri uri, HttpMethod method, string responseMimeType, string requestMimeType) { requestMimeType = requestMimeType ?? responseMimeType; var httpWebRequest = (HttpWebRequest)WebRequest.Create(uri); httpWebRequest.Method = ((object)method).ToString().ToUpperInvariant(); if (method == HttpMethod.Post || method == HttpMethod.Put) { httpWebRequest.ContentType = requestMimeType; } httpWebRequest.Accept = responseMimeType; return httpWebRequest; } public Stream GetRequestStream() { return ((WebRequest)_httpWebRequest).GetRequestStream(); } public Stream GetResponseStream() { return ((WebRequest) _httpWebRequest).GetResponse().GetResponseStream(); } }
public class JsonNetSerializer<T> : ISerializer<T> { public T Deserialize(string input) { return JsonConvert.DeserializeObject<T>(input); } public IList<T> DeserializeList(string input) { return JsonConvert.DeserializeObject<IList<T>>(input); } public T Deserialize(Stream input) { var serializer = new JsonSerializer(); using (var sr = new StreamReader(input)) using (var jsonTextReader = new JsonTextReader(sr)) { return serializer.Deserialize<T>(jsonTextReader); } } public IEnumerable<T> DeserializeList(Stream input) { var serializer = new JsonSerializer(); using (var sr = new StreamReader(input)) using (var jsonTextReader = new JsonTextReader(sr)) { return serializer.Deserialize<IList<T>>(jsonTextReader); } } public Stream Serialize(T item) { throw new NotImplementedException(); } }
public class MyRequestFactory : IHttpRequestFactory { public IHttpRequest Create(Uri uri, HttpMethod method, string responseMimeType, string requestMimeType) { var request = MyHttpWebRequestAdapter.CreateHttpWebRequest(uri, method, responseMimeType, requestMimeType); var appName = Configuration.ApplicationSection.Current.AppName; var appSecret = Configuration.ApplicationSection.Current.Secret; request.Headers[HttpRequestHeader.Authorization] = string.Format("MY_SERVER {0}={1}", appName, appSecret); return (IHttpRequest)new MyHttpWebRequestAdapter(request); } }
public class MyJsonNetSerializerFactory : ISerializerFactory { public ISerializer<T> Create<T>() { return new JsonNetSerializer<T>(); } public ISerializer<T> Create<T, TSource>() { return new JsonNetSerializer<T>(); } }
Given all this the call to the api can be done this way:
public IQueryable<expectedObject> LoadData() { //This parameters will be still considered var apiUrl = "/api/customer/ListItems?sessionId=123456"; var serializerFactory = new MyJsonNetSerializerFactory(); var requestFactory = new MyRequestFactory(); var jsonRestClient = new JsonRestClient(new Uri(apiUrl),requestFactory); using (var restContext = new RestContext<expectedObject>(jsonRestClient, serializerFactory)) { return restContext.Query; } }
At this point we could apply the standard linq to the result of LoadData. And the real call will be made ONLY when the data will be enumerated!