2021年02月13日 23:26
原创作品,转载时请务必以超链接形式标明文章原始出处,否则将追究法律责任。

之前讲了一个查询新增页面,但是没有登录页面,登录页面其实实现起来是比较麻烦的,因为它牵涉登录注销,从而扯到权限的认证和清除。过年了,学习还得继续。

image.png

我们首先看一下登录页面,俗话说,无图无真相,呵呵。

image.png

没错,很简单的一个页面,一个登录,一个取消,我们来看一下代码

<Card Color="Color.None" Class="ml-3  mt-2" style="width: 30rem;">
    <CardHeader>
        <b>会员登录</b>
    </CardHeader>
    <CardBody>
        <EditForm EditContext="editContext" OnValidSubmit="OnValidSubmit">
            <div class="row">
                <div class="col-md-3">
                    <label class="control-label">用户名:</label>
                </div>
                <div class="col-md-8">
                    <input type="text" @bind-value="@userEntity.UserNo" class="form-control" placeholder="请输入用户名" />
                    <ValidationMessage For="()=>userEntity.UserNo"></ValidationMessage>
                </div>
            </div>
            <div class="margin-t10"></div>
            <div class="row">
                <div class="col-md-3">
                    <label class="control-label">密码:</label>
                </div>
                <div class="col-md-8">
                    <input type="password" @bind-value="userEntity.Password" class="form-control" placeholder="请输入密码" />
                    <ValidationMessage For="()=>userEntity.Password"></ValidationMessage>
                </div>
            </div>
            <div class="margin-t10"></div>
            <div class="row">
                <div class="col-md-3"></div>
                <div class="col-md-8">
                    <Button ButtonType="ButtonType.Submit" Color="Color.Primary">登录</Button>
                    <Button @onclick="@OnCancelButtonClick" Color="Color.Warning">取消</Button>
                </div>
            </div>
            <DataAnnotationsValidator />
        </EditForm>
    </CardBody>
</Card>

使用了一个Bootstrap blazor的组件Card,里面两个文本框,一个是用户名,一个是密码。ok,验证还是使用自带的DataAnnotations验证方式,如下

image.png

ok,在看登录逻辑之前,我们先看一下登录和主页的贴换。我们都知道,默认的Layout是MainLayout.razor。如果把登录页面放到这个Layout页面,恐怕页面顶部的Banner和左侧的导航栏隐藏显示不好控制,所以我们就新建一个Layout叫LoginLayout.razor,同时我们设置Login页面的layout为LoginLayout,如下在Login.razor页面顶部我们设置其Layout为LoginLayout,这就像asp.net mvc页面中的_Layout.cshtml和webform中的母版页一样。

@page  "/system/login"
@layout LoginLayout

我们来看一下LoginLayout的代码,非常的easy,就只是展示一个@Body的占位符

@inherits LayoutComponentBase

<div id="main_layout">
   @Body
</div>

@code {

}

仅仅这样还不够,因为我们系统启动起来会进入index组件("/"),所以是不能直接到登录页面的,因此我们需要在MainLayout中去判断如果用户没有登录就自动跳转至登录页面。MainLayout是什么呢,就是我们之前讲过的主页面,如下

image.png


在这个主页面里面我们去判断是否登录,如下

protected async override void OnInitialized()
{
    var userJson = await JSRuntime.GetLocalStorage("login_user");
    try
    {
        if (!string.IsNullOrEmpty(userJson))
        {
            currentUser = userJson.DeserializeFromJson<UserCreditEntity>();
        }
    }
    catch
    {
    }

    if (currentUser == null)
    {
        Navigation.NavigateTo("/system/login", false);
    }

    var timer = new System.Threading.Timer((_) =>
    {
        dateNow = DateTime.Now;
        InvokeAsync(() =>
        {
            StateHasChanged();
        });
    }, null, 0, 1000);
    base.OnInitialized();
}

很简单,就是去判断LocalStorage中有没有用户信息,如果没有,就跳转至登录页面。对于主页面的注销,就是清除用户信息,清除token信息。

private async Task LogOut()
{
    await JSRuntime.RemoveLocalStorage("login_user");
    await JSRuntime.RemoveLocalStorage("access_token");
    Navigation.NavigateTo("/system/login");
}

在这里我们用到了js和.net的互操作,.net可以调用js function,js也可以调用.net method。在这里我们封装了对LocalStorage和SessionStorage的操作,首选是js部分,如下

function SetLocalStorage(key, value) {
    localStorage.setItem(key, value);
}

function GetLocalStorage(key) {
    return localStorage.getItem(key);
}

function RemoveLocalStorage(key) {
    return localStorage.removeItem(key);
}

然后我们需要把这个js文件引用到wwwroot下面的index.html里面,引用完成后我们就可以使用C#去调用了

public static async Task SetLocalStorage(this IJSRuntime _jsRuntime, string key, string value)
{
    await _jsRuntime.InvokeVoidAsync("SetLocalStorage", key, value);
}

public static async Task<string> GetLocalStorage(this IJSRuntime _jsRuntime, string key)
{
    return await _jsRuntime.InvokeAsync<string>("GetLocalStorage", key);
}

public static async Task RemoveLocalStorage(this IJSRuntime _jsRuntime, string key)
{
    await _jsRuntime.InvokeVoidAsync("RemoveLocalStorage", key);
}

主要是使用IJSRuntime的InvokeAsync和InvokeVoidAsync方法,一个是调用有返回值的函数,一个是调用没有返回值的函数。OK,主页面这里就简单实现了,

我们看一下登录页面的逻辑代码。

点击登录,验证通过后进入OnValidSubmit方法,代码如下

private async Task OnValidSubmit()
{
    var userGetUrl = string.Format(APIUrlDefination.USER_GET, userEntity.UserNo, userEntity.Password);
    var response = await Http.GetAsync(userGetUrl);

    if (response.StatusCode == HttpStatusCode.NoContent)
    {
        await JSRuntime.ShowError("用户名或者密码不正确!");
        return;
    }

    var userCredit = await response.Content.ReadFromJsonAsync<UserCreditEntity>();
    await JSRuntime.SetLocalStorage("login_user", userCredit.ToJson());
    userCredit.ClientID = "membership_19861017";

    if (Http.DefaultRequestHeaders.Contains(USER_CREDIT_HEADER))
    {
        Http.DefaultRequestHeaders.Remove(USER_CREDIT_HEADER);
    }

    Http.DefaultRequestHeaders.Add(USER_CREDIT_HEADER, new List<string> { userCredit.ClientID, userCredit.UserNo, userCredit.Password });

    try
    {
        var tokenResult = await Http.PostAsync(APIUrlDefination.TOKEN_GENERATE, null);
        var tokenEntity = tokenResult.Content.ReadFromJsonAsync<TokenResult>();
        await JSRuntime.SetLocalStorage("access_token", tokenEntity.ToJson());
    }
    catch
    {
        await JSRuntime.ShowError("用户授权认证失败!");
        return;
    }

    NavigationManager.NavigateTo("/");
}

调用Get api如果没有查询到用户,即http status code是204 no content,提示用户名或密码不正确,效果如下,这里的css和啥地方冲突了,导致内容偏右。

image.png

当登录成功后,我们把用户信息从response读取出来,反序列化为实体对象,存入LocalStorage中,同时由于api端有权限认证,所以我们得把ClientID传递过去。最终我们的请求头里面的userno,password,clientID会送到获取JWT的api去请求一个access_token。如果请求成功我们也会把token放到本地存储,对于以后的api,直接拿这个token去请求api,请求权限认证失败了api端会返回401,客户端这边会自动跳转至登录页面再次获取token。对于token这块,讲起来比较麻烦,我们在后面的章节再述。

OK,最后我们输入正确的用户名和密码,登录一下看看本地存储,一个是user信息,一个是access_token。

image.png

好的,今天就讲到这里,下一篇我们换服务端,讲讲swagger,JWT认证。

发表评论
匿名  
用户评论
暂无评论