MiSeCo #7: Communication

Last week, I created an API, which has been able to find and invoke a method in a service. The API was getting all service classes by dependency injection supported by an Autofac container. The next step was to connect the dots and implement a way to invoke a remote service method by using the MiSeCo class.

Communication

Just to remind you, how it’s going to work, I will give an example. I have created a simple service called Service1, which implements an interface IService1:

public interface IService1 : IContractInterface
{
    int Add(int a, int b);
}

As you have probably already correctly guessed, the only method here is adding two integers (without worrying about adding two big integers and overflowing the int type ;)). This interface is implemented in a class library project called Service1, which is referencing MiSeCo. MiSeCo contains a WebApi controller inside and everything that’s needed to host a web service.

On the other end, we have a simple console application, which is referencing Service1.Contract (containing just the IService1 interface) and MiSeCo. The difference is, that here MiSeCo will just provide a method to find and invoke Service1 methods over an HTTP API. miseco

To summarize:

  • The application doesn’t know about the Service1 implementation - it only references its interface
  • Service1 has no idea about the Application

API Model

To communicate, I had to create an API model:

public class InvocationApiModel
{
    public string ServiceName { get; set; }
    public string MethodName { get; set; }
    public object[] Parameters { get; set; }
}

It contains everything that’s needed to find and invoke a method.

Application

When the application needs to call a service it asks the MiSeCo class for an interface implementation:

public class Program
{
    public static void Main(string[] args)
    {
        var miseco = new MiSeCo();
        var service1 = miseco.CreateServiceObject<iservice1>();
        int c = service1.Add(2, 2);
        Console.WriteLine(c);
    }
}

Then it can just call the method as if it was a real service implementation.

MiSeCo

What’s happening behind the scenes is that MiSeCo is creating a dynamic proxy object with a method interceptor:

public void Intercept(IInvocation invocation)
{
    if (invocation.Method.ReflectedType == null) throw new Exception("Unknown service type");
    var model = new InvocationApiModel
    {
        ServiceName = invocation.Method.ReflectedType.Name,
        MethodName = invocation.Method.Name,
        Parameters = invocation.Arguments
    };

    using (var client = new HttpClient())
    {
        client.BaseAddress = new Uri("http://localhost:5000/");
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

        HttpResponseMessage response = client.PostAsJsonAsync("invokeMethod", model)
            .ConfigureAwait(false)
            .GetAwaiter()
            .GetResult();

        if (!response.IsSuccessStatusCode) return;

        string result = response.Content.ReadAsStringAsync()
            .ConfigureAwait(false)
            .GetAwaiter()
            .GetResult();
        object returnValue = JsonConvert.DeserializeObject(result, invocation.Method.ReturnType);
        invocation.ReturnValue = returnValue;
    }
}

When a call comes in, it’s getting the name of the service, its method and its parameters, and creates a model. This model is then sent to the API (service URL is hard-coded for now, support for many services is coming later). The API should return the result, which is converted to a return type and set as a return value of the method.

Service WebApi

The MiSeCo WebAPI finds a service implementation and the requested method, then it simply sets all the parameters, invokes it and returns the result as a JSON response.

[HttpPost]
[Route("invokeMethod")]
public object Services([FromBody] InvocationApiModel model)
{
    IContractInterface service = _services.FirstOrDefault(s => s.GetType().GetInterfaces().Any(i => i.Name == model.ServiceName));
    if (service == null) throw new Exception($"Service {model.ServiceName} could not be found");

    MethodInfo methodInfo = service.GetType().GetMethods().First(m => m.Name == model.MethodName);
    if (methodInfo == null) throw new Exception($"Method {model.MethodName} not found in service {model.ServiceName}");

    var methodParameters = methodInfo.GetParameters();
    if (methodParameters.Length == 0) methodInfo.Invoke(service, null);

    if (methodParameters.Length != model.Parameters.Length) throw new Exception($"Wrong number of parameters for method {model.ServiceName}.{model.MethodName}");
    var parameters = new List<object>();
    for (int i = 0; i < methodParameters.Length; i++)
    {
        object value = Convert.ChangeType(model.Parameters[i], methodParameters[i].ParameterType);
        parameters.Add(value);
    }

    return methodInfo.Invoke(service, parameters.ToArray());
}

Testing time!

I have started the service in one console, application in an other and it all worked! miseco testing

Next, I need to find a way to autodiscover running services. It will be a hard nut to crack.

All the code for MiSeCo is available on Github: https://github.com/mdymel/miseco

Comments

Michał Dymel's Picture

About Michał Dymel

Passionate software developer interested in Web Development, .NET, Angular2, Architecture and security. Currently doing remote consulting.

Szczecin, Poland https://devblog.dymel.pl
Web Analytics