Linq2Rest

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.

On the server side

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();
    }
}

BEWARE THAT...

...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.

On the client side

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

Request adapter

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();
    }
}

Json serializer

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();
    }
}

Request factory

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);
    }
}

Json serializer factory

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!


Last modified on: February 06, 2015