[GitHub](https://github.com/dmitrynogin/dynoproxy) and [NuGet](https://www.nuget.org/packages/Dynoproxy)

Allows to invoke public/private REST API just by defining an interface. 200 lines of C# in total.

### Demo

Sample API is publicly available at http://jsonplaceholder.typicode.com:


    public interface ITypicode : IDisposable
    {
        [Description("GET posts")]
        Task<BlogPost[]> GetAsync();
    
        [Description("GET posts/{0}")]
        Task<BlogPost> GetAsync(int id);

        [Description("PUT posts/{0} {1}")]
        Task<BlogPost> PutAsync(int id, BlogPost data);
    }

    public class BlogPost
    {
        public int UserId { get; set; }
        public int Id { get; set; }
        public string Title { get; set; }
        public string Body { get; set; }
    }

Now we could test proxy generator:

    [TestMethod]
    public async Task Call_REST_API()
    {
        using (var proxy = Proxy.Create<ITypicode>("http://jsonplaceholder.typicode.com"))
        {
            var posts = await proxy.GetAsync();
            Assert.AreEqual(100, posts.Length);

            var post = await proxy.GetAsync(1);
            Assert.AreEqual(1, post.Id);

            post.Title = "XYZ";
            post = await proxy.PutAsync(1, post);
            Assert.AreEqual("XYZ", post.Title);
        }
    }

Optional `authenticate` parameter supports custom authentication schemas using ad-hoc `HttpClient` extension method to setup `HttpClient.DefaultRequestHeaders` in a way like this:

    Proxy.Create<ITypicode>(url, (HttpClient client) => client.AuthenticateAsync(...))

### Library

Library code contains a unified proxy generator based on Castle.Core:

    public static class Proxy
    {
        public static T Create<T>(object target) where T : class =>
            Create<T>(call => Dynamic.InvokeMember(
                target, call.Name, call.Args.ToArray()));

        public static T Create<T>(string apiUrl, Func<HttpClient, Task> authenticate = null) where T : class, IDisposable =>
            Create<T>(new Uri(apiUrl, UriKind.Absolute), authenticate);

        public static T Create<T>(Uri apiUrl, Func<HttpClient, Task> authenticate = null) where T : class, IDisposable =>
            RestProxy.Create<T>(apiUrl, authenticate);

        public static T Create<T>(Func<ProxyCall, object> target) where T : class
        {
            var proxyGenerator = new ProxyGenerator();
            return proxyGenerator.CreateInterfaceProxyWithoutTarget<T>(
                ProxyGenerationOptions.Default,
                new Interceptor(target));
        }

        class Interceptor : IInterceptor
        {
            public Interceptor(Func<ProxyCall, object> target) => Target = target;
            Func<ProxyCall, object> Target { get; }
            public void Intercept(IInvocation invocation) =>
                invocation.ReturnValue = Target(
                    new ProxyCall(invocation.Method.Name, invocation.Arguments)
                        .Returns(invocation.Method.ReturnType)
                        .Define(invocation.Method.GetCustomAttributes()));
        }
    }

Where helpers are:

    public class ProxyCall
    {
        public ProxyCall(string name, IEnumerable<object> args)
            : this(name, args.ToArray())
        {
        }

        public ProxyCall(string name, params object[] args)
        {
            Name = name;
            Args = args;
        }

        public string Name { get; }
        public IReadOnlyList<object> Args { get; }

        public CallResult Result { get; private set; } = CallResult.None;
        public ProxyCall Returns<T>() => Returns(typeof(T));
        public ProxyCall ReturnsAsync<T>() => Returns(typeof(Task<T>));
        public ProxyCall Returns(Type type) => With(result: new CallResult(type));

        public CallMethod Method { get; private set; } = CallMethod.Undefined;
        public ProxyCall Define(string description) =>
            Define(new DescriptionAttribute(description));
        public ProxyCall Define<TAttribute>() where TAttribute : Attribute, new() =>
            Define(new TAttribute());
        public ProxyCall Define(IEnumerable<Attribute> attributes) =>
            Define(attributes.ToArray());
        public ProxyCall Define(params Attribute[] attributes) => 
            With(method: new CallMethod(attributes.Concat(Method)));
        
        ProxyCall With(CallResult result = null, CallMethod method = null) => 
            new ProxyCall(Name, Args) 
            { 
                Result = result ?? Result,
                Method = method ?? Method
            };

        public bool IsDispose => 
            Name == nameof(IDisposable.Dispose) &&
            Result.Void &&
            !Args.Any();
    }

And:

    public class CallResult 
    {
        public static readonly CallResult None = new CallResult(typeof(void));

        internal CallResult(Type raw) => Raw = raw;
        public Type Raw { get; }
        public bool Sync => !Async;
        public bool Async => typeof(Task).IsAssignableFrom(Raw);
        public bool Void => Raw == typeof(void) || Raw == typeof(Task);
        public Type Type => Async 
            ? (Void ? typeof(void) : Raw.GetGenericArguments()[0])
            : Raw;
    }

And:

    public class CallMethod : ReadOnlyCollection<Attribute>
    {
        public static readonly CallMethod Undefined = new CallMethod();

        internal CallMethod(IEnumerable<Attribute> attributes) 
            : this(attributes.ToArray())
        {
        }

        internal CallMethod(params Attribute[] attributes)
            : base(attributes)
        {
        }

        public bool Contains<TAttribute>() where TAttribute : Attribute =>
            Select<TAttribute>().Any();
        public T Peek<TAttribute, T>(Func<TAttribute, T> selector) where TAttribute : Attribute =>
            Select(selector).FirstOrDefault();
        public IEnumerable<Attribute> Select<TAttribute>() where TAttribute : Attribute =>
            Select((TAttribute a) => a);
        public IEnumerable<T> Select<TAttribute, T>(Func<TAttribute, T> selector) where TAttribute : Attribute =>
            Items.OfType<TAttribute>().Select(selector);

        public string Description => Peek((DescriptionAttribute a) => a.Description);
    }

The following class provides all necessary support for REST API calls:

    static class RestProxy
    {
        public static T Create<T>(Uri apiUrl, Func<HttpClient, Task> authenticate = null) 
            where T : class, IDisposable
        {
            var client = new HttpClient() { BaseAddress = apiUrl };
            return Proxy.Create<T>(Execute);            
            object Execute(ProxyCall call)
            {
                if(call.IsDispose)
                {
                    client.Dispose();
                    return null;
                }
                                
                return call.Result.Void ? Send() : SendAndReceive();
                object Send() => client.SendAsync(call, authenticate);
                object SendAndReceive() =>
                    typeof(RestProxy)
                        .GetMethod(nameof(SendAndReceiveAsync), BindingFlags.Static | BindingFlags.NonPublic)
                        .MakeGenericMethod(call.Result.Type)
                        .Invoke(null, new object[] { client, call, authenticate });
            }
        }        
        
        static async Task<T> SendAndReceiveAsync<T>(
            HttpClient client, ProxyCall call, Func<HttpClient, Task> authenticate)
        {
            var response = await client.SendAsync(call, authenticate);
            var json = await response.Content.ReadAsStringAsync();
            return JsonConvert.DeserializeObject<T>(json);
        }

        static async Task<HttpResponseMessage> SendAsync(
            this HttpClient client, ProxyCall call, Func<HttpClient, Task> authenticate = null)
        {
            var description = call.Method.Description
                .Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

            var request = new HttpRequestMessage(
                new HttpMethod(description[0]),
                new Uri(
                    string.Format(description[1], call.Args.ToArray()),
                    UriKind.Relative))
                {
                    Content = description.Length < 3 ||
                        !int.TryParse(description[2].Trim("{}".ToCharArray()), out var index)
                        ? null
                        : new StringContent(
                            JsonConvert.SerializeObject(call.Args[index]),
                            Encoding.UTF8,
                            "application/json")
                };

            var response = await client.SendAsync(request);
            if (response.StatusCode == HttpStatusCode.Unauthorized)
                if(authenticate == null)
                    throw new AuthenticationException();
                else
                {
                    await authenticate(client);
                    return await SendAsync(client, call);
                }

            response.EnsureSuccessStatusCode();
            return response;            
        }
    }

_P.S. Let me know please if something is missing here which could prevent it from being really useful :)_