Bob's Blog

Web开发、测试框架、自动化平台、APP开发、机器学习等

返回上页首页

MAUI加Blazor做一个跨平台的记账APP(五)存储信息和http连接



这篇主要记录通用信息的存储和管理。

比如token的管理。上一篇写了通过登录表单提交后得到token并判断是否登录,有个问题是一旦app退出了,信息没了,打开又是未登录的状态。要是用户每次打开都得登录,那体验是挺不好的。会考虑存储起来,当然如果token过期了,这个逻辑体现在后端,会有相应返回,到时就退出登录状态直到重新请求token。这类似有的app登录后一周都直接用,过了时间后就提示身份失效。

比如后端server地址,总不能每个service文件里定义一个完整的url吧,以后要是后端地址改了,改起来也麻烦。这里打算放在全局配置中。

1. 先说token或者某某配置的版本号之类的信息。

可以用本地文件类似linux常用的pid文件,不过这种显得比较原始;

可以用轻量型的db比如sqlite来存储,但这种又夸张了一点,本来就一个两个信息,不是批量的关系型数据。

不过还有一种键值存储,直接能用,叫做Preference。https://learn.microsoft.com/zh-cn/dotnet/maui/platform-integration/storage/preferences ; 还有用到perference但是更安全的叫做SecureStorage。learn.microsoft.com/zh-cn/dotnet/maui/platform-integration/storage/secure-storage

另外还可以参考做前端时会用到的 localStorage。看到一个包叫Jinget Blazor,有GetItemAsync、SetItemAsync之类的方法。没试过这个,但看起来可以用。Jinget Blazor ;

Secure Storage在iOS和Mac上还要额外的配置一下Entitlements.plist,想了一下也没有什么重要的数据需要加密。于是就只采用Preference了。

Preference很简单,直接get和set,把之前的UserService.cs改一下即可。然后app只要没卸载,就能在重新打开后获取上一次的登陆状态。

using System.Text.Json;
using System.Text;
using accountingMAUIBlazor.Models;

namespace accountingMAUIBlazor.Services;

public class UserService : IUserService
{
    public bool IsLoggedIn;
    public string loginName;
    private string _token;

    public bool CheckLoggedStatus()
    {
        IsLoggedIn = Preferences.Get("IsLoggedIn", false);  // here
        return IsLoggedIn;
    }

    public string CheckLoginName()
    {
        loginName = Preferences.Get("loginName", null);  // here
        return loginName;
    }

    public async Task<String> GetTokenAsync(string userName, string passWord)
    {
        _token = Preferences.Get("UserToken", String.Empty);  // here
        if (!string.IsNullOrWhiteSpace(_token) || IsLoggedIn)
        {
            Console.WriteLine("no need to request api, return existent token");
            return _token;
        }

        using var httpClient = new HttpClient();
        HttpResponseMessage response;
        try
        {
            var loginData = new
            {
                username = userName,
                password = passWord
            };
            var loginContent = new StringContent(JsonSerializer.Serialize(loginData), Encoding.UTF8, "application/json");

            response =
                await httpClient.PostAsync("http://127.0.0.1:8000/external/api/login/", loginContent);
            response.EnsureSuccessStatusCode();
        }
        catch (Exception e)
        {
            Console.WriteLine("Error when request api: " + e.Message);
            return _token;
        }

        var result = await response.Content.ReadAsStringAsync();
        Console.WriteLine("result: " + result);
        try
        {
            _token = JsonSerializer.Deserialize<Token>(result)?.token ?? throw new JsonException();
            Preferences.Set("UserToken", _token);  // here
            loginName = JsonSerializer.Deserialize<Token>(result)?.username ?? throw new JsonException();
            Preferences.Set("loginName", loginName);  // here
            IsLoggedIn = true;
            Preferences.Set("IsLoggedIn", IsLoggedIn);  // here
        }
        catch (Exception e)
        {
            Console.WriteLine("Error when handle json: " + e.Message);
            return _token;

        }

        return _token;
    }

}

2. 然后是后端server地址,全局http连接等。

原本打算用appsettings.json,但是本地报错了,等后续再来更新这一部分。

至于http连接,总不能每次调用都把url写全吧,这样以后维护更新比较麻烦;而同一个后端接口可能在不同的文件里调用,总不能重复写逻辑吧。于是打算精简这一块。

可以把server地址、header的定义添加token、后端接口列表的定义、基础的操作和处理统一封装一下;

也可以把每一个后端接口的基础操作定义出来。

我目前选择先按前者做一点。

首先除了appsettings.json的使用之外,还可以在MauiProgram.cs里加一句,来配置上http client的base address。

...
builder.Services.AddScoped(hc => new HttpClient { BaseAddress = new Uri("http://127.0.0.1:8000/external/api/") });
...

现在到login.razor里加上inject

@inject HttpClient http

现在到login.razor.cs里随便加一两个语句,打上断点,就能看到http的base address是有值的。

对于service中调用,有一个最简单直接的方式。可以新增一个BackendService.cs文件(比如要是专门请求github的、openai的,可以新增类似的并加上基础信息),定义base address,然后通过传递的值拼接出对应的url,使用比如是" BackendService backend = new(); var url = backend.GetApi("sign_out");"

也可以再定义一个哈希表,存储后端接口的列表,再通过传递接口名来调用,也可以做到在一个地方统一管理,将来修改基本只修改这一个地方。

namespace accountingMAUIBlazor.Services;

public class BackendService
{
    public const string baseAddress = "http://127.0.0.1:8000/external/api/";
    UriBuilder uriBuilder = new (baseAddress);

    public string GetApi(string name)
	{
        return string.Concat(uriBuilder, name);
    }
}

但这种方式也不是完善的,只是能用而已。如果只是少量的请求还好,如果是中大型应用里请求又多的,很容易耗尽连接和端口。

httpclient的使用参考: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests

以及另一篇:The Right Way To Use HttpClient In .NET

我打算再改一下,考虑到可能还要请求其他的后端接口,这里就用上Named Client和IHttpClientFactory

在MauiProgram.cs中加上一句:

builder.Services.AddHttpClient("Accounting", httpClient => { httpClient.BaseAddress = new Uri("http://127.0.0.1:8000/external/api/"); });

把BackendService改一点,因为考虑到可能会有很多接口,url也许比较长,通过命名的方式来简易获取对应的接口地址,同时也保持在尽量少的地方去维护数据,以免将来有变化时到处改service文件。

namespace accountingMAUIBlazor.Services;

public class BackendService
{
    public Dictionary<string, string> apiList = new()
    {
        { "login", "login/" },
        { "logout", "logout/" },
        ...
    };

    public string GetApiByAlias(string alias)
	{
        var endpoint = apiList[alias];
        return endpoint;
    }
}

在UserService.cs中加上IHttpClientFactory,将httpClient的定义换一下,同时请求的url不必写全了:

    private readonly IHttpClientFactory _httpClientFactory;
    public BackendService backend = new();

    public UserService(IHttpClientFactory httpClientFactory) =>
        _httpClientFactory = httpClientFactory;

    ...
    //using var httpClient = new HttpClient();
    using var httpClient = _httpClientFactory.CreateClient("Accounting");
    ...

    //response = await httpClient.PostAsync("http://127.0.0.1:8000/external/api/login/", loginContent);
    //response = await httpClient.PostAsync("login/", loginContent);
    response = await httpClient.PostAsync(backend.GetApiByAlias("login"), loginContent);
    ...

    //response = await httpClient.PostAsync("http://127.0.0.1:8000/external/api/logout/", null);
    //response = await httpClient.PostAsync("logout/", null);
    response = await httpClient.PostAsync(backend.GetApiByAlias("logout"), null);
    ...

 

下一篇:  MAUI加Blazor做一个跨平台的记账APP(六)界面美化
上一篇:  MAUI加Blazor做一个跨平台的记账APP(四)表单

共有0条评论

添加评论

暂无评论