前言:使用HttpClient获取Api数据。
第一步:对获取数据结构进行规划,我们还是采用经典的Business作为中间层,HttpClient等底层请求数据的方法放在Service层。
ViewModel -> Business -> Service
第二步:HttpClient的使用httpClientFactory.CreateClient()来获取,避免端口号未能及时释放导致耗尽的情况,我们用单例来构造HttpClientHelper。
public class HttpClientHelper
{
private static HttpClientHelper instance = null;
private static object obj = new object();
private IHttpClientFactory httpClientFactory;
public static HttpClientHelper Instance
{
get
{
if (instance == null)
{
lock (obj)
{
if (instance == null)
{
instance = new HttpClientHelper();
var serviceProvider = new ServiceCollection().AddHttpClient().BuildServiceProvider();
instance.httpClientFactory = serviceProvider.GetService<IHttpClientFactory>();
}
}
}
return instance;
}
}
/// <summary>
/// 记录日志
/// </summary>
public Action<string> HandleLog { get; set; }
/// <summary>
/// 使用post方法异步请求
/// </summary>
/// <param name="url">目标链接</param>
/// <param name="json">发送的参数字符串,只能用json</param>
/// <returns>返回的字符串</returns>
public async Task<string> PostAsyncJson(string url, string json, Dictionary<string, string> header = null, TimeSpan? timeSpan = null)
{
HttpClient client = httpClientFactory.CreateClient();
if (timeSpan.HasValue)
{
client.Timeout = timeSpan.Value;
}
HttpContent content = new StringContent(json);
if (header != null)
{
client.DefaultRequestHeaders.Clear();
foreach (var item in header)
{
client.DefaultRequestHeaders.Add(item.Key, item.Value);
}
}
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
string responseBody = string.Empty;
string resData = string.Empty;
DateTime startTime = DateTime.Now;
try
{
HttpResponseMessage response = await client.PostAsync(url, content);
response.EnsureSuccessStatusCode();
responseBody = await response.Content.ReadAsStringAsync();
resData = responseBody;
}
catch (Exception ex)
{
resData = $"异常:{ExceptionHelper.GetExceptionAllMsg(ex)}";
throw ex;
}
finally
{
var time = DateTime.Now - startTime;
if (resData?.Length > 1000)
{
resData = resData.Substring(0, 1000);
resData += "......";
}
string log =
$@"方向:请求外部接口
url:{url}
method:{"Post"}
耗时:{(int)time.TotalMilliseconds}ms
返回:{resData}
";
HandleLog?.Invoke(log);
}
return responseBody;
}
}
第三步:我们需要两个方法:一个登录的时候获取Token的方法(使用jwt登录校验,登录后服务返回一个密匙Token,客户端用这个Token去访问服务),一个Post请求数据的方法,如下:
public class ApiDataProvider : IDataProvider
{
private string Url { get; set; }
public string Token { get; set; }
//携带Token
public Dictionary<string, string> SetHeader()
{
Dictionary<string, string> header = new Dictionary<string, string>();
header.Add("Authorization", string.Format("Bearer {0}", Token));
return header;
}
//获取数据
public async Task<AjaxResult<T>> GetData<T>(string url, string json = "{}")
{
if (!url.StartsWith("http"))
{
url = Url + url;
}
var content = await HttpClientHelper.Instance.PostAsyncJson(url, json, SetHeader());
var result = JsonConvert.DeserializeObject<AjaxResult<T>>(content);
return result;
}
//获取Token
public async Task<AjaxResult> GetToken(string url, string userName, string password)
{
Url = url;
var content = await HttpClientHelper.Instance.PostAsyncJson((string.Format("{0}/Base_Manage/Home/SubmitLogin", Url)), JsonConvert.SerializeObject(new { userName = userName, password = password }));
var result = JsonConvert.DeserializeObject<AjaxResult>(content);
Token = result.Data as string;
return result;
}
}
同样我们用接口实现,好处是方便随时切换数据获取方法,比如HttpClient换成了WebSocket、SignalR,使用GetToken和GetData的地方是不用动的,只要实现WebSocketDataProvider就行了。
public interface IDataProvider
{
Task<AjaxResult> GetToken(string url, string userName, string password);
//[LogHandler]
Task<AjaxResult<T>> GetData<T>(string url, string json = "{}");
}
第四步:添加Core(有些也叫Util)工程,一些基础公共的方法和类放在这里,此处不在贴代码,暂时目录结构如下:
第五步:修改登录页面LoginViewModel的逻辑,使用_dataProvider.GetToken来实现登录。
IContainerExtension _container;
IRegionManager _regionManager;
IDataProvider _dataProvider;
public LoginViewModel(IContainerExtension container, IRegionManager regionManager, IDataProvider dataProvider)
{
_container = container;
_regionManager = regionManager;
_dataProvider = dataProvider;
Title = "Login";
}
private async void Login()
{
if (!string.IsNullOrEmpty(UserName) && !string.IsNullOrEmpty(Password))
{
var mD5Password = Password.ToMD5String();
var token = await _dataProvider.GetToken(ServerIP, UserName, mD5Password);
if (!token.Success)
{
MessageBox.Show(token.Msg);
return;
}
_regionManager.RequestNavigate("MainContentRegion", nameof(IntroduceView));
}
else
{
MessageBox.Show("请输入用户名或密码!");
}
}
好了,运行看效果,很遗憾报错,因为IDataProvider并没有实现注册,程序并不知道IDataProvider是用ApiDataProvider实现的,当然你直接new一个也是可以的_dataProvider = new ApiDataProvider()。这就违背了我们IOC的初衷了。
第六步:在App.xaml.cs中注册IDataProvider。
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.Register<IDataProvider, ApiDataProvider>();
}
再次运行,效果上还是一样的,但是我们成功的访问了后台。
后续:下一章将实现,将登录界面做成独立窗口,登录后再显示主窗口。
源码地址:https://gitee.com/akwkevin/aistudio.-wpf.-client.-stepby-step
另外推荐一下我的Wpf客户端框架:https://gitee.com/akwkevin/aistudio.-wpf.-aclient