Skip to content

Instantly share code, notes, and snippets.

@alexhiggins732
Last active May 28, 2019 06:01
Show Gist options
  • Save alexhiggins732/0abf9a6d236a2f17bd2092896994bdc3 to your computer and use it in GitHub Desktop.
Save alexhiggins732/0abf9a6d236a2f17bd2092896994bdc3 to your computer and use it in GitHub Desktop.
Static NancyFX Api Module
class Program
{
static void Main(string[] args)
{
HostConfiguration hostConfigs = new HostConfiguration()
{
UrlReservations = new UrlReservations() { CreateAutomatically = true }
};
using (var host = new NancyHost(hostConfigs, new Uri("http://localhost:8080")))
{
host.Start();
Console.WriteLine("Server started at http://localhost:8080");
Console.WriteLine("Press any key to exit...");
Console.ReadLine();
}
}
}
public class NancyLoggingModule : NancyModule
{
static ConcurrentDictionary<string, Func<ILogger, object[], object>> handlers;
static NancyLoggingModule()
{
handlers = new ConcurrentDictionary<string, Func<ILogger, object[], object>>();
var methods = typeof(ILogger).GetMethods();
foreach (var method in methods)
{
handlers[method.Name] = CompileHandler(method);
}
}
private static Func<ILogger, object[], object> CompileHandler(MethodInfo method)
{
var iLogger = Expression.Parameter(typeof(ILogger), "ilogger");
var parameters = method.GetParameters();
var arrayExpr = Expression.Parameter(typeof(object[]), "input");
var accessExpressions = new List<Expression>();
for (var i = 0; i < parameters.Length; i++)
{
accessExpressions.Add(Expression.Convert(Expression.ArrayAccess(
arrayExpr,
Expression.Constant(i)
), parameters[i].ParameterType));
}
var serviceCall = Expression.Call(iLogger, method, accessExpressions.ToArray());
var box = Expression.Convert(Expression.Constant(null), typeof(object));
if (method.ReturnType != typeof(void))
{
box = Expression.Convert(serviceCall, typeof(object));
}
var compiled = Expression.Lambda<Func<ILogger, object[], object>>(box, iLogger, arrayExpr).Compile();
return compiled;
}
ILogger loggerService;
private object[] getRequestArgsForMethod(MethodInfo method)
{
var postData = "";
using (var reader = new StreamReader(Request.Body))
{
postData = reader.ReadToEnd();
}
var inputParameters = JObject.Parse(postData);
var inputDict = inputParameters.ToObject<Dictionary<string, object>>();
var methodParameters = method.GetParameters();
for (var i = 0; i < methodParameters.Length; i++)
{
var paramName = methodParameters[i].Name;
var value = inputDict[paramName];
if (value is JObject jobj)
{
inputDict[paramName] = jobj.ToObject(methodParameters[i].ParameterType);
}
if (value is JArray)
{
var json = value.ToString();
inputDict[paramName] = JsonConvert.DeserializeObject(json, methodParameters[i].ParameterType);
}
}
var args = inputDict.Values.ToArray();
return args;
}
public NancyLoggingModule(ILogger loggerService) : base("/ILogger/")
{
this.loggerService = loggerService;
var methods = typeof(ILogger).GetMethods();
foreach (var method in methods)
{
Post($"{method.Name}", (ctx) =>
{
var args = getRequestArgsForMethod(method);
var result = handlers[method.Name](loggerService, args);
if (result.GetType().BaseType == typeof(Task))
result = ((dynamic)result).Result;
return JsonConvert.SerializeObject(new { status = "ok", result });
});
}
}
}
public class LoggerService : ILogger
{
public Guid Log(string message) => Guid.NewGuid();
public Task<Guid> LogAsync(string message) => Task.FromResult(Guid.NewGuid());
public Guid LogError(string message) => Guid.NewGuid();
public Task<Guid> LogErrorAsync(string message) => Task.FromResult(Guid.NewGuid());
public Guid LogException(string message) => Guid.NewGuid();
public Guid LogException(Exception e) => Guid.NewGuid();
public Guid LogException(Exception e, string message) => Guid.NewGuid();
public Task<Guid> LogExceptionAsync(string message) => Task.FromResult(Guid.NewGuid());
public Task<Guid> LogExceptionAsync(Exception e) => Task.FromResult(Guid.NewGuid());
public Task<Guid> LogExceptionAsync(Exception e, string message) => Task.FromResult(Guid.NewGuid());
public void LogResponse(LoggingResponse response) => Guid.NewGuid();
public Task LogResponseAsync(LoggingResponse response) => Task.FromResult(Guid.NewGuid());
public Guid LogWarning(string message) => Guid.NewGuid();
public Task<Guid> LogWarningAsync(string message) => Task.FromResult(Guid.NewGuid());
}
public interface IGenericLogger
{
Guid NewGuid();
}
public class GenericService : IGenericLogger
{
public Guid NewGuid() => Guid.NewGuid();
}
public abstract class NancyApiModule<TService> : NancyModule//, IInterceptor<TService>
where TService : class
{
private TService implementation;
//private ConcurrentDictionary<Type, Action<Castle.DynamicProxy.IInvocation>> executeMethods;
public NancyApiModule(TService service)
{
this.implementation = service;
var serviceType = typeof(TService);
if (!serviceType.IsInterface)
throw new NotImplementedException();
var serviceRoot = serviceType.Name;
var methods = serviceType.GetMethods();
foreach (var method in methods)
{
bool returnsTask = method.ReturnType.BaseType == typeof(Task); ;
var endpoint = $"{serviceRoot}/{method.Name}";
Post(endpoint, async (ctx, token) =>
{
try
{
using (var reader = new StreamReader(Request.Body))
{
var incomingJson = await reader.ReadToEndAsync();
var inputParameters = JObject.Parse(incomingJson);
var paramDict = inputParameters.ToObject<Dictionary<string, string>>();
var keys = paramDict.Keys.ToList();
//var jsonParameters = inputParameters["Value"] as JArray;
var parameterTypes = method.GetParameters().Select(p => p.ParameterType).ToArray();
object[] parameters = new object[parameterTypes.Length];
for (var i = 0; i < parameters.Length; i++)
{
var type = parameterTypes[i];
// deserialize each parameter to it's respective type
var json = keys[i].ToString();
parameters[i] = JsonConvert.DeserializeObject(json, type);
}
object result = null;
if (returnsTask)
{
dynamic task = method.Invoke(implementation, parameters);
result = await task;
}
else
{
result = method.Invoke(implementation, parameters);
}
return JsonConvert.SerializeObject(result);
}
}
catch (Exception ex)
{
var errorData = new Dictionary<string, object>();
errorData["$exception"] = true;
errorData["$exceptionMessage"] = ex.InnerException.Message;
return JsonConvert.SerializeObject(errorData);
}
});
}
}
}
public class NancyGeneric : NancyApiModule<IGenericLogger>
{
public NancyGeneric(IGenericLogger service) : base(service)
{
}
}
public interface IWebApiClient
{
string BaseUrl { get; set; }
T Execute<T>(string endpoint);
Task<T> ExecuteAsync<T>(string endpoint);
T Execute<T>(string endpoint, Dictionary<string, object> payload);
Task<T> ExecuteAsync<T>(string endpoint, Dictionary<string, object> payload);
}
public class WebApiClient : IWebApiClient
{
public string BaseUrl { get; set; }
public T Execute<T>(string endpoint) => Execute<T>(endpoint, null);
public Task<T> ExecuteAsync<T>(string endpoint) => ExecuteAsync<T>(endpoint, null);
public T Execute<T>(string endpoint, Dictionary<string, object> param)
{
var json = JsonConvert.SerializeObject(param);
var bytes = System.Text.Encoding.UTF8.GetBytes(json);
using (var client = new WebClient())
{
var resultData = client.UploadData(BaseUrl + endpoint, bytes);
var resultJson = System.Text.Encoding.UTF8.GetString(resultData);
//TODO: Handler errors
var apiResponse = JsonConvert.DeserializeObject<ApiResponse<T>>(resultJson);
return apiResponse.result;
}
}
public Task<T> ExecuteAsync<T>(string endpoint, Dictionary<string, object> param)
=> Task.Run<T>(() => Execute<T>(endpoint, param));
}
public static class IInterceptorExtensions
{
public static Dictionary<string, object> ArgumentDictionary(this Castle.DynamicProxy.IInvocation invocation)
{
var payload = new Dictionary<string, object>();
var arguments = invocation.Arguments;
var parameters = invocation.Method.GetParameters();
for (var i = 0; i < parameters.Length; i++)
{
payload[parameters[i].Name] = arguments[i];
}
return payload;
}
}
public interface IInterceptor<TService> : IInterceptor
{
}
public class ApiFactory<TService> : IInterceptor<TService>
where TService : class
{
private IWebApiClient client;
private ConcurrentDictionary<Type, Action<Castle.DynamicProxy.IInvocation>> executeMethods;
public ApiFactory(IWebApiClient client)
{
this.client = client;
this.executeMethods = new ConcurrentDictionary<Type, Action<Castle.DynamicProxy.IInvocation>>();
}
public ApiFactory(IApiServiceLocator apiLocator)
{
var apiBaseUrl = apiLocator.GetServiceLocation(typeof(TService).Name);
this.client = new WebApiClient();
this.client.BaseUrl = apiBaseUrl;
this.executeMethods = new ConcurrentDictionary<Type, Action<Castle.DynamicProxy.IInvocation>>();
}
public ApiFactory(IServiceProvider serviceProvider)
{
var locator = (IApiServiceLocator)serviceProvider.GetService(typeof(IApiServiceLocator));
var serviceLocation = locator.GetServiceLocation(typeof(TService).Name);
this.client = new WebApiClient();
this.client.BaseUrl = serviceLocation;
this.executeMethods = new ConcurrentDictionary<Type, Action<Castle.DynamicProxy.IInvocation>>();
}
public void Execute<T>(Castle.DynamicProxy.IInvocation invocation)
=> invocation.ReturnValue = client.Execute<T>(invocation.Method.Name, invocation.ArgumentDictionary());
public void ExecuteAsync<T>(Castle.DynamicProxy.IInvocation invocation)
=> invocation.ReturnValue = client.ExecuteAsync<T>(invocation.Method.Name, invocation.ArgumentDictionary());
public void Intercept(Castle.DynamicProxy.IInvocation invocation)
{
var method = invocation.Method;
var target = executeMethods.GetOrAdd(method.ReturnType, x =>
{
var isGeneric = x.IsGenericType;
var isAsync = x == typeof(Task) || (isGeneric && x.GetGenericTypeDefinition() == typeof(Task<>));
var interceptMethodName = isAsync ? nameof(ExecuteAsync) : nameof(Execute);
var interceptMethod = this.GetType()
.GetMethod(interceptMethodName)
.MakeGenericMethod(isGeneric ? x.GetGenericArguments() : new[] { x });
var instance = Expression.Constant(this);
var invocationParameter = Expression.Parameter(typeof(Castle.DynamicProxy.IInvocation), "invocation");
var call = Expression.Call(instance, interceptMethod, invocationParameter);
return Expression.Lambda<Action<Castle.DynamicProxy.IInvocation>>(call, invocationParameter).Compile();
});
target(invocation);
return;
}
internal TService CreateClient()
{
var proxyGenerator = new ProxyGenerator();
return proxyGenerator.CreateInterfaceProxyWithoutTarget<TService>(this);
}
}
public class TestInterfaceInterceptor : IInterceptor
{
private IWebApiClient client;
private ConcurrentDictionary<Type, Action<Castle.DynamicProxy.IInvocation>> genericInterceptors;
public TestInterfaceInterceptor(WebApiClient client)
{
this.client = client;
this.genericInterceptors = new ConcurrentDictionary<Type, Action<Castle.DynamicProxy.IInvocation>>();
}
public void GenericIntercept<T>(Castle.DynamicProxy.IInvocation invocation)
=> invocation.ReturnValue = client.Execute<T>(invocation.Method.Name, invocation.ArgumentDictionary());
public void GenericAsyncIntercept<T>(Castle.DynamicProxy.IInvocation invocation)
=> invocation.ReturnValue = client.ExecuteAsync<T>(invocation.Method.Name, invocation.ArgumentDictionary());
public void Intercept(Castle.DynamicProxy.IInvocation invocation)
{
var method = invocation.Method;
var interceptorMethod = genericInterceptors.GetOrAdd(method.ReturnType, x =>
{
var isGeneric = x.IsGenericType;
var isAsync = x == typeof(Task) || (isGeneric && x.GetGenericTypeDefinition() == typeof(Task<>));
var interceptMethodName = isAsync ? nameof(GenericAsyncIntercept) : nameof(GenericIntercept);
var interceptMethod = this.GetType()
.GetMethod(interceptMethodName)
.MakeGenericMethod(isGeneric ? x.GetGenericArguments() : new[] { x });
return CompileGenericInterceptor(this, interceptMethod);
});
interceptorMethod(invocation);
return;
}
private Action<Castle.DynamicProxy.IInvocation>
CompileGenericInterceptor(TestInterfaceInterceptor testInterfaceInterceptor, MethodInfo interceptor)
{
var instance = Expression.Constant(testInterfaceInterceptor);
var invocationParameter = Expression.Parameter(typeof(Castle.DynamicProxy.IInvocation), "invocation");
var call = Expression.Call(instance, interceptor, invocationParameter);
return Expression.Lambda<Action<Castle.DynamicProxy.IInvocation>>(call, invocationParameter).Compile();
}
}
public interface IApiServiceLocator
{
string GetServiceLocation(string serviceName);
}
public class UnitTest1
{
[Fact]
public void Test1()
{
var proxyGen = new ProxyGenerator();
var client = new WebApiClient();
client.BaseUrl = "http://localhost:28180/api/ilogger/";
var i = new TestInterfaceInterceptor(client);
var p = proxyGen.CreateInterfaceProxyWithoutTarget<ILogger>(i);
var result = p.Log("Hello");
var t = p.LogAsync("AsyncErrorMessage");
t.Wait();
var asyncResult = t.Result;
Task.Run(() => p.LogAsync("Async Error Message").GetAwaiter().GetResult()).GetAwaiter().GetResult();
p.LogError("Error Message");
p.LogException(new Exception("Test Exception"));
p.LogException("Test Exception Message");
p.LogException(new Exception("Test Exception"), "Test Exception Message");
p.LogWarning("Test Warning");
}
[Fact]
public void TestGeneric()
{
var proxyGen = new ProxyGenerator();
var client = new WebApiClient();
client.BaseUrl = "http://localhost:28180/api/ilogger/";
var apiLoggerFactory = new ApiFactory<ILogger>(client);
var logger = apiLoggerFactory.CreateClient();
var guid1 = logger.Log("Hello");
var guid2 = logger.LogAsync("Hello Async").GetAwaiter().GetResult();
}
[Fact]
public void TestGenericNancy()
{
var proxyGen = new ProxyGenerator();
var client = new WebApiClient();
client.BaseUrl = "http://localhost:8080/ilogger/";
var apiLoggerFactory = new ApiFactory<ILogger>(client);
var logger = apiLoggerFactory.CreateClient();
var exceptionType = typeof(System.Exception);
var guid1 = logger.Log("Hello");
var guid2 = logger.LogAsync("Hello Async").GetAwaiter().GetResult();
var guid3 = logger.LogException(new Exception(), "argument x was null");
var guid4= logger.LogException(new ArgumentNullException(), "argument x was null");
}
[Fact]
public void TestGenericWithLocator()
{
var locatorClient = new WebApiClient();
locatorClient.BaseUrl = "http://localhost:28180/api/ServiceLocator/";
var locatorFactory = new ApiFactory<IApiServiceLocator>(locatorClient);
var apiLocator = locatorFactory.CreateClient();
//var client = new WebApiClient();
//client.BaseUrl = "http://localhost:28180/api/ilogger/";
var apiLoggerFactory = new ApiFactory<ILogger>(apiLocator);
var logger = apiLoggerFactory.CreateClient();
var guid1 = logger.Log("Hello");
var guid2 = logger.LogAsync("Hello Async").GetAwaiter().GetResult();
var someException = new ArgumentException("Argument error occured");
var guid3 = logger.LogException(someException, "ArgumentException:");
}
[Fact]
public void TestCompileHandle()
{
var args = new object[] { "a", "b" };
var instance = Expression.Constant(this);
var arrayExpr = Expression.Parameter(typeof(object[]), "input");
var vars = new List<ParameterExpression>();
var assignments = new List<Expression>();
var accessExpressions = new List<Expression>();
for (var i = 0; i < args.Length; i++)
{
var type = args[i].GetType();
var varExpression = Expression.Variable(type, "var" + i.ToString());
vars.Add(varExpression);
//ParameterExpression arrayExpr = Expression.Parameter(typeof(int[]), "Array");
// This parameter expression represents an array index.
//ParameterExpression indexExpr = System.Linq.Expressions.Expression.Constant(0); ;// Expression.Parameter(typeof(int), "Index");
// This parameter represents the value that will be added to a corresponding array element.
ParameterExpression valueExpr = Expression.Parameter(typeof(int), "Value");
// This expression represents an array access operation.
// It can be used for assigning to, or reading from, an array element.
Expression arrayAccessExpr = Expression.ArrayAccess(
arrayExpr,
System.Linq.Expressions.Expression.Constant(i)
);
accessExpressions.Add(Expression.Convert(Expression.ArrayAccess(
arrayExpr,
System.Linq.Expressions.Expression.Constant(i)
), type));
Expression convert = Expression.Convert(arrayAccessExpr, type);
BinaryExpression assignment = Expression.Assign(varExpression, convert);
assignments.Add(assignment);
}
var callArgs = new List<ParameterExpression>();
var method = this.GetType().GetMethod(nameof(HandlerTest1));
var callExpression = Expression.Call(instance, method, accessExpressions);
var box = Expression.Convert(callExpression, typeof(object));
assignments.Add(box);
BlockExpression execute = Expression.Block(vars.Concat(assignments));
var exStr = execute.ToString();
// vars.Insert(0, arrayExpr);
var fun = Expression.Lambda<Func<object[], object>>(box, arrayExpr).Compile();
var result = (string)fun(args);
Assert.True(result == "ab");
}
public string HandlerTest1(string a, string b)
{
return a + b;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment