Web-Anchor icon indicating copy to clipboard operation
Web-Anchor copied to clipboard

Be able to configure by code

Open martin-oom opened this issue 6 years ago • 6 comments

Maybe it would be nice to be able to configure a web-anchor api not by attributes but in code. The idea is to be able to befine an api interface in a "contracts" library and use it server and client side without a reference to webanchor.

Contracts library:

public class Customer
{
  ...
}

public interface ICustomerApi
{
  Task<IEnumerable<Customer>> GetCustomersAsync();
  Task AddCustomerAsync(Customer customer);
}

WebApi:

[Route("api/[controller]")]
[ApiController]
public class CustomerController : ControllerBase, ICustomerApi
{
  [HttpGet]
  public Task<IEnumerable<Customer>> GetCustomersAsync()
  {
    ...
  }

  [HttpPost]
  public Task AddCustomerAsync(Customer customer)
  {
    ...
  }
}

Client application calling the WebApi:

// Startup.cs
var customerApi = Api.For<ICustomerApi>("http://localhost:5000", configure => 
{
  configure.Endpoint(x => x.GetCustomersAsync()) // configure the "GetCustomersAsync" endpoint
     .UseHttpGet(optionalRoute); // instead of attribute "Get"
  configure.Endpoint(x => x.AddCustomerAsync()) // configure the "AddCustomerAsync" endpoint (how to handle the parameter? Like Moq: "It.IsAny<Customer>()"?)
    .UseHttpPost() // instead of attribute "Post"
    .Parameter(x => x.customer) // configure the customer parameter, is this even possible without using "magic" strings?
      .UseContent() // instead of attribute "Content"
});
services.AddSingleton(customerApi);

Why?

  • It would be possible to use the interface in an application with an implementation and later switch to use a separate api (don't ask...)
  • To be able to distribute both an api and a separate library to consume it, assuming .NET is used,

Alternatives:

  • Override the ICustomerApi in the client application
public interface ICustomerApiClient : ICustomerApi
{
  [Get]
  Task<IEnumerable<Customer>> GetCustomersAsync();
  ...
}

This is not possible, right?

  • Override the ICustomerApi in the client application, using a dummy class to be able to add the attributes (like EF?)
public abstract class CustomerApiMetadata : ICustomerApi
{
  [Get]
  public abstract Task<IEnumerable<Customer>> GetCustomersAsync();
  [Post]
  public abstract Task AddCustomerAsync([Content] Customer customer);
}
// then in Startup.cs
var customerApi = Api.For<ICustomerApi>("http://localhost:5000", configure => 
  configure.UseMetadataFrom(typeof(CustomerApiMetadata)) // eg extract the attributes from this class instead of the interface
);
services.AddSingleton(customerApi);
  • Just ignore the fact that the "Contracts" library references webanchor and use the attributes there. Then this issue can be ignored... :-)

martin-oom avatar Aug 29 '19 19:08 martin-oom

This has been brought up before by someone else. I didn't see the point of it by that time, but when you mention this ...

[ApiController]
public class CustomerController : ControllerBase, ICustomerApi

... I definitely see the point.

Let's have a look at the options:

  1. Seperate nuget package for attributes It feels like the easiest way would be to just move the attributes to another package, and leave the rest in webanchor as is. However, the attributes are not simple "marker" attributes in webanchor. Some of them contain quite some code and references to actual implementation of logic. See for example https://github.com/mattiasnordqvist/Web-Anchor/blob/master/WebAnchor/Attributes/Content/MultiPartAttribute.cs. So moving the attributes to another package would scatter the implementation of webanchor pretty much.

  2. Configure the IApi client side using for example the fluent interface you suggested I think this looks horrible compared to standard webanchor 🤣, but I guess you could tuck it away somewhere where you don't see it that much. I will have a look at the current state of webanchor and see if this is feasible.

  3. Override the interface. Nope, not possible. Or you could probably be using the hiding new keyword, but that kind of defeats the purpose of overriding (methods have to be redeclared altogether) and refactoring in VS doesn't recognize the relation between the ICustomerApi-methods and the new ICustomerApiClient-methods.

    public interface ICustomerApi
    {
        Task<IEnumerable<Customer>> GetCustomersAsync();
    }

    public interface ICustomerApiClientSide : ICustomerApi
    {
        new Task<IEnumerable<Customer>> GetCustomersAsync();
    }
  1. Use metadata from a dummy class This preserves the webanchor "look & feel". I like it. Inner workings would probably not be very much unlike option nr 2. I'll look into it.

  2. My other stupid idea that might not work at all and not even be very nice... Maybe we could reference webanchor as normal, also on the server side, while we develop... but then use roslyn to remove any webanchor attributes and also webanchor dll references when we compile?

mattiasnordqvist avatar Aug 29 '19 21:08 mattiasnordqvist

Agree, the fluent interface will be hard to implement (is it even possible to access parameter) and look horrible. So best option is probably alternative 4. Also the idea is to have a better defined contract between client and server. Using this approach the dto's, methods and parameters will be well defined and kept in sync but the routing will not. Eg. changing the server from [HttpPost] to [HttpPut] will not break the contract but still break the communication. But I guess this is out of scope...

martin-oom avatar Aug 30 '19 07:08 martin-oom

So option 4: It is certainly possible. It would require some big but probably good changes. If done right it could aslo enable the fluent interface as well as a programmable api at runtime. Its gonna take me at least a week to get all things right. 🤣

mattiasnordqvist avatar Aug 30 '19 09:08 mattiasnordqvist

I think this calls for a brainstorm meeting and a design meeting @carl-berg @martin-oom

mattiasnordqvist avatar Aug 30 '19 09:08 mattiasnordqvist

I totally forgot about this. Is this still something you want @martin-oom ? :)

mattiasnordqvist avatar Jan 07 '20 11:01 mattiasnordqvist

Well, maybe want but doesn't currently need. 😄 I vote to close.

martin-oom avatar Jan 07 '20 19:01 martin-oom