之前讲了一个查询新增页面,但是没有登录页面,登录页面其实实现起来是比较麻烦的,因为它牵涉登录注销,从而扯到权限的认证和清除。过年了,学习还得继续。
我们首先看一下登录页面,俗话说,无图无真相,呵呵。
没错,很简单的一个页面,一个登录,一个取消,我们来看一下代码
<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验证方式,如下
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是什么呢,就是我们之前讲过的主页面,如下
在这个主页面里面我们去判断是否登录,如下
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和啥地方冲突了,导致内容偏右。
当登录成功后,我们把用户信息从response读取出来,反序列化为实体对象,存入LocalStorage中,同时由于api端有权限认证,所以我们得把ClientID传递过去。最终我们的请求头里面的userno,password,clientID会送到获取JWT的api去请求一个access_token。如果请求成功我们也会把token放到本地存储,对于以后的api,直接拿这个token去请求api,请求权限认证失败了api端会返回401,客户端这边会自动跳转至登录页面再次获取token。对于token这块,讲起来比较麻烦,我们在后面的章节再述。
OK,最后我们输入正确的用户名和密码,登录一下看看本地存储,一个是user信息,一个是access_token。
好的,今天就讲到这里,下一篇我们换服务端,讲讲swagger,JWT认证。