Bob's Blog

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

返回上页首页

MAUI加Blazor做一个跨平台的记账APP(四)表单



上一篇记录了请求后端接口的数据,用户信息是直接传递到后端,这一篇会记录一下修改登录页面用表单的方式传递用户输入的信息。

期望的功能为:校验表单,登录失败或成功都给予提示,登录成功后跳转,登录成功后再次切换到login页面不再展示表单而是直接显示用户名。

在Login.razor.cs中添加引用,可用上内置的表单和校验,并添加一个类用于绑定表单的字段,同时也能定义上校验的规则

using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Components.Forms;

......
public UserLoginViewModel LoginModel = new();

public class UserLoginViewModel
{
    [Required(ErrorMessage = "用户名不能为空")]
    [StringLength(16, ErrorMessage = "用户名不应超出16个字符长度")]
    public string UserName { get; set; }

    [Required(ErrorMessage = "密码不能为空")]
    public string Password { get; set; }
}

然后修改login.razor,这里用了EditForm,并绑定刚才定义的UserLoginViewModel,定义了用户名输入框和密码输入框和提交按钮,然后绑定到事件OnValidSubmit="LoginWithInput",这代表表单通过校验才会跳至该方法来处理逻辑。DataAnnotationsValidator和ValidationMessage则是用来校验的。

<h3>Test Login</h3>

<div class="row">
    <div class="col-6">
        <EditForm Model="LoginModel" OnValidSubmit="LoginWithInput">
            <DataAnnotationsValidator />
            <div class="mt-2">
                <label class="col-2 form-label">用户名:</label>
                <InputText @bind-Value="LoginModel.UserName" class="form-control col-10" />
                <ValidationMessage For="()=>LoginModel.UserName" />
            </div>
            <div class="mt-2">
                <label class="col-2 form-label">密码:</label>
                <InputText type="password" @bind-Value="LoginModel.Password" class="form-control col-10" />
                <ValidationMessage For="()=>LoginModel.Password" />
            </div>
            <div class="mt-2">
                <button type="submit" class="btn btn-primary">
                    登录
                </button>
            </div>
        </EditForm>
    </div>
</div>

然后添加方法用于表单提交后的处理,失败停留在页面,成功则跳转账户页面。

    public async Task LoginWithInput(EditContext context)
    {
        Console.WriteLine("start to login...");
        token = await _userService.GetTokenAsync(LoginModel.UserName, LoginModel.Password);
        Console.WriteLine($"Token is: {token}");
        if (!string.IsNullOrWhiteSpace(token))
        {
            formMessage = "登录成功, 即将跳转";
            await Task.Delay(500);
            NavigationManager.NavigateTo("/accounts");
        }
        else
        {
            errorMessage = "登录失败";
        }
    }

要记得UserService需要修改为通过传入的参数去请求后端

public async Task<String> GetTokenAsync(string userName, string passWord)
......
            var loginData = new
            {
                username = userName,
                password = passWord
            };
            var loginContent = new StringContent(JsonSerializer.Serialize(loginData), Encoding.UTF8, "application/json");
......

此时还需要加上获取登录状态、用户名,根据登录情况展示信息,都是小修改,放在下面完整的文件内容中。

IUserService.cs

namespace accountingMAUIBlazor.Services;

public interface IUserService
{
    Task<String> GetTokenAsync(string userName, string password);

    bool CheckLoggedStatus();

    string CheckLoginName();

}

UserService.cs

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

namespace accountingMAUIBlazor.Services;

public class UserService : IUserService
{
    public bool IsLoggedIn { get; set; } = false;
    public string loginName;
    private string _token = string.Empty;

    public bool CheckLoggedStatus()
    {
        return IsLoggedIn;
    }

    public string CheckLoginName()
    {
        return loginName;
    }

    public async Task<String> GetTokenAsync(string userName, string passWord)
    {
        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();
            loginName = JsonSerializer.Deserialize<Token>(result)?.username ?? throw new JsonException();
            IsLoggedIn = true;
        }
        catch (Exception e)
        {
            Console.WriteLine("Error when handle json: " + e.Message);
            return _token;

        }

        return _token;
    }

}

Login.razor

@page "/login"
@inject NavigationManager NavigationManager
@inject IUserService _userService


<h3>Test Login</h3>

@if (@isLoggedIn)
{
    <div class="alert alert-success">你好 @loginName, 你已经登录过了</div>
}
else
{
    <div class="row">
        <div class="col-6">
            @if (!string.IsNullOrWhiteSpace(@errorMessage))
            {
                <div class="alert alert-danger">@errorMessage</div>
            }
            @if (!string.IsNullOrWhiteSpace(@formMessage))
            {
                <div class="alert alert-success">@formMessage</div>
            }
            <EditForm Model="LoginModel" OnValidSubmit="LoginWithInput">
                <DataAnnotationsValidator />
                <div class="mt-2">
                    <label class="col-2 form-label">用户名:</label>
                    <InputText @bind-Value="LoginModel.UserName" class="form-control col-10" />
                    <ValidationMessage For="()=>LoginModel.UserName" />
                </div>
                <div class="mt-2">
                    <label class="col-2 form-label">密码:</label>
                    <InputText type="password" @bind-Value="LoginModel.Password" class="form-control col-10" />
                    <ValidationMessage For="()=>LoginModel.Password" />
                </div>
                <div class="mt-2">
                    <button type="submit" class="btn btn-primary">
                        登录
                    </button>
                </div>
            </EditForm>
        </div>
    </div>
}

Login.razor.cs

using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Components.Forms;

namespace accountingMAUIBlazor.Pages;

public partial class Login
{
    public bool isLoggedIn;
    public string loginName;
    private string token;
    public string formMessage;
    public string errorMessage;
    public UserLoginViewModel LoginModel = new();

    protected override void OnInitialized()
    {
        isLoggedIn = _userService.CheckLoggedStatus();
        if (isLoggedIn)
        {
            loginName = _userService.CheckLoginName();
        }
    }

    public async Task LoginWithInput(EditContext context)
    {
        Console.WriteLine("start to login...");
        token = await _userService.GetTokenAsync(LoginModel.UserName, LoginModel.Password);
        Console.WriteLine($"Token is: {token}");
        if (!string.IsNullOrWhiteSpace(token))
        {
            formMessage = "登录成功, 即将跳转";
            await Task.Delay(500);
            NavigationManager.NavigateTo("/accounts");
        }
        else
        {
            errorMessage = "登录失败";
        }
    }
}

public class UserLoginViewModel
{
    [Required(ErrorMessage = "用户名不能为空")]
    [StringLength(16, ErrorMessage = "用户名不应超出16个字符长度")]
    public string UserName { get; set; }

    [Required(ErrorMessage = "密码不能为空")]
    public string Password { get; set; }
}

效果大致如下,当然后面还得美化一下。

接下来要通过登录后获得的token,作为全局的变量去请求后端,后端server也可以作为一个统一的全局变量。

通过server加上接口名,加token到header去请求各接口。同时注意token过期的逻辑,并退出登录状态提示再次登录。

下一篇:  MAUI加Blazor做一个跨平台的记账APP(五)存储信息和http连接
上一篇:  MAUI加Blazor做一个跨平台的记账APP(三)请求后端接口

共有0条评论

添加评论

暂无评论