Nancy 依 Class Method 自動宣告路由

  • 182
  • 0
  • 2019-05-08

利用映射的方式將指定的類別或介面方法自動宣告對應的 Nancy Route

用 .NET 架設 Web API 然後在 Client 端也是 .NET 在呼叫,並不少見。

但是當兩邊對接的 Programmer 是同一個人的時候,就會有一種寫重複程式碼的感覺。

如果可以在 Server 端用統一方式將類別方法自動對應成 Web API,那相對的也只需要在 Client 使用一樣的類別或介面就有機會用統一方式自動產出呼叫 API 的方法吧?

考量環境限制,我覺得 Nancy 是比較不受限制的,它不需要 IIS ,而且有機會包裝成 Windows Service,或併在其它應用程式中。

Nancy 宣告 Route 的方式如下,

public class TestModule : NancyModule
{
    public TestModule()
    {
        Get<string>("/", (p) =>
        {
            return "response string";
        });
    }
}

以 CustomerRepository 為例

public class CustomerRepository : ICustomerRepository
{
    public Customer Find(int id);
    public List<Customer> ALL();
}

我想要只做如下宣告 Route 就能自動生成 API,像是 http://domain/Customer/Find?id=1

public class TestModule : DataProviderModule
{
    public TestModule()
    {
        var repo = new CustomerRepository();
        GetMapping<ICustomerRepository>("/Customer", repo);
    }
}

DataProviderModule 會繼承 NancyModule 並擴充 GetMapping<T> 方法,一個實驗性的實作如下

public abstract class DataProviderModule : NancyModule
{
    public void GetAlias<T>(string path, Func<dynamic, object> action, Func<NancyContext, bool> condition = null, string name = null) {
        Get<T>(path, p => { return (T)action.Invoke(p); }, condition, name);
    }

    public void GetMapping<T>(string path, T target)
    {
        var methodInfos = typeof(T).GetMethods(BindingFlags.Public | BindingFlags.Instance);

        foreach (var method in methodInfos)
        {
            var paraInfos = method.GetParameters();


            Func<dynamic, object> func = (p) =>
            {
                var paras = paraInfos.Select(para =>
                    Convert.ChangeType(Request.Query[para.Name], para.ParameterType)
                ).ToArray();

                return method.Invoke(target, paras);
            };

            var returnVoid = method.ReturnType == typeof(void);
            typeof(DataProviderModule)
                .GetMethod("GetAlias")
                .MakeGenericMethod(returnVoid ? typeof(string) : method.ReturnType)
                .Invoke(this, new object[] { path + "/" + method.Name, func , null, null});
        }
    }
}

GetAlias 是 NanacyModule.Get 的多載太多,不好映射的偷懶寫法。

雖然 GetMapping 目前只能對應以基礎型別為參數的方法,但已經能應付大部狀況了。

接下來就看 Client 端怎麼依據介面生成對應呼叫 Web API 的方法了,預計用法會像 NSubstitute 能在執行期動態產生繼承介面的實體。

ICustomerRepository repo = Substitute.For<ICustomerRepository>();

關於如何實作,或許會用到 System.Reflection.Emit 相關功能吧。