使用REST API的设计模式

时间:2019-02-07 16:12:40

标签: c# asp.net-core design-patterns

我正在制作一个DLL,以使用aspnetcore中的REST API。

理想情况下,我希望这样访问它:

API api = new API(clientInfo);
api.Module.Entity.Action(params);

但我正在努力实现这一目标。我无法将任何内容设为静态,因为可能会同时实例多个会话。我无法传递会话,除非通过引用,否则会话状态(cookies等)可能会在副本中更改。我应该使用一种设计模式吗?

public class API
{
    private Session _session;
    public API(ClientInfo clientInfo)
    {
        _session = new Session(clientInfo);
    }
}

该会话用作客户端的中间件,在客户端需要重复登录时存储登录数据,处理一些错误/重试并公开客户端方法。

public class Session
{
    private Client _client;
    private string _path;
    public Session(ClientInfo clientInfo)
    {
        _client= new Client(clientInfo);
        _path = clientInfo.Path;
    }
    public HttpResponseMessage Get(string name, string arguments = "")
    {
        return _client.Get(_path, name, arguments);
    }
    ...
}

客户端实际上执行呼叫。

public class Client
{
    public HttpResponseMessage Get(string path, string endpointName, string arguments)
    {
        return GetClient().GetAsync(path + endpointName + arguments).Result;
    }
    private HttpClient GetClient(){...}
    ...
}

2 个答案:

答案 0 :(得分:1)

您最大的问题之一就是HttpClient的重用。这是“预核心”时期的一个已知问题。幸运的是,它已得到解决,从Net Core 2.1开始,我们现在有了HttpClientFactory,它允许您根据需要启动并管理HttpClient,它们已作为框架的一部分进行处理。

https://www.stevejgordon.co.uk/introduction-to-httpclientfactory-aspnetcore

考虑到这一点,没有什么可以阻止您使用DI注入IHttpClientFactory,这将为您提供对所需管道的必要访问。除此之外,如何设计“管理”对REST资源的访问的代码完全取决于您。也许某种存储库模式? (真的不知道您的架构就可以猜测工作)

答案 1 :(得分:0)

就我个人而言,我只是为我的API构建一个简单的客户端,并使用与该API具有的端点相对应的方法:

public class FooClient
{
    private readonly HttpClient _httpClient;

    public FooClient(HttpClient httpClient)
    {
        _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
    }

    public async Task<GetFooResult> Get(int id)
    {
        ...
    }

    // etc
}

HttpClient依赖性是通过在Startup.cs中注册类型化的客户端来提供的:

services.AddHttpClient<FooClient>(c =>
{
    // configure client
});

然后我添加一个IServiceCollection扩展名以封装此扩展名和其他任何设置逻辑:

public static class IServiceCollectionExtensions
{
    public static IServiceCollection AddFooClient(this IServiceCollection services, string uri)
    {
        ...
    }
}

然后,在启动程序中,我可以简单地执行以下操作:

services.AddFooClient(Configuration.GetValue<string>("FooUri"));

这对于通过Polly设置自动错误处理,重试策略等非常有用,因为您可以在扩展中一次设置所有配置。

现在,要解决诸如身份验证令牌之类的持久性问题,您有几种可能。我倾向于将身份验证令牌保留为声明,在这种情况下,您可以简单地检索声明并将其传递给需要它的客户端上的方法:

var foo = await _fooClient.Get(fooId, User.FindFirstValue("FooAuthToken"));

如果您以这种方式处理事情,则可以在任何范围(包括单例)中绑定客户端。

另一种方法是将auth令牌实际保留在客户端中,但这必须小心进行。除非您使用ConcurrentDictionary之类的东西,否则绝对应该避免使用单例作用域,即使那样,要确保始终使用正确的令牌也可能有点麻烦。

假设您使用的是请求范围,则可以将令牌直接存储为ivar或其他内容,但是您仍然需要将其保存在其他地方,或者仍然需要重新进行身份验证每个请求。例如,如果要将其存储在会话中,则可以执行以下操作:

services.AddScoped<FooClient>(p =>
{
    var httpClientFactory = p.GetRequiredService<IHttpClientFactory>();
    var httpContextAccessor = p.GetRequiredService<IHttpContextAccessor>();

    var httpClient = httpClientFactory.Create("ClientName");
    var session = httpContextAccessor.HttpContext.Session;

    var client = new FooClient(httpClient);
    client.SetAuthToken(session["FooAuthToken"]);
});

但是,即使那样,我仍然要说将auth令牌传递到方法中比执行任何其他方法要好。关于哪些操作需要身份验证而哪些请求不需要身份验证,则更加明确,并且您始终确切知道从何而来。