從零開始Blazor Server(9)--修改Layout

2022-08-09 12:00:25

目前我們的MainLayout還是預設的,這裡我們需要修改為BootstrapBlazor的Layout,並且處理一下選單。

修改MainLayout

BootstrapBlazor已經自帶了一個Layout元件,這個元件裡常用功能已經很全了,所以我們直接使用這個元件即可。

<Layout SideWidth="0" IsPage="true" IsFullSide="true" IsFixedHeader="true" IsFixedFooter="true"
        ShowFooter="true" ShowCollapseBar="true" OnCollapsed="@OnCollapsed" Menus="@_menuItems">
    <Header>
            <span class="ms-3 flex-sm-fill d-none d-sm-block">BlazorLearn</span>
        <div class="flex-fill d-sm-none">
        </div>
        <Logout ImageUrl="images/argo-c.png" DisplayName="@_user.Name" UserName="@_user.UserName">
                    <LinkTemplate>
                        <LogoutLink Url="/api/account/logout"></LogoutLink>
                    </LinkTemplate>
                </Logout>
    </Header>
    <Side>
            <div class="layout-banner">
                <img class="layout-logo" src="images/Argo.png" />
                <div class="layout-title">
                    <span>BlazorLearn</span>
                </div>
            </div>
        </Side>
    <Main>
        <CascadingValue Value="this" IsFixed="true">
            @Body
        </CascadingValue>
    </Main>
    <Footer>
        <div class="text-center flex-fill">
            <a href="/" target="_blank">BlazorLearn</a>
        </div>
    </Footer>
</Layout>

@code
{
    private bool IsCollapsed { get; set; }
    
    private List<MenuItem>? _menuItems;

    [NotNull]
    private UserEntity? _user;
    
    private Task OnCollapsed(bool collapsed)
    {
        IsCollapsed = collapsed;
        return Task.CompletedTask;
    }
    
    protected override void OnInitialized()
    {
        base.OnInitialized();
        _user = UserEntity.Where(x => x.UserName == Furion.App.User.FindFirstValue(ClaimTypes.Name)).First();
        if (_user == null)
        {
            return;
        }
        _menuItems = CreateMenuItems(MenuEntity.Where(x => x.Roles!.Any(y => y.Id == _user.RoleId)).ToList(), 0);
    }

    private List<MenuItem> CreateMenuItems(List<MenuEntity> menus, int parentId)
    {
        var selectedMenus = new List<MenuItem>();
        var selectedMenuEntities = menus.Where(x => x.ParentId == parentId).ToList();

        foreach (var menuEntity in selectedMenuEntities)
        {
            var menuItem = new MenuItem(menuEntity.Name!, menuEntity.Url, menuEntity.Icon);
            menuItem.Items = CreateMenuItems(menus, menuEntity.Id);
            selectedMenus.Add(menuItem);
        }
        return selectedMenus;
    }
}

這裡沒什麼需要多說的,每個引數的意義在檔案裡都比較清楚,如果需要查詢具體的含義,可以看這裡


這裡需要注意的是,Menus裡面必須要定義一個變數,不能直接放一個方法,否則此方法每次跳轉都會執行,導致選單不正常。


另外Logout是一個獨立的元件,這個元件其實叫Logout並不貼切,它是一個帶有頭像,歡迎資訊以及下拉式選單的使用者資訊元件。


這裡我們只放一個LogoutLink登出選單。

修改AdminHandler

如果你直接啟動專案,會發現Layout不見了,因為我們的Layout裡面做了比較多的處理,會有一個需要許可權驗證的請求。這個請求不會攜帶Resource,所以不會返回true。就導致Layout一直不顯示,所以我們需要處理這種情況,我們目前修改為Resource裡不是RouteData的全部都通過驗證。

    public override Task<bool> PipelineAsync(AuthorizationHandlerContext context, DefaultHttpContext httpContext)
    {
        if (!int.TryParse(context.User.FindFirst(ClaimTypes.Role)?.Value, out var roleId))
        {
            return Task.FromResult(false);
        }
        if (context.Resource is RouteData routeData)
        {
            var routeAttr = routeData.PageType.CustomAttributes.FirstOrDefault(x =>
                x.AttributeType == typeof(RouteAttribute));
            if (routeAttr == null)
            {
                return Task.FromResult(true);
            }
            else
            {
                var url = routeAttr.ConstructorArguments[0].Value as string;
                var permission = MenuEntity
                    .Where(x => x.Roles!.Any(y => y.Id == roleId) && x.Url == url).First();
                if (permission != null)
                {
                    return Task.FromResult(true);
                }
            }
        }
        else
        {
            return Task.FromResult(true);
        }
        
        return Task.FromResult(false);
    }

這樣我們再啟動應該就可以看到Layout了。

修改LoginController

我們之前只有一個登入,所以我們寫了一個LoginController,現在我們需要加入登出,所以我們直接把LoginController改為AccountController,然後內容改為PostLoginGetLogout

public class AccountController: IDynamicApiController
{
    public async Task<object> PostLogin([FromBody]LoginVo loginVo)
    {
        if (string.IsNullOrEmpty(loginVo.UserName))
        {
            return new { code = 50000, message = "使用者名稱不能為空" };
        }
        if (string.IsNullOrEmpty(loginVo.Password))
        {
            return new { code = 50000, message = "密碼不能為空" };
        }

        var password = MD5Encryption.Encrypt(loginVo.Password);
        var user = await UserEntity.Where(x =>
            x.UserName == loginVo.UserName && x.Password == password).Include(x => x.Role).FirstAsync();
        if (user != null)
        {
            var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
            identity.AddClaim(new Claim(ClaimTypes.Name, user.UserName!));
            identity.AddClaim(new Claim(ClaimTypes.Role, user.Role!.Id.ToString()));
            await Furion.App.HttpContext.SignInAsync(new ClaimsPrincipal(identity), new AuthenticationProperties(){IsPersistent = true, ExpiresUtc = loginVo.RememberMe? DateTimeOffset.Now.AddDays(5): DateTimeOffset.Now.AddMinutes(30)});

            return new { code = 20000, message = "登入成功" };
        }
        return new { code = 50000, message = "使用者名稱或密碼錯誤" };
    }

    [Authorize]
    public async Task<IActionResult> GetLogout()
    {
        await Furion.App.HttpContext.SignOutAsync();
        return new RedirectResult("/Login");
    }
}

這裡我們直接給Logout[Authorize],只有登入以後才能存取。


程式碼在github:https://github.com/j4587698/BlazorLearnj,分支lesson9。