CodeProject API - 第 1 部分






4.99/5 (51投票s)
在本文中,我们将探讨如何开始构建一个适用于所有 C# 开发的客户端 CodeProject API。
系列文章
- CodeProject API - 第 1 部分(本文)
- CodeProject API - 第 2 部分 - 获取一些 Rest
引言
这可能是我为 CodeProject 写过的最雄心勃勃的文章之一。在本文中,我想介绍我是如何着手为 CodeProject API 构建一个包装器的。你可能会问自己,既然可以直接与 API 交互,为什么还要使用另一个包装器,但我希望我的目标能说服你继续读下去。
不久前,我和 Chris Maunder 谈话(好吧,是缠着他要一个 API),结果他真的鞭策团队给了我们一个 API。现在,有很多事情让我一直没能像我希望的那样尽情地玩转这个 API,但我总觉得没有动手使用这个 API 让我心痒难耐——而这又是我迫不及待想尝试的东西。于是,我挤出了一点空闲时间,决定是时候硬着头皮上了。我不想只是制作又一个包装器,因为这里已经有一些非常出色的例子了。相反,我想制作一个最贴近我如今开发方式的包装器,并写一篇文章详细介绍我处理这类开发的方式。为此,我决定需要设定一些目标来指导我,这些目标将作为我“验收标准”的基础。顺便说一句,如果你的项目没有验收标准,你应该开始建立它们——如果你没有书面标准来衡量,你怎么知道你已经成功地完成了你设定的目标呢?
所以,以下是我这次迭代的目标。
目标 1
该包装器必须符合可移植类库(Portable Class Library)标准。只要我严格遵守 PCL 规范,我就应该能够制作出一个可以在多种环境中使用的包装器。这个包装器的第一个版本目标是 .NET 4.5、Windows 8、Windows Phone 8 和 Windows Phone Silverlight 8。我稍后将为这个包装器设定 Xamarin 目标,所以会进行重定向,以便我们也能轻松地在 Android 和 iOS 上使用。
目标 2
API 必须尽可能遵循我认为良好的设计原则。当然,这是一个非常主观的事情,但在可能的情况下,我将力求广泛使用 SOLID 原则。虽然开发将主要遵循 TDD(测试驱动开发),但有时我也会偏离这一点,以便使用一些不易测试的特性,比如 HttpClient。如果我尽可能地遵循单一职责原则,我将最小化所谓的“接触点”。最终,我将制作出供他人使用的东西,所以我必须尽可能务实——这意味着会有一些设计上的妥协,但我会在这里记下来。
目标 3
这将是一个迭代构建的过程。我将第一个版本的功能限制在仅仅获取一个访问令牌(access token)——这个令牌在我们以后使用 API 时至关重要。这看起来可能微不足道,但由于初始设计中将会有大量的设计决策,这是一个很好的基础,可以作为第一步来“发布”一些东西作为试验场。我建议不要过分依赖这个版本,因为它很可能在未来的构建中会被我大量重构,但这是一个很好的起点。
目标 4
我想使用 IoC(控制反转),但我不想强迫你使用**我选择的 IoC**。好吧,这个说法有点奇怪,但因为我希望这个库能被尽可能多的人采用,我的设计是让你可以使用任何适合你的机制。最终,如果你想自己“new”出所有东西,那完全是你的选择,但我喜欢 IoC。话虽如此,我并不期望如果你想使用 IoC,就必须自己注册所有东西。代码提供了一个抽象,我们可以用它来隐藏我们正在使用 IoC 的事实,让我们能够换入我们喜欢的 IoC 容器。我将提供一个 Autofac 的实现,这将演示我们如何将东西包装在一个合适的 IoC 容器中。
目标 5
除非有非常好的理由,否则代码将尽可能遵循单一职责原则(Single Responsibility Principle)。这意味着将会有很多小类将各种东西连接在一起。我是 SRP 的忠实粉丝。
目标 6
我不会写那些我不需要写的东西。这意味着我将使用其他人为诸如转换 JSON 等事情编写的包,除非我发现我需要的东西没有 PCL 形式存在。
目标 7
除了示例项目和测试之外,所有的库都将是可移植类库(Portable Class Libraries)。这只是回到我希望这个库尽可能广泛可用的事实上。
目标 8
输入是不可信的。如果一个方法或构造函数接受一个类型,至少我们应该测试以确保它已被设置。会引入一些有效性检查,但第一步是确保每个公共构造函数或方法都不信任其输入。这可以通过测试轻松验证。
目标 9
最初,我将只提供单元级测试。我不会进行系统级测试(System-Under-Test,SUT),所以输入将尽可能地被模拟(mocked)。
目标 10
现阶段,我不太担心异常处理或日志记录。对于这次迭代,代码将天真地假设当它调用外部源时一切都正常。在下一次迭代中,我将考虑引入更正式的处理方式,以应对所谓的“异常情况”("unhappy day" cases)。这意味着现阶段不会引发任何自定义异常——这些将在后续阶段引入。
好了,这些目标应该足以让你相信我有一个明确的最终目标,那么让我们看看构建这个版本需要什么。
必备组件
Telerik JustMock Lite。这是一个我非常喜欢的开源模拟框架。在我的公司里,我使用 JustMock 的完整版,因为它可以做一些事情,比如让我完全模拟像 `HttpClient` 这样的东西,这样我就可以在非虚方法和虚方法上进行编排。这个功能在开源版本中不可用,但它仍然功能强大——而且它支持 PCL。
Autofac。这是一个极其简单但功能强大的 IoC 容器。我极力推荐它。
Json.NET。在撰写本文时,这是我能找到的唯一一个作为 PCL 库工作的 JSON 转换器,所以我选择了这个实现。
Microsoft HTTP Client Libraries。默认情况下,`HttpClient` 不是作为 PCL 项目提供的。如果我们想使用它,我们将需要下载 PCL 实现。
请注意,我使用 NuGet 获取了最后三个先决条件。
关于我的写作风格的一点说明
读过我以前文章的读者可能知道,我喜欢在我演示如何编写一段代码时使用更具包容性的“我们”。多年来,我发现这有助于使文章更亲切。由于本文将重点讨论我所遵循的流程和我所做的决定,不幸的是,它不会过多地使用这种格式。不过不要绝望,因为在文章的后面,我们将看到如何编写我们自己的代码来集成这个库,而且那部分会更具互动性,我希望到时你能和我一起编写代码。
术语表
- IoC – 控制反转(Inversion of Control)。
- SOLID – 面向对象设计中使用的 5 个关键原则。代表单一职责、开闭、里氏替换、接口隔离、依赖倒置。
- YAGNI – 你不会需要它(You Aren't Gonna Need It)。这个理念是,你不要仅仅因为将来某个时候可能会需要而编写代码。
包装器 API
从一开始,我就决定将 API 分成两个独立的 DLL。第一个 DLL 完全由接口组成。实际上,这个 DLL 规定了我将在这个开发中遵循的契约。这纯粹是个人选择,但基于这样一个事实:我经常编写组件分散的系统,并使用 MEF 自动将它们组合在一起。通过拥有一个中央接口库,这个过程对我来说基本上是轻而易举的。
CodeProject.Api.Http
Http 命名空间是我放置与实际 Http 交互直接相关功能的地方。
HttpClientBuilder
顾名思义,这个类负责构建 `HttpClient`。
public HttpClientBuilder(HttpClient client, IClientSettings clientSettings)
{
if (client == null) throw new ArgumentNullException("client");
if (clientSettings == null) throw new ArgumentNullException("clientSettings");
client.BaseAddress = new Uri(clientSettings.BaseUrl);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
this.client = client;
}
如果我们看构造函数的定义,我们会发现我们需要将 `HttpClient` 传入这个类。看到这个,我们的第一反应可能是这看起来是错的,如果我们在外部创建 `HttpClient`,我们应该在那里填充它。这里要记住的重要一点是,我倾向于使用 IoC,所以我们不会自己创建 `HttpClient` ——我们仅仅是注册 `HttpClient`,然后让 IoC 容器负责给我们一个实例。这当然意味着,我们必须有某种机制来定义我们想要如何设置 `HttpClient` —— 这当然意味着我创建了这个小类来为我们设置它。
在这个 `HttpClient` 实例中,我们设置的关键是 `BaseAddress` 和 `DefaultRequestHeaders`,我们将其设置为接受 JSON。正如我们所见,当我们构建小型的自包含单元时,我们的类不必非常复杂。有趣的是,即使我知道将来需要使用不同的地址,我现在仍保持其尽可能的简单。我不会试图在这里猜测未来的实现,我也不会为我这次迭代中不需要的代码构建架构。虽然 YAGNI 并非严格的 SOLID 概念,但它是一个重要的概念,对我们编写代码的方式有各种影响——我们太容易编写超出我们需要的代码,仅仅因为我们认为它在未来的某个时候可能会派上用场。
ClientRequest
这个类将负责实际向服务器发出 POST 请求,并将响应转换为 JSON。它不会对 JSON 对象进行任何翻译,这个责任在别处。
这个类公开了一个方法:`PostRequestAsync`。我遵循了将异步任务的操作名称附加 Async 的惯例,这是一个很好的方式,可以让他人知道你的代码适用于 `async`/`await`。
public async Task<object> PostRequestAsync(FormUrlEncodedContent request)
{
if (request == null) throw new ArgumentNullException("request");
HttpClient client = clientBuilder.GetClient();
HttpResponseMessage message = await client.PostAsync("Token", request);
string jsonData = await message.Content.ReadAsStringAsync();
var output = JsonConvert.DeserializeObject(jsonData);
return output;
}
我在这里做的第一件事是确保我们传入一个请求对象。你会注意到我并没有说它是一个有效的请求对象,只是说它是一个请求对象。这与我最初的目标一致,即我不信任输入,即使是写调用这个方法的代码的人是我自己。随着开发的进行,像这样的输入将被更严格的验证所包围——当我们看到 `ClientSettings` 和 `UserDetails` 时,我将演示一些这种检查。
下一行显示我们从 `HttpClientBuilder` 中检索 `HttpClient`。`IHttpClientBuilder` 已经被注入到这个类中,只是为了提供这个功能。
现在,我们想发送一个异步的 post 请求来检索我们的令牌。这里我们开始偏离 SRP(单一职责原则)了——这个方法是不是做得太多了?答案当然是,它做得太多了,但我决定暂时保持原样。我知道,在不久的将来,我将需要把它拆分出来,因为将会有比访问令牌测试更多的东西,但是现在,这对我的需求来说已经足够了。重构会在某个时候发生,只是现在不必进行。
最后,我们想要读取响应并将其反序列化为 JSON。这种反序列化是我没有开始拆分这个类的原因。现在,我选择只检索单个属性(这在别处完成),而不是填充整个模型。一旦我开始引入更丰富的模型,就更容易看出我希望这个特定类的形状是什么样的。
顺便说一下,对这个特定方法的测试不如我希望的那么严格。我做出了一个有意识的决定,限制这里的测试集,因为我知道我想模拟 `HttpClient` 的请求,而我还没有为这个特定实现建立完整的基础设施。我推迟了那件事,因为我想在下一篇文章中演示,当我们使用 SRP 时,如何隔离一个变更。
CodeProject.Api.Json
JsonConverter
目前,这个命名空间只包含一个类。这个类提供了从单个令牌中获取值的能力。
CodeProject.Api.Token
AuthenticationTokenPostData
眼尖的读者(好吧,是那些已经浏览过代码的人)会注意到,在 `CodeProject.Api.Interfaces.Http` 中声明的接口数量与 `CodeProject.Api.Http` 中的类数量似乎不匹配。相关命名空间中的接口比类多一个;“缺失的类”在这里实现,因为它是用于构建访问令牌的 post 数据的那个。
public FormUrlEncodedContent Build()
{
clientSettings = validateClientSettings.Validate(clientSettings);
userDetails = validateUserDetails.Validate(userDetails);
List<KeyValuePair<string, string>> postData = new List<KeyValuePair<string, string>>();
Add(postData, "grant_type", "password");
Add(postData, "client_id", clientSettings.ClientId);
Add(postData, "client_secret", clientSettings.Secret);
Add(postData, "username", userDetails.Email);
Add(postData, "password", userDetails.Password);
return new FormUrlEncodedContent(postData);
}
当我们调用 `Build` 方法时,我们期望收到一个编码后的访问令牌项目列表。这里最有趣的是,我已经开始引入一些其他的输入验证,以确保我们拥有实际发出请求所需的值。你可能想知道为什么我是在这个时间点进行验证,而不是在构造函数中——毕竟,我说过不信任输入,但这里似乎没有任何输入参数。简单来说,输入与方法所依赖的任何状态有关,我们在这里验证是因为这是我们实际需要检查状态的时刻。如果我们在构造函数中进行验证,我们就需要事先填充好用户详细信息和客户端设置。虽然这对客户端设置来说没问题(当我们看那个类时会知道为什么),但我们期望用户详细信息更具动态性。通常情况下,我们可能希望用户在运行时输入详细信息,这远在对象图被构建之后。
AccessToken
这个类是我们用来保存访问令牌的模型。它不负责检索令牌,那是 `FetchAccessToken` 的责任,但它将是我们代码从 Web 服务器检索令牌,并将其存储以便在任何需要的地方轻松访问的入口点。
FetchAccessToken
这个类使用 `AuthenticationTokenPostData` 类来构建编码后的内容,然后使用 `ClientRequest` 来检索访问令牌,最后使用 `JsonConverter` 对其进行解码。
public async Task<string> GetTokenAsync()
{
FormUrlEncodedContent encodedContent = encodedPostData.Build();
var ret = await clientRequest.PostRequestAsync(encodedContent) as JObject;
return jsonConverter.GetValueFromToken<string>(ret, "access_token");
}
CodeProject.Api.Settings
ClientSettings
这个类提供了我们用于在线访问 API 的客户端 ID、密钥和基础 URL。这些值是在我们向 CodeProject 注册应用程序时为我们设置的。在示例代码中,我提供了一些值供我们针对我的一个注册进行使用。我强烈建议您创建自己的应用程序,而不是假设我会永久保留这个应用程序的可用性。
public class ClientSettings : IClientSettings
{
public string ClientId
{
get { return "F_FV6JInrKlX35cgj8o_gmzKb17mArv-"; }
}
public string Secret
{
get { return "J8SRZ10DEWy7uCaWdYNnh5VhUV1ObOTWM73DCxTb9JP9Pa_qbfg_8A4YgtD0etst"; }
}
public string BaseUrl
{
get { return "<a href="https://api.codeproject.com/">https://api.codeproject.com/</a>"; }
}
}
UserDetails
这个类是用户详细信息模型,当用户想要访问网站以检索其账户详情时,它提供了用户的电子邮件地址和密码。尽管我所有的编程直觉都告诉我,我应该实现 `INotifyPropertyChanged` 来引发属性更改通知,但我目前还不需要这样做,所以就没有添加它。
public class UserDetails : IUserDetails
{
public string Email { get; set; }
public string Password { get; set; }
}
CodeProject.Api
ModuleRegistration
我想谈论的 `CodeProject.Api` 程序集中的最后一个类实现了一个未在核心 `CodeProject.Api.Interfaces` 程序集中定义的接口。`ModuleRegistration` 实现了 `IModuleRegistration` 接口,该接口实际上定义在 `CodeProject.Api.Ioc.Builder` 中。
这个类之所以在这里实现,是因为它负责注册所有相关的接口到类型的映射,这样客户端应用程序就不必这样做了。我在这里的目标是使注册完全与特定的 IoC 技术无关。最初,所有的注册都标记为注册为单例,因为对于这个特定的迭代,这就是我所需要的全部,但这将被更改,以便像验证之类的东西成为多实例。
public class ModuleRegistration : IModuleRegistration
{
public void Register<T>(IRegistration<T> registration) where T : class
{
if (registration == null) throw new ArgumentNullException("registration");
registration.RegisterAsSingleInstance<IFetchAccessToken, FetchAccessToken>();
registration.RegisterAsSingleInstance<IAccessToken, AccessToken>();
registration.RegisterAsSingleInstance<IUserDetails, UserDetails>();
registration.RegisterAsSingleInstance<IValidate<IClientSettings>, ValidateClientSettings>();
registration.RegisterAsSingleInstance<IValidate<IUserDetails>, ValidateUserDetails>();
registration.RegisterAsSingleInstance<IEncodedPostData, AuthenticationTokenPostData>();
registration.RegisterAsSingleInstance<IClientSettings, ClientSettings>();
registration.RegisterTypeAsSingleInstance<HttpClient>();
registration.RegisterAsSingleInstance<IClientRequest, ClientRequest>();
registration.RegisterAsSingleInstance<IHttpClientBuilder, HttpClientBuilder>();
registration.RegisterAsSingleInstance<IJsonConverter, JsonConverter>();
}
}
同样,尽管为注册添加名称以进行类型解析等功能很诱人,但由于我目前还不需要这个功能,所以我不会去构建它。
好了,这样就涵盖了核心 API。让我们来看看我们如何添加自己的 IoC 实现以及如何将所有东西引导到位。
CodeProject.Api.Ioc.Builder.Autofac
注册
这个类提供了高级 IoC 抽象与 Autofac 中实际底层 IoC 调用之间的映射。实际的调用非常简单,但它们依赖于我们传入将要注册依赖项的 `ContainerBuilder`。确实,随着我们开发其他 IoC 容器,我们会看到它们将有自己的容器传入,比如 Unity 的 `UnityContainer`。
底层接口是一个泛型接口,允许我们指定容器的类型。
public class Registration : IRegistration<ContainerBuilder>
{
public void RegisterAsSingleInstance<TInterface, TType>()
where TType : class, TInterface
{
if (Container == null) throw new ArgumentNullException();
Container.RegisterType<TType>().As<TInterface>().SingleInstance();
}
public void RegisterTypeAsSingleInstance<TType>() where TType : class
{
if (Container == null) throw new ArgumentNullException();
Container.RegisterType<TType>().SingleInstance();
}
public ContainerBuilder Container { get; set; }
}
正如我们在这里看到的,这段代码并不是非常复杂——但它确实完成了我们需要它做的事情。正如我之前提到的,YAGNI 意味着我还没有添加多实例支持,虽然我很想提供一个流畅接口(fluent interface),但我现在还不需要这样做,所以我将在这个迭代中避免这样做。
在这一点上,值得注意的是,我确实打算在以后的迭代中增加对其他 IoC 容器的支持。
Bootstrapper
好了,我们有能力将接口和类型注册到 IoC 容器中,但是我们需要一些东西来将模块注册与 IoC 容器的创建联系起来。这就是 `Bootstrapper` 的用武之地。这个抽象类为想要使用 API 的应用程序提供了入口点。当我们想在幕后为我们连接 API 时,我们将从此类派生;如果我们想,我们也可以完全手动完成,但我提供了这个功能,这样我们就不必这样做了。
public abstract class Bootstrapper
{
protected Bootstrapper()
{
Bootstrap();
}
protected virtual void OnStartup()
{
}
protected virtual void RegisterAdditionalModules(IRegistration<ContainerBuilder> registration)
{
}
protected IContainer Container { get; private set; }
protected virtual void InitializeModules()
{
OnStartup();
var containerBuilder = new ContainerBuilder();
IModuleRegistration moduleRegistration = new ModuleRegistration();
IRegistration<ContainerBuilder> registration = new Registration()
{
Container = containerBuilder
};
moduleRegistration.Register(registration);
RegisterAdditionalModules(registration);
Container = containerBuilder.Build();
}
private void Bootstrap()
{
InitializeModules();
}
}
引导过程的核心是 `InitializeModules`。在这个方法中,我们创建 Autofac 所需的 `ContainerBuilder`,并将其分配给 `Registration` 类。然后我们在 `ModuleRegistration` 中调用 register,它会为我们注册 API 的不同部分。这个谜题的最后一部分需要代码在容器中构建 IoC 注册。
我提供了 `OnStartup` 以允许我们的应用程序在我们开始初始化模块之前做一些工作,以及 `RegisterAdditionalModules` 以允许我们在我们的应用程序中建立注册映射。实际上,这使得 `Bootstrapper` 成为我们所有应用程序注册所在的“一站式商店”。这是我在为其他容器提供引导程序时将遵循的模式。
值得注意的是,`Bootstrapper` 背后的测试是我最不满意的。它们存在,但它们所做的只是验证不同的方法是否被调用。我计划在下一次迭代中扩展这些测试。
我们的第一个访问令牌应用
我知道我们已经讲了很多内容。现在是我们开始构建一个应用程序的时候了。我们可以构建的最简单的应用程序类型当然是控制台应用程序,所以我们将从这个开始。我把我自己的应用程序命名为 `CodeProject.Api.Autofac.ConsoleApplication`,但如果你觉得这个名字太笨拙,可以随意更改。
在我们真正开始添加功能之前,我们需要以下引用
让我们从创建描述我们想做什么的接口开始。由于我们要检索一个授权令牌,我们需要一个机制来获取用户的电子邮件地址和密码,并且我们需要一个方法来调用我们 `AccessToken` 实现中的 `GetTokenAsync`;因为我们知道我们将要调用一个异步方法,让我们确保我们的接口也把这个方法暴露为一个适合异步方法调用的候选方法。
using System.Threading.Tasks;
namespace CodeProject.Api.Autofac.ConsoleApplication
{
public interface IAuthTokenSample
{
/// <summary>
/// Enter the email address and password.
/// </summary>
void EnterCredentials();
/// <summary>
/// Uses the API to retrieve the token.
/// </summary>
/// <returns>An awaitable task</returns>
Task GetTokenAsync();
}
}
好了,接口已经创建好了。让我们继续创建这个类的实现。我巧妙地将这个类命名为 `AuthTokenSample`。
using System;
using System.Threading.Tasks;
using CodeProject.Api.Interfaces.Settings;
using CodeProject.Api.Interfaces.Token;
namespace CodeProject.Api.Autofac.ConsoleApplication
{
public class AuthTokenSample : IAuthTokenSample
{
public void EnterCredentials()
{
}
public async Task GetTokenAsync()
{
}
}
}
现在,这个类依赖于访问令牌,而访问令牌只有在我们填充了用户详细信息之后才能获得,所以这告诉我们,我们必须获取对 `IAccessToken` 和 `IUserDetails` 的引用。我们将在这里使用构造函数注入,将这些引用注入到类中,并存储它们以备后用。
private readonly IAccessToken accessToken;
private readonly IUserDetails userDetails;
public AuthTokenSample(IAccessToken accessToken, IUserDetails userDetails)
{
if (accessToken == null) throw new ArgumentNullException("accessToken");
if (userDetails == null) throw new ArgumentNullException("userDetails");
this.accessToken = accessToken;
this.userDetails = userDetails;
}
我们现在已经准备好一切,可以开始实现接口方法了。让我们先让用户输入他们的详细信息。
public void EnterCredentials()
{
do
{
Console.Write("Enter email address: ");
userDetails.Email = Console.ReadLine();
} while (string.IsNullOrWhiteSpace(userDetails.Email));
do
{
Console.Write("Enter password: ");
userDetails.Password = Console.ReadLine();
} while (string.IsNullOrWhiteSpace(userDetails.Password));
}
我们需要做的最后一件事是,检索并显示令牌。
public async Task GetTokenAsync()
{
Console.WriteLine(await accessToken.GetTokenAsync());
}
就这么简单 - 因为用户详细信息存储在单个实例中,所以访问令牌可以完全访问已填充的电子邮件地址和密码。我们需要在 API 的这一端编写的代码量微不足道。
这个谜题的最后一步是编写我们的 `Bootstrapper`。由于这是我们 API 之旅的开始,我选择将这个类命名为 `StartApi`。同样,请选择任何你觉得最舒服的名字。
由于我们添加了一个希望被视为 IoC 生命周期一部分的接口和类,我们的引导程序需要在我们使用它们之前注册它们。幸运的是,引导程序生命周期为我们提供了 `RegisterAdditionalModules`,以便将它们包含在注册构建过程中。
最后,我们将提供一种实际使用我们测试实现的方法。由于我们利用了 Autofac,我们的类最终会是这个样子:
using Autofac;
using CodeProject.Api.Ioc.Builder;
namespace CodeProject.Api.Autofac.ConsoleApplication
{
public class StartApi : Bootstrapper
{
protected override void RegisterAdditionalModules(IRegistration<ContainerBuilder> registration)
{
registration.RegisterAsSingleInstance<IAuthTokenSample, AuthTokenSample>();
base.RegisterAdditionalModules(registration);
}
public async void TestAuthTokenAsync()
{
using (var scope = Container.BeginLifetimeScope())
{
var item = scope.Resolve<IAuthTokenSample>();
item.EnterCredentials();
await item.GetTokenAsync();
}
}
}
}
最后,向 `Main` 方法添加代码,我们就可以开始了——我们可以检索我们的访问令牌。
using System;
namespace CodeProject.Api.Autofac.ConsoleApplication
{
class Program
{
static void Main(string[] args)
{
StartApi api = new StartApi();
api.TestAuthTokenAsync();
Console.ReadKey();
}
}
}
就这样。这就是创建我们的应用程序所需要做的全部。真的就这么简单。这是我们的应用程序运行时的视图:
结论
在这个阶段,我们已经为我们的 API 准备好了很多基础。我们还有很多工作要做,并且在添加额外功能时需要进行一些重构,但我们现在已经迈出了很好的第一步。
我的下一步是扩展 API 检索的内容,引入诸如多实例 IoC 支持等功能,开始考虑添加合理的异常处理,并应对在线资源不可用的情况,以及加强测试和添加额外的示例。我们现在拥有的是一个良好的开端,我希望我处理开发的方式在这里是清晰的。
最后一点。我前面谈到了我的目标。在这个阶段,我可以说我已经实现了我设定的目标。预先有可用的目标使得判断我们是否实现了我们想要实现的目标变得容易得多。
音乐
在我关于开发 Ultimate Coder 应用程序的文章中,我列出了我在编码时听的音乐。当开发进展顺利时,音乐能帮助我放松,这次开发也不例外;所以我听的音乐是:
- Joe Satriani – The Complete Albums
- Joe Bonamassa – Live From the Royal Albert Hall
- Brad Gillis – Alligator
- Pink Floyd – The Wall
- Rush – Moving Pictures
- Sixx A.M. – Modern Vintage
- The Answer – Everyday Demons
- Thunder – Wonder Days
感谢您的阅读,希望您喜欢这篇文章。至少,您现在有了一种启用 Autofac 的方式来获取 CodeProject 访问令牌。
历史
- 2015-04-28 - 提交初始版本
- 2015-05-07 - 链接到第二篇文章