How to begin creating Linq-To-Repository to HttpRequest framework

538 Views Asked by At

As an example I have a simple web api which has has a single controller called ParentsController it's a standard WebAPi2 ApiController. It is boiler plate.

On the client side I have created an Repository Base class and based on that I have a ParentRepository class.

ApiRequester:

    public class ApiRequester
{
    readonly Uri _apiServiceBaseAddress;
    readonly String _apiControllerPath;
    readonly Guid _apiKey;

    public ApiRequester(string apiServiceBaseAddress, string apiControllerPath, Guid apiKey)
    {
        _apiServiceBaseAddress = new Uri(apiServiceBaseAddress);
        _apiControllerPath = apiControllerPath;
        _apiKey = apiKey;
    }
    public async Task<T> Get<T>(string queryParameters)
    {
        using (var client = new HttpClient { BaseAddress = _apiServiceBaseAddress })
        {
            //client.Timeout = TimeSpan.FromSeconds(60);
            client.DefaultRequestHeaders.Add("X-APIKey", _apiKey.ToString());

            var response = await client.GetAsync(_apiControllerPath + queryParameters);

            var result = await response.Content.ReadAsAsync<T>();

            return result;
        }
    }

    public async Task<T> Post<T>(string queryParameters, T entity)
    {
        using (var client = new HttpClient { BaseAddress = _apiServiceBaseAddress })
        {
            client.DefaultRequestHeaders.Add("X-APIKey", _apiKey.ToString());
            HttpResponseMessage response = null;

            response = await client.PostAsJsonAsync<T>(_apiControllerPath + queryParameters, entity);

            response.EnsureSuccessStatusCode();
            var result = await response.Content.ReadAsAsync<T>();
            return result;
        }
    }

    public async Task<T> Put<T>(string queryParameters, T entity)
    {
        using (var client = new HttpClient { BaseAddress = _apiServiceBaseAddress })
        {
            client.DefaultRequestHeaders.Add("X-APIKey", _apiKey.ToString());
            var response = await client.PutAsJsonAsync<T>(_apiControllerPath + queryParameters, entity);
            response.EnsureSuccessStatusCode();

            var result = await response.Content.ReadAsAsync<T>();

            return result;
        }
    }

    public async Task Delete(string queryParameters)
    {
        using (var client = new HttpClient { BaseAddress = _apiServiceBaseAddress })
        {
            client.DefaultRequestHeaders.Add("X-APIKey", _apiKey.ToString());

            var response = await client.DeleteAsync(_apiControllerPath + queryParameters);
            response.EnsureSuccessStatusCode();
        }
    }
}

RepositoryBase:

public abstract class RepositoryBase<T> where T : class, IEntity
{
    readonly ApiRequester _requester;

    protected RepositoryBase(string apiBaseUri, string controllerPath, Guid apiKey)
    {
        if (String.IsNullOrWhiteSpace(apiBaseUri))
            throw new ArgumentNullException("apiBaseUri");

        if (apiKey.Equals(default(Guid)))
            throw new ArgumentNullException("apiKey");

        _requester = new ApiRequester(apiBaseUri, controllerPath, apiKey);
    }
    public virtual Task<ICollection<T>> GetAsync(string queryParameters)
    {
        return _requester.Get<ICollection<T>>(queryParameters);
    }

    public virtual Task<T> PostAsync(string queryParameters, T entity)
    {
        return _requester.Post<T>(queryParameters, entity);
    }

    public virtual Task<T> PutAsync(string queryParameters, T entity)
    {
        return _requester.Put<T>(queryParameters, entity);
    }
    public virtual Task DeleteAsync(string queryParameters)
    {
        return _requester.Delete(queryParameters);
    }
}

ParentRepository:

    public class ParentRepository : RepositoryBase<Parent>
{
    public ChannelRepository(string apiBaseUri, string controllerPath, Guid apiKey)
        : base(apiBaseUri, controllerPath, apiKey)
    { }
}

Thus consumers of the client can make a call to get a list of parent from the API thus:

pRepo = new ParentRepository();
var parents = await pRepo.GetAsync();

// Do something with parent collection.

The Parent entity looks like this:

public class Parent : IEntity
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid ParentId { get; set; }

    public String Name { get; set; }

// Navigation Properties
    public virtual ICollection<Child> Children { get; set; }
}

What I would like to allow on my repository methods is something similar to the following:

pRepo = new ParentRepository();
var parents = await pRepo.GetAsync().Where(p => p.Name == "aName").Include(p => p.Children);

And here is where I am stuck.

How do I intercept this in my GetAsync() method such that I can build my http request?

To get the 'Where' clause do I create an Extension method? something like:

public static ICollection<TEntity> Where(this ICollection<TEntity> source, Func<TEntity> , bool> predicate)
{
// Do something or not?
}

but then what?

And for the 'Include' part, What do I need here, perhaps something from System.Reflection to get the Child collection property name?

Again how do I intercept the inference in my GetAsync() method?

I understand there are easier ways to do what I want with overloaded methods perhaps, but I like the idea of the creating a linq like approach.

EDIT:

For clarity what I want and I think I need to use expression trees (not sure how yet) to convert my linq expression to a web uri:

so in my client I write: var parents = await pRepo.GetAsync().Where(p => p.Name == "aName").Include(p => p.Children);

and that expression gets converted to something like:

GET /api/parents/?name=aName&IncludeChildren=true

The client can then perform the Get request. Note this should be something I can use in any client I build targeting any type of web based API does not necessarily need to be WebAPI.

1

There are 1 best solutions below

2
On

In my (limited) experience, the Web API Controllers have the optional parameters for things like filtering and sorting, and then translate these into linq-like requests to Repository classes in a data access layer. None of which resides on the client, which is making requests to the Web API controller using standard HTTP conventions in the query string such as:

GET /api/parent?minimumId=5&maximumId=20&orderBy=Name

The optional query string parameters could be converted into either Linq or Dynamic Linq to query some sort of IQueryable provided by the underlying DAL.

You probably won't see linq-like behavior on the requests to the Web API controllers very often since the controllers are HTTP-oriented in nature, and tend towards HTTP-like behavior instead.

If you really want to wrap the calls to the Web API controllers in Linq-like syntax, I would look into converting your data (Funcs/Expressions/Strings/etc) into Linq queries using Dynamic Linq since it at least lets you avoid fussing with the reflection yourself.