feat(module: tabs): Add AuthorizeReuseTabsRouteView (#1910)

* 使MenItem同时支持Icon和IconFont,解决当使用iconfont.cn图标后,缩起内嵌菜单导致按钮样式不统一的问题。

* 空值判断

* add IconTemplate in MenuItem

* code format

* Add AuthorizeReuseTabsRouteView

* add readme.md

* remove comments

* Add AuthorizeResuseTabsRouteView test demo

* save

* save

* done

* refactor the test app

* fix test app

* debug

* debug

* fix tab render

* fix the copyright

* fix package infomation

* fix tab display order

* fix content focerender

Co-authored-by: BG345554 <ymgu@best-inc.com>
Co-authored-by: James Yeung <shunjiey@hotmail.com>
This commit is contained in:
Guyiming 2021-10-11 23:01:27 +08:00 committed by GitHub
parent 5c2f48f718
commit cc836bcf10
51 changed files with 2285 additions and 49 deletions

View File

@ -87,6 +87,7 @@ jobs:
dotnet build
dotnet pack components/AntDesign.csproj /p:PackageVersion=${FULL_VERSION} -c Release -o publish
dotnet pack tests/AntDesign.TestKit/AntDesign.TestKit.csproj /p:PackageVersion=${FULL_VERSION} -c Release -o publish
dotnet pack src/AntDesign.Components.Authentication/AntDesign.Components.Authentication.csproj /p:PackageVersion=${FULL_VERSION} -c Release -o publish
env:
next_version: ${{ steps.get_next_version.outputs.next_version }}

View File

@ -39,6 +39,7 @@ jobs:
dotnet build
dotnet pack components/AntDesign.csproj /p:PackageVersion=$VERSION -c Release -o publish
dotnet pack tests/AntDesign.TestKit/AntDesign.TestKit.csproj /p:PackageVersion=$VERSION -c Release -o publish
dotnet pack src/AntDesign.Components.Authentication/AntDesign.Components.Authentication.csproj /p:PackageVersion=$VERSION -c Release -o publish
dotnet nuget push publish/*.nupkg -s https://api.nuget.org/v3/index.json -k $NUGET_API_KEY --skip-duplicate
- name: Upload package artifact

View File

@ -35,9 +35,17 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AntDesign.Tests", "tests\An
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{DFD13180-D1BF-44DA-BEBE-4A54EFDEFFE2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AntDesign.Tests.Js", "tests\AntDesign.Tests.Js\AntDesign.Tests.Js.csproj", "{3C4ADCD5-6879-4478-9BA5-28C894AD52F3}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AntDesign.Tests.Js", "tests\AntDesign.Tests.Js\AntDesign.Tests.Js.csproj", "{3C4ADCD5-6879-4478-9BA5-28C894AD52F3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AntDesign.Docs.Build", "site\AntDesign.Docs.Build\AntDesign.Docs.Build.csproj", "{67E9D6C5-106F-412C-B43C-F096145FD8A9}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AntDesign.Docs.Build", "site\AntDesign.Docs.Build\AntDesign.Docs.Build.csproj", "{67E9D6C5-106F-412C-B43C-F096145FD8A9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AntDesign.Components.Authentication", "src\AntDesign.Components.Authentication\AntDesign.Components.Authentication.csproj", "{88E00D83-912A-4AC4-9A6D-34F87DC3A675}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AntDesign.TestApp", "AntDesign.TestApp", "{606789E3-AFE9-4489-9963-2B06A701D6B6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AntDesign.TestApp.Server", "tests\AntDesign.TestApp\Server\AntDesign.TestApp.Server.csproj", "{32A251DB-FF95-4325-B80F-0E3B0FA5CBC5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AntDesign.TestApp.Client", "tests\AntDesign.TestApp\Client\AntDesign.TestApp.Client.csproj", "{5ADDB648-9417-4FE9-8A7D-0D4BF2DD3EC5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -77,14 +85,26 @@ Global
{51D7507D-48BA-43BB-8223-CE35A2D7D0D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{51D7507D-48BA-43BB-8223-CE35A2D7D0D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{51D7507D-48BA-43BB-8223-CE35A2D7D0D8}.Release|Any CPU.Build.0 = Release|Any CPU
{67E9D6C5-106F-412C-B43C-F096145FD8A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{67E9D6C5-106F-412C-B43C-F096145FD8A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{67E9D6C5-106F-412C-B43C-F096145FD8A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{67E9D6C5-106F-412C-B43C-F096145FD8A9}.Release|Any CPU.Build.0 = Release|Any CPU
{3C4ADCD5-6879-4478-9BA5-28C894AD52F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3C4ADCD5-6879-4478-9BA5-28C894AD52F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3C4ADCD5-6879-4478-9BA5-28C894AD52F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3C4ADCD5-6879-4478-9BA5-28C894AD52F3}.Release|Any CPU.Build.0 = Release|Any CPU
{67E9D6C5-106F-412C-B43C-F096145FD8A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{67E9D6C5-106F-412C-B43C-F096145FD8A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{67E9D6C5-106F-412C-B43C-F096145FD8A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{67E9D6C5-106F-412C-B43C-F096145FD8A9}.Release|Any CPU.Build.0 = Release|Any CPU
{88E00D83-912A-4AC4-9A6D-34F87DC3A675}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{88E00D83-912A-4AC4-9A6D-34F87DC3A675}.Debug|Any CPU.Build.0 = Debug|Any CPU
{88E00D83-912A-4AC4-9A6D-34F87DC3A675}.Release|Any CPU.ActiveCfg = Release|Any CPU
{88E00D83-912A-4AC4-9A6D-34F87DC3A675}.Release|Any CPU.Build.0 = Release|Any CPU
{32A251DB-FF95-4325-B80F-0E3B0FA5CBC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{32A251DB-FF95-4325-B80F-0E3B0FA5CBC5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{32A251DB-FF95-4325-B80F-0E3B0FA5CBC5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{32A251DB-FF95-4325-B80F-0E3B0FA5CBC5}.Release|Any CPU.Build.0 = Release|Any CPU
{5ADDB648-9417-4FE9-8A7D-0D4BF2DD3EC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5ADDB648-9417-4FE9-8A7D-0D4BF2DD3EC5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5ADDB648-9417-4FE9-8A7D-0D4BF2DD3EC5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5ADDB648-9417-4FE9-8A7D-0D4BF2DD3EC5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -98,8 +118,12 @@ Global
{5A765767-0766-433D-B78D-97D8F29CA6A6} = {D34F1DE5-ECF7-4CF0-8325-B7A38F41D376}
{7193BE83-12C1-4B35-8197-C28D20FBA1E6} = {DFD13180-D1BF-44DA-BEBE-4A54EFDEFFE2}
{51D7507D-48BA-43BB-8223-CE35A2D7D0D8} = {DFD13180-D1BF-44DA-BEBE-4A54EFDEFFE2}
{67E9D6C5-106F-412C-B43C-F096145FD8A9} = {D34F1DE5-ECF7-4CF0-8325-B7A38F41D376}
{3C4ADCD5-6879-4478-9BA5-28C894AD52F3} = {DFD13180-D1BF-44DA-BEBE-4A54EFDEFFE2}
{67E9D6C5-106F-412C-B43C-F096145FD8A9} = {D34F1DE5-ECF7-4CF0-8325-B7A38F41D376}
{88E00D83-912A-4AC4-9A6D-34F87DC3A675} = {C60BCE84-4AF4-4393-8D3E-1B69E29549C1}
{606789E3-AFE9-4489-9963-2B06A701D6B6} = {DFD13180-D1BF-44DA-BEBE-4A54EFDEFFE2}
{32A251DB-FF95-4325-B80F-0E3B0FA5CBC5} = {606789E3-AFE9-4489-9963-2B06A701D6B6}
{5ADDB648-9417-4FE9-8A7D-0D4BF2DD3EC5} = {606789E3-AFE9-4489-9963-2B06A701D6B6}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E124DDCB-1F8D-4F96-BF41-D87019D0A412}

View File

@ -2,18 +2,21 @@
@inherits AntDomComponentBase
<Tabs Class="@Class" Style="@Style" Id="@Id" HideAdd Type="@TabType.EditableCard" @bind-ActiveKey="@CurrentUrl" OnClose="RemovePage">
@foreach (var item in Pages)
@if (Pages != null)
{
<TabPane @key="@item.Url" Key="@item.Url" Class="@TabPaneClass" Closable ForceRender>
<TabTemplate>
@item.Title
</TabTemplate>
<ChildContent>
@if (Body != null)
{
@Body(item)
}
</ChildContent>
</TabPane>
@foreach (var item in Pages)
{
<TabPane @key="@item.Url" Key="@item.Url" Class="@TabPaneClass" Closable ForceRender>
<TabTemplate>
@item.Title
</TabTemplate>
<ChildContent>
@if (Body != null)
{
@Body(item)
}
</ChildContent>
</TabPane>
}
}
</Tabs>

View File

@ -16,7 +16,7 @@ namespace AntDesign
[CascadingParameter(Name = "RouteView")]
public ReuseTabsRouteView RouteView { get; set; }
internal ReuseTabsPageItem[] Pages => RouteView.Pages;
internal ReuseTabsPageItem[] Pages => RouteView?.Pages;
protected string CurrentUrl
{

View File

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Components;
using System;
using Microsoft.AspNetCore.Components;
namespace AntDesign
{
@ -6,6 +7,8 @@ namespace AntDesign
{
public string Url { get; set; }
public DateTime CreatedAt { get; set; }
public RenderFragment Title { get; set; }
public RenderFragment Body { get; set; }

View File

@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
namespace AntDesign
{
@ -19,12 +20,22 @@ namespace AntDesign
private string CurrentUrl => Navmgr.Uri;
internal ReuseTabsPageItem[] Pages => _pageMap.Values.ToArray();
internal ReuseTabsPageItem[] Pages => _pageMap.Values.OrderBy(x => x.CreatedAt).ToArray();
public void RemovePage(string key)
{
_pageMap.Remove(key);
}
public void RemovePageWithRegex(string pattern)
{
foreach (var key in _pageMap.Keys)
{
if (Regex.IsMatch(key, pattern))
{
_pageMap.Remove(key);
}
}
}
public void ReplaceBody(string key, RenderFragment body)
{
@ -50,7 +61,6 @@ namespace AntDesign
builder.AddAttribute(3, "ChildContent", (RenderFragment)(b =>
{
b.OpenComponent(20, layoutType);
b.AddAttribute(21, "Body", body);
b.CloseComponent();
}));
}
@ -63,6 +73,7 @@ namespace AntDesign
{
Body = body,
Url = CurrentUrl,
CreatedAt = DateTime.Now,
};
}
}

View File

@ -3,7 +3,7 @@
@if (_hasClosed)
{
return;
return;
}
@if (IsTab)
@ -12,18 +12,18 @@
var onclickStopPropagation = !_isActive;
<div @key="Key"
@ref="_tabRef"
@onclick:stopPropagation="@onclickStopPropagation"
@onclick="@(e => Parent.HandleTabClick(this))"
@ondragover:preventDefault="@ondragoverPreventDefault"
@ondragstart="@(e => Parent.HandleDragStart(e, this))"
@ondrop="@(_ => Parent.HandleDrop(this))"
aria-controls="@($"rc-tabs-{Id}-tab-{Key}")"
aria-selected="@(_isActive)"
class="@ClassMapper.Class"
draggable="@Parent.Draggable.ToString()"
id="@($"rc-tabs-{Id}-tab-{Key}")"
ondragover="event.preventDefault();">
@ref="_tabRef"
@onclick:stopPropagation="@onclickStopPropagation"
@onclick="@(e => Parent.HandleTabClick(this))"
@ondragover:preventDefault="@ondragoverPreventDefault"
@ondragstart="@(e => Parent.HandleDragStart(e, this))"
@ondrop="@(_ => Parent.HandleDrop(this))"
aria-controls="@($"rc-tabs-{Id}-tab-{Key}")"
aria-selected="@(_isActive)"
class="@ClassMapper.Class"
draggable="@Parent.Draggable.ToString()"
id="@($"rc-tabs-{Id}-tab-{Key}")"
ondragover="event.preventDefault();">
<div role="tab" aria-selected="false" class="ant-tabs-tab-btn" tabindex="0">
@if (TabTemplate != null)
{
@ -45,16 +45,16 @@
else if (IsPane)
{
<div @key="Key"
tabindex="@(_isActive ? "0" : "-1")"
class="ant-tabs-tabpane @(_isActive ? "ant-tabs-tabpane-active" : "")"
id="@($"rc-tabs-{Id}-panel-{Key}")"
role="tabpanel" aria-hidden="@(_isActive ? "false" : "true")"
aria-labelledby="@($"rc-tabs-{Id}-panel-{Key}")"
style="@(_isActive || Parent.Animated ? "" : "display: none;")">
@if (_isActive || _hasRendered || ForceRender)
{
_hasRendered = true;
@ChildContent
}
tabindex="@(_isActive ? "0" : "-1")"
class="ant-tabs-tabpane @(_isActive ? "ant-tabs-tabpane-active" : "")"
id="@($"rc-tabs-{Id}-panel-{Key}")"
role="tabpanel" aria-hidden="@(_isActive ? "false" : "true")"
aria-labelledby="@($"rc-tabs-{Id}-panel-{Key}")"
style="@(_isActive || Parent.Animated ? "" : "display: none;")">
@if (_isActive || _hasRendered || ForceRender)
{
_hasRendered = true;
@ChildContent
}
</div>
}

View File

@ -52,6 +52,8 @@ namespace AntDesign
public bool Closable { get; set; } = true;
internal bool IsActive => _isActive;
private bool HasTabTitle => Tab != null || TabTemplate != null;
internal ElementReference TabRef => _tabRef;
private const string PrefixCls = "ant-tabs-tab";
@ -76,6 +78,11 @@ namespace AntDesign
{
base.OnAfterRender(firstRender);
if (IsTab && HasTabTitle)
{
_hasRendered = true;
}
_shouldRender = false;
_shouldTabRender = false;
}
@ -84,15 +91,18 @@ namespace AntDesign
{
base.OnParametersSet();
_shouldRender = true;
_shouldTabRender = true;
}
public override async Task SetParametersAsync(ParameterView parameters)
{
// Avoid changes in tab as we modify the properties used for display when drag and drop occurs
if (!IsTab)
if (IsTab && _hasRendered)
{
await base.SetParametersAsync(parameters);
return;
}
await base.SetParametersAsync(parameters);
}
private void SetClass()

View File

@ -0,0 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.1;net5</TargetFrameworks>
<LangVersion>9.0</LangVersion>
<OutputType>Library</OutputType>
<IsPackable>true</IsPackable>
<RazorLangVersion>3.0</RazorLangVersion>
<Description>🌈 An authentication extensions components for Ant Design Blazor library. </Description>
<PackageProjectUrl>https://github.com/ant-design-blazor/ant-design-blazor</PackageProjectUrl>
<RepositoryUrl>https://github.com/ant-design-blazor/ant-design-blazor</RepositoryUrl>
<PackageTags>blazor,ant-design,antd,design,razor,components</PackageTags>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<RepositoryType>git</RepositoryType>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Copyright>.NET Foundation and Contributors</Copyright>
<Authors>James Yeung</Authors>
<PackageIcon>logo.png</PackageIcon>
<NoWarn>CA2007</NoWarn>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<ItemGroup Condition="$(TargetFramework) == 'netstandard2.1'">
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="3.1.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net5'">
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\components\AntDesign.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,54 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Authorization;
namespace AntDesign.Components.Authentication
{
/// <summary>
/// This class is copy from the official repo.
/// source: https://github.com/dotnet/aspnetcore/blob/main/src/Components/Authorization/src/AttributeAuthorizeDataCache.cs
/// </summary>
internal static class AttributeAuthorizeDataCache
{
private static readonly ConcurrentDictionary<Type, IAuthorizeData[]?> _cache = new ConcurrentDictionary<Type, IAuthorizeData[]?>();
public static IAuthorizeData[]? GetAuthorizeDataForType(Type type)
{
if (!_cache.TryGetValue(type, out var result))
{
result = ComputeAuthorizeDataForType(type);
_cache[type] = result; // Safe race - doesn't matter if it overwrites
}
return result;
}
private static IAuthorizeData[]? ComputeAuthorizeDataForType(Type type)
{
// Allow Anonymous skips all authorization
var allAttributes = type.GetCustomAttributes(inherit: true);
List<IAuthorizeData>? authorizeDatas = null;
for (var i = 0; i < allAttributes.Length; i++)
{
if (allAttributes[i] is IAllowAnonymous)
{
return null;
}
if (allAttributes[i] is IAuthorizeData authorizeData)
{
authorizeDatas ??= new List<IAuthorizeData>();
authorizeDatas.Add(authorizeData);
}
}
return authorizeDatas?.ToArray();
}
}
}

View File

@ -0,0 +1,136 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Rendering;
namespace AntDesign.Components.Authentication
{
/// <summary>
/// This class is modify from the official AuthorizeRouteView.cs
/// source: https://github.com/dotnet/aspnetcore/blob/main/src/Components/Authorization/src/AuthorizeRouteView.cs
///
/// Combines the behaviors of <see cref="AuthorizeView"/> and <see cref="RouteView"/>,
/// so that it displays the page matching the specified route but only if the user
/// is authorized to see it.
///
/// Additionally, this component supplies a cascading parameter of type <see cref="Task{AuthenticationState}"/>,
/// which makes the user's current authentication state available to descendants.
/// </summary>
public sealed class AuthorizeReuseTabsRouteView : ReuseTabsRouteView
{
// We expect applications to supply their own authorizing/not-authorized content, but
// it's better to have defaults than to make the parameters mandatory because in some
// cases they will never be used (e.g., "authorizing" in out-of-box server-side Blazor)
private static readonly RenderFragment<AuthenticationState> _defaultNotAuthorizedContent
= state => builder => builder.AddContent(0, "Not authorized");
private static readonly RenderFragment _defaultAuthorizingContent
= builder => builder.AddContent(0, "Authorizing...");
private readonly RenderFragment _renderAuthorizeRouteViewCoreDelegate;
private readonly RenderFragment<AuthenticationState> _renderAuthorizedDelegate;
private readonly RenderFragment<AuthenticationState> _renderNotAuthorizedDelegate;
private readonly RenderFragment _renderAuthorizingDelegate;
/// <summary>
/// Initialize a new instance of a <see cref="AuthorizeRouteView"/>.
/// </summary>
public AuthorizeReuseTabsRouteView()
{
// Cache the rendering delegates so that we only construct new closure instances
// when they are actually used (e.g., we never prepare a RenderFragment bound to
// the NotAuthorized content except when you are displaying that particular state)
RenderFragment renderBaseRouteViewDelegate = builder => base.Render(builder);
_renderAuthorizedDelegate = authenticateState => renderBaseRouteViewDelegate;
_renderNotAuthorizedDelegate = authenticationState => builder => RenderNotAuthorizedInDefaultLayout(builder, authenticationState);
_renderAuthorizingDelegate = RenderAuthorizingInDefaultLayout;
_renderAuthorizeRouteViewCoreDelegate = RenderAuthorizeRouteViewCore;
}
/// <summary>
/// The content that will be displayed if the user is not authorized.
/// </summary>
[Parameter]
public RenderFragment<AuthenticationState>? NotAuthorized { get; set; }
/// <summary>
/// The content that will be displayed while asynchronous authorization is in progress.
/// </summary>
[Parameter]
public RenderFragment? Authorizing { get; set; }
/// <summary>
/// The resource to which access is being controlled.
/// </summary>
[Parameter]
public object? Resource { get; set; }
[CascadingParameter]
private Task<AuthenticationState>? ExistingCascadedAuthenticationState { get; set; }
/// <inheritdoc />
protected override void Render(RenderTreeBuilder builder)
{
if (ExistingCascadedAuthenticationState != null)
{
// If this component is already wrapped in a <CascadingAuthenticationState> (or another
// compatible provider), then don't interfere with the cascaded authentication state.
_renderAuthorizeRouteViewCoreDelegate(builder);
}
else
{
// Otherwise, implicitly wrap the output in a <CascadingAuthenticationState>
builder.OpenComponent<CascadingAuthenticationState>(0);
builder.AddAttribute(1, nameof(CascadingAuthenticationState.ChildContent), _renderAuthorizeRouteViewCoreDelegate);
builder.CloseComponent();
}
}
private void RenderAuthorizeRouteViewCore(RenderTreeBuilder builder)
{
builder.OpenComponent<AuthorizeRouteViewCore>(0);
builder.AddAttribute(1, nameof(AuthorizeRouteViewCore.RouteData), RouteData);
builder.AddAttribute(2, nameof(AuthorizeRouteViewCore.Authorized), _renderAuthorizedDelegate);
builder.AddAttribute(3, nameof(AuthorizeRouteViewCore.Authorizing), _renderAuthorizingDelegate);
builder.AddAttribute(4, nameof(AuthorizeRouteViewCore.NotAuthorized), _renderNotAuthorizedDelegate);
builder.AddAttribute(5, nameof(AuthorizeRouteViewCore.Resource), Resource);
builder.CloseComponent();
}
//[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2111:RequiresUnreferencedCode",
// Justification = "OpenComponent already has the right set of attributes")]
//[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2110:RequiresUnreferencedCode",
// Justification = "OpenComponent already has the right set of attributes")]
private void RenderContentInDefaultLayout(RenderTreeBuilder builder, RenderFragment content)
{
builder.OpenComponent<LayoutView>(0);
builder.AddAttribute(1, nameof(LayoutView.Layout), DefaultLayout);
builder.AddAttribute(2, nameof(LayoutView.ChildContent), content);
builder.CloseComponent();
}
private void RenderNotAuthorizedInDefaultLayout(RenderTreeBuilder builder, AuthenticationState authenticationState)
{
var content = NotAuthorized ?? _defaultNotAuthorizedContent;
RenderContentInDefaultLayout(builder, content(authenticationState));
}
private void RenderAuthorizingInDefaultLayout(RenderTreeBuilder builder)
{
var content = Authorizing ?? _defaultAuthorizingContent;
RenderContentInDefaultLayout(builder, content);
}
private sealed class AuthorizeRouteViewCore : AuthorizeViewCore
{
[Parameter]
public RouteData RouteData { get; set; } = default!;
protected override IAuthorizeData[]? GetAuthorizeData()
=> AttributeAuthorizeDataCache.GetAuthorizeDataForType(RouteData.PageType);
}
}
}

View File

@ -0,0 +1,63 @@
# AuthorizeReuseTabsRouteView
A combination of `ReuseTabsRouteView` and `AuthorizeRouteView`.
# How to use
Almost the same as `AuthorizeRouteView`.
1. Modify the `App.razor` file, replace the `RouteView` or `ReuseTabsRouteView` with `AuthorizeReuseTabsRouteView`.
```diff
+<CascadingAuthenticationState>
<Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
<Found Context="routeData">
- <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
+ <AuthorizeReuseTabsRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
+ <NotAuthorized>
+ <RedirectToLogin />
+ </NotAuthorized>
+ <Authorizing>
+ <p>Authorizing............</p>
+ </Authorizing>
+ </AuthorizeReuseTabsRouteView>
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
+</CascadingAuthenticationState>
```
2. Then modify the `MainLayout.razor` file, add the `ReuseTabs` component. Note that `@Body` is **required** at this case, so you can perform redirect and other actions.
```diff
@inherits LayoutComponentBase
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<div class="main">
- <div class="top-row px-4">
- <a href="http://blazor.net" target="_blank" class="ml-md-auto">About</a>
- </div>
<div class="content px-4">
@Body
</div>
+ <ReuseTabs Class="top-row px-4" TabPaneClass="content px-4" / >
</div>
</div>
```
# Customize tab title
Same as `ReuseTabsRouteView`

View File

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="5.0.9" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="5.0.9" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="5.0.9" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
<PackageReference Include="System.Net.Http.Json" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\components\AntDesign.csproj" />
<ProjectReference Include="..\..\..\src\AntDesign.Components.Authentication\AntDesign.Components.Authentication.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,26 @@
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
<Found Context="routeData">
<AntDesign.Components.Authentication.AuthorizeReuseTabsRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
<NotAuthorized>
@if (!context.User.Identity.IsAuthenticated)
{
<RedirectToLogin />
}
else
{
<p>You are not authorized to access this resource.</p>
}
</NotAuthorized>
</AntDesign.Components.Authentication.AuthorizeReuseTabsRouteView>
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
<AntContainer />

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace AntDesign.TestApp.Client.Data
{
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureC { get; set; }
public string Summary { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
}

View File

@ -0,0 +1,25 @@
@page "/authentication/{action}"
@attribute [ReuseTabsPageTitle("Authentication")]
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
<RemoteAuthenticatorView Action="@Action" OnLogInSucceeded="OnLogInSucceed" OnLogOutSucceeded="OnLogOutSucceed" />
@code{
[Parameter] public string Action { get; set; }
[CascadingParameter(Name = "RouteView")]
public ReuseTabsRouteView RouteView { get; set; }
private void OnLogInSucceed(RemoteAuthenticationState state)
{
this.RouteView?.RemovePageWithRegex("/authentication/");
}
private void OnLogOutSucceed(RemoteAuthenticationState state)
{
this.RouteView?.RemovePageWithRegex("/authentication/");
state.ReturnUrl = "/";
}
}

View File

@ -0,0 +1,23 @@
@page "/counter/{id}"
@inherits ComponentBase
@implements IReuseTabsPage
<h1>Counter @Id</h1>
<p>Current count: @currentCount</p>
<Button Type="primary" @onclick="IncrementCount">Click me</Button>
@code {
[Parameter ] public string Id { get; set; }
private int currentCount = 0;
public RenderFragment GetPageTitle() =>@<span>@($"Counter {Id}")</span>;
private void IncrementCount()
{
currentCount++;
}
}

View File

@ -0,0 +1,58 @@
@page "/fetchdata"
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using AntDesign.TestApp.Client.Data
@attribute [Authorize]
@inject HttpClient Http
@attribute [ReuseTabsPageTitle("Fetchdata")]
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from the server.</p>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private WeatherForecast[] forecasts;
protected override async Task OnInitializedAsync()
{
try
{
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
}
catch (AccessTokenNotAvailableException exception)
{
Console.WriteLine("--------------->" + exception.Message);
exception.Redirect();
}
}
}

View File

@ -0,0 +1,7 @@
@page "/"
@attribute [ReuseTabsPageTitle("Index")]
<h1>Hello, world!</h1>
Welcome to your new app.
<SurveyPrompt Title="How is Blazor working for you?" />

View File

@ -0,0 +1,34 @@
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace AntDesign.TestApp.Client
{
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.Services.AddHttpClient("AntDesign.TestApp.ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
// Supply HttpClient instances that include access tokens when making requests to the server project
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("AntDesign.TestApp.ServerAPI"));
builder.Services.AddApiAuthorization();
builder.Services.AddAntDesign();
await builder.Build().RunAsync();
}
}
}

View File

@ -0,0 +1,30 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:57346",
"sslPort": 44322
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"AntDesign.Tests.Authentication": {
"commandName": "Project",
"dotnetRunMessages": "true",
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -0,0 +1,24 @@
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject NavigationManager Navigation
@inject SignOutSessionStateManager SignOutManager
<AuthorizeView>
<Authorized>
<a href="authentication/profile">Hello, @context.User.Identity.Name!</a>
<button class="nav-link btn btn-link" @onclick="BeginSignOut">Log out</button>
</Authorized>
<NotAuthorized>
<a href="authentication/register">Register</a>
<a href="authentication/login">Log in</a>
</NotAuthorized>
</AuthorizeView>
@code{
private async Task BeginSignOut(MouseEventArgs args)
{
await SignOutManager.SetSignOutState();
Navigation.NavigateTo("authentication/logout");
}
}

View File

@ -0,0 +1,31 @@
@inherits LayoutComponentBase
<Layout Style="height: 100vh;">
<Sider Collapsible Collapsed=@collapsed NoTrigger>
<div class="logo" />
<NavMenu />
</Sider>
<Layout>
<Header>
<LoginDisplay />
<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
</Header>
<Content>
@Body
<div>
<ReuseTabs />
</div>
</Content>
<Footer>Footer</Footer>
</Layout>
</Layout>
@code
{
bool collapsed;
void toggle()
{
collapsed = !collapsed;
}
}

View File

@ -0,0 +1,5 @@
.logo {
height: 32px;
background: rgba(255, 255, 255, 0.2);
margin: 16px;
}

View File

@ -0,0 +1,7 @@
<Menu Theme="MenuTheme.Dark">
<MenuItem Title="Home" Icon="home" RouterLink="" RouterMatch="NavLinkMatch.All" />
<MenuItem Title="Counter 1" Icon="plus" RouterLink="counter/1" RouterMatch="NavLinkMatch.Prefix" />
<MenuItem Title="Counter 2" Icon="plus" RouterLink="counter/2" RouterMatch="NavLinkMatch.Prefix" />
<MenuItem Title="Counter 3" Icon="plus" RouterLink="counter/3" RouterMatch="NavLinkMatch.Prefix" />
<MenuItem Title="Fetch" Icon="unordered-list" RouterLink="fetchdata" RouterMatch="NavLinkMatch.All"/>
</Menu>

View File

@ -0,0 +1,9 @@
@inject NavigationManager Navigation
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@code {
protected override void OnInitialized()
{
Navigation.NavigateTo($"authentication/login?returnUrl={Uri.EscapeDataString(Navigation.Uri)}");
}
}

View File

@ -0,0 +1,16 @@
<div class="alert alert-secondary mt-4" role="alert">
<span class="oi oi-pencil mr-2" aria-hidden="true"></span>
<strong>@Title</strong>
<span class="text-nowrap">
Please take our
<a target="_blank" class="font-weight-bold" href="https://go.microsoft.com/fwlink/?linkid=2137916">brief survey</a>
</span>
and tell us what you think.
</div>
@code {
// Demonstrates how a parent component can supply parameters
[Parameter]
public string Title { get; set; }
}

View File

@ -0,0 +1,12 @@
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using AntDesign.TestApp.Client
@using AntDesign.TestApp.Client.Shared
@using AntDesign

View File

@ -0,0 +1,49 @@

html, body {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
a, .btn-link {
color: #0366d6;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.content {
padding-top: 1.1rem;
}
.valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050;
}
.invalid {
outline: 1px solid red;
}
.validation-message {
color: red;
}
#blazor-error-ui {
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>AntDesign.Tests.Authentication</title>
<base href="/" />
<link href="css/app.css" rel="stylesheet" />
<link href="AntDesign.TestApp.Client.styles.css" rel="stylesheet" />
<link href="_content/AntDesign/css/ant-design-blazor.css" rel="stylesheet">
<script src="_content/AntDesign/js/ant-design-blazor.js"></script>
</head>
<body>
<div id="app">Loading...</div>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"></script>
<script src="_framework/blazor.webassembly.js"></script>
</body>
</html>

View File

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<UserSecretsId>AntDesign.Tests.Authentication.Server-60788253-21B9-4F6A-87BD-B31690EE5806</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="5.0.9" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Client\AntDesign.TestApp.Client.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="5.0.9" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="5.0.9" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="5.0.9" />
<PackageReference Include="Microsoft.AspNetCore.ApiAuthorization.IdentityServer" Version="5.0.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.9" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,35 @@
@using Microsoft.AspNetCore.Identity
@using AntDesign.TestApp.Server.Models
@inject SignInManager<ApplicationUser> SignInManager
@inject UserManager<ApplicationUser> UserManager
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
var returnUrl = "/";
if (Context.Request.Query.TryGetValue("returnUrl", out var existingUrl)) {
returnUrl = existingUrl;
}
}
<ul class="navbar-nav">
@if (SignInManager.IsSignedIn(User))
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello @User.Identity.Name!</a>
</li>
<li class="nav-item">
<form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="/" method="post">
<button type="submit" class="nav-link btn btn-link text-dark">Logout</button>
</form>
</li>
}
else
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Register" asp-route-returnUrl="@returnUrl">Register</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login" asp-route-returnUrl="@returnUrl">Login</a>
</li>
}
</ul>

View File

@ -0,0 +1,26 @@
using Microsoft.AspNetCore.ApiAuthorization.IdentityServer;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace AntDesign.TestApp.Server.Controllers
{
public class OidcConfigurationController : Controller
{
private readonly ILogger<OidcConfigurationController> _logger;
public OidcConfigurationController(IClientRequestParametersProvider clientRequestParametersProvider, ILogger<OidcConfigurationController> logger)
{
ClientRequestParametersProvider = clientRequestParametersProvider;
_logger = logger;
}
public IClientRequestParametersProvider ClientRequestParametersProvider { get; }
[HttpGet("_configuration/{clientId}")]
public IActionResult GetClientRequestParameters([FromRoute] string clientId)
{
var parameters = ClientRequestParametersProvider.GetClientParameters(HttpContext, clientId);
return Ok(parameters);
}
}
}

View File

@ -0,0 +1,42 @@
using AntDesign.TestApp.Client.Data;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace AntDesign.TestApp.Server.Controllers
{
[Authorize]
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] _summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = _summaries[rng.Next(_summaries.Length)]
})
.ToArray();
}
}
}

View File

@ -0,0 +1,21 @@
using AntDesign.TestApp.Server.Models;
using IdentityServer4.EntityFramework.Options;
using Microsoft.AspNetCore.ApiAuthorization.IdentityServer;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace AntDesign.TestApp.Server.Data
{
public class ApplicationDbContext : ApiAuthorizationDbContext<ApplicationUser>
{
public ApplicationDbContext(
DbContextOptions options,
IOptions<OperationalStoreOptions> operationalStoreOptions) : base(options, operationalStoreOptions)
{
}
}
}

View File

@ -0,0 +1,380 @@
// <auto-generated />
using AntDesign.TestApp.Server.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using System;
namespace AntDesign.TestApp.Server.Data.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("00000000000000_CreateIdentitySchema")]
partial class CreateIdentitySchema
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.UseIdentityColumns()
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("ProductVersion", "5.0.0-rc.1.20417.2");
modelBuilder.Entity("AntDesign.TestApp.Server.Models.ApplicationUser", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("bit");
b.Property<bool>("LockoutEnabled")
.HasColumnType("bit");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetimeoffset");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("PasswordHash")
.HasColumnType("nvarchar(max)");
b.Property<string>("PhoneNumber")
.HasColumnType("nvarchar(max)");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("bit");
b.Property<string>("SecurityStamp")
.HasColumnType("nvarchar(max)");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("bit");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex")
.HasFilter("[NormalizedUserName] IS NOT NULL");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b =>
{
b.Property<string>("UserCode")
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<string>("ClientId")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2");
b.Property<string>("Data")
.IsRequired()
.HasMaxLength(50000)
.HasColumnType("nvarchar(max)");
b.Property<string>("Description")
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<string>("DeviceCode")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<DateTime?>("Expiration")
.IsRequired()
.HasColumnType("datetime2");
b.Property<string>("SessionId")
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("SubjectId")
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.HasKey("UserCode");
b.HasIndex("DeviceCode")
.IsUnique();
b.HasIndex("Expiration");
b.ToTable("DeviceCodes");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b =>
{
b.Property<string>("Key")
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<string>("ClientId")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<DateTime?>("ConsumedTime")
.HasColumnType("datetime2");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2");
b.Property<string>("Data")
.IsRequired()
.HasMaxLength(50000)
.HasColumnType("nvarchar(max)");
b.Property<string>("Description")
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<DateTime?>("Expiration")
.HasColumnType("datetime2");
b.Property<string>("SessionId")
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("SubjectId")
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.HasKey("Key");
b.HasIndex("Expiration");
b.HasIndex("SubjectId", "ClientId", "Type");
b.HasIndex("SubjectId", "SessionId", "Type");
b.ToTable("PersistedGrants");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex")
.HasFilter("[NormalizedName] IS NOT NULL");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.UseIdentityColumn();
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.UseIdentityColumn();
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<string>("ProviderKey")
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<string>("ProviderDisplayName")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("RoleId")
.HasColumnType("nvarchar(450)");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("LoginProvider")
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<string>("Name")
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<string>("Value")
.HasColumnType("nvarchar(max)");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("AntDesign.TestApp.Server.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("AntDesign.TestApp.Server.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("AntDesign.TestApp.Server.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("AntDesign.TestApp.Server.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,290 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
namespace AntDesign.TestApp.Server.Data.Migrations
{
public partial class CreateIdentitySchema : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AspNetRoles",
columns: table => new
{
Id = table.Column<string>(type: "nvarchar(450)", nullable: false),
Name = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
NormalizedName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
ConcurrencyStamp = table.Column<string>(type: "nvarchar(max)", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetRoles", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AspNetUsers",
columns: table => new
{
Id = table.Column<string>(type: "nvarchar(450)", nullable: false),
UserName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
NormalizedUserName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
Email = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
NormalizedEmail = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
EmailConfirmed = table.Column<bool>(type: "bit", nullable: false),
PasswordHash = table.Column<string>(type: "nvarchar(max)", nullable: true),
SecurityStamp = table.Column<string>(type: "nvarchar(max)", nullable: true),
ConcurrencyStamp = table.Column<string>(type: "nvarchar(max)", nullable: true),
PhoneNumber = table.Column<string>(type: "nvarchar(max)", nullable: true),
PhoneNumberConfirmed = table.Column<bool>(type: "bit", nullable: false),
TwoFactorEnabled = table.Column<bool>(type: "bit", nullable: false),
LockoutEnd = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: true),
LockoutEnabled = table.Column<bool>(type: "bit", nullable: false),
AccessFailedCount = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUsers", x => x.Id);
});
migrationBuilder.CreateTable(
name: "DeviceCodes",
columns: table => new
{
UserCode = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
DeviceCode = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
SubjectId = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
SessionId = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
ClientId = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
Description = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
Expiration = table.Column<DateTime>(type: "datetime2", nullable: false),
Data = table.Column<string>(type: "nvarchar(max)", maxLength: 50000, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_DeviceCodes", x => x.UserCode);
});
migrationBuilder.CreateTable(
name: "PersistedGrants",
columns: table => new
{
Key = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
Type = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
SubjectId = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
SessionId = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
ClientId = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
Description = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
Expiration = table.Column<DateTime>(type: "datetime2", nullable: true),
ConsumedTime = table.Column<DateTime>(type: "datetime2", nullable: true),
Data = table.Column<string>(type: "nvarchar(max)", maxLength: 50000, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_PersistedGrants", x => x.Key);
});
migrationBuilder.CreateTable(
name: "AspNetRoleClaims",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
RoleId = table.Column<string>(type: "nvarchar(450)", nullable: false),
ClaimType = table.Column<string>(type: "nvarchar(max)", nullable: true),
ClaimValue = table.Column<string>(type: "nvarchar(max)", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);
table.ForeignKey(
name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",
column: x => x.RoleId,
principalTable: "AspNetRoles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserClaims",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
UserId = table.Column<string>(type: "nvarchar(450)", nullable: false),
ClaimType = table.Column<string>(type: "nvarchar(max)", nullable: true),
ClaimValue = table.Column<string>(type: "nvarchar(max)", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserClaims", x => x.Id);
table.ForeignKey(
name: "FK_AspNetUserClaims_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserLogins",
columns: table => new
{
LoginProvider = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
ProviderKey = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
ProviderDisplayName = table.Column<string>(type: "nvarchar(max)", nullable: true),
UserId = table.Column<string>(type: "nvarchar(450)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey });
table.ForeignKey(
name: "FK_AspNetUserLogins_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserRoles",
columns: table => new
{
UserId = table.Column<string>(type: "nvarchar(450)", nullable: false),
RoleId = table.Column<string>(type: "nvarchar(450)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId });
table.ForeignKey(
name: "FK_AspNetUserRoles_AspNetRoles_RoleId",
column: x => x.RoleId,
principalTable: "AspNetRoles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_AspNetUserRoles_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserTokens",
columns: table => new
{
UserId = table.Column<string>(type: "nvarchar(450)", nullable: false),
LoginProvider = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
Name = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
Value = table.Column<string>(type: "nvarchar(max)", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name });
table.ForeignKey(
name: "FK_AspNetUserTokens_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_AspNetRoleClaims_RoleId",
table: "AspNetRoleClaims",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "RoleNameIndex",
table: "AspNetRoles",
column: "NormalizedName",
unique: true,
filter: "[NormalizedName] IS NOT NULL");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserClaims_UserId",
table: "AspNetUserClaims",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserLogins_UserId",
table: "AspNetUserLogins",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserRoles_RoleId",
table: "AspNetUserRoles",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "EmailIndex",
table: "AspNetUsers",
column: "NormalizedEmail");
migrationBuilder.CreateIndex(
name: "UserNameIndex",
table: "AspNetUsers",
column: "NormalizedUserName",
unique: true,
filter: "[NormalizedUserName] IS NOT NULL");
migrationBuilder.CreateIndex(
name: "IX_DeviceCodes_DeviceCode",
table: "DeviceCodes",
column: "DeviceCode",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_DeviceCodes_Expiration",
table: "DeviceCodes",
column: "Expiration");
migrationBuilder.CreateIndex(
name: "IX_PersistedGrants_Expiration",
table: "PersistedGrants",
column: "Expiration");
migrationBuilder.CreateIndex(
name: "IX_PersistedGrants_SubjectId_ClientId_Type",
table: "PersistedGrants",
columns: new[] { "SubjectId", "ClientId", "Type" });
migrationBuilder.CreateIndex(
name: "IX_PersistedGrants_SubjectId_SessionId_Type",
table: "PersistedGrants",
columns: new[] { "SubjectId", "SessionId", "Type" });
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AspNetRoleClaims");
migrationBuilder.DropTable(
name: "AspNetUserClaims");
migrationBuilder.DropTable(
name: "AspNetUserLogins");
migrationBuilder.DropTable(
name: "AspNetUserRoles");
migrationBuilder.DropTable(
name: "AspNetUserTokens");
migrationBuilder.DropTable(
name: "DeviceCodes");
migrationBuilder.DropTable(
name: "PersistedGrants");
migrationBuilder.DropTable(
name: "AspNetRoles");
migrationBuilder.DropTable(
name: "AspNetUsers");
}
}
}

View File

@ -0,0 +1,378 @@
// <auto-generated />
using AntDesign.TestApp.Server.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using System;
namespace AntDesign.TestApp.Server.Data.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
partial class ApplicationDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.UseIdentityColumns()
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("ProductVersion", "5.0.0-rc.1.20417.2");
modelBuilder.Entity("AntDesign.TestApp.Server.Models.ApplicationUser", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("bit");
b.Property<bool>("LockoutEnabled")
.HasColumnType("bit");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetimeoffset");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("PasswordHash")
.HasColumnType("nvarchar(max)");
b.Property<string>("PhoneNumber")
.HasColumnType("nvarchar(max)");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("bit");
b.Property<string>("SecurityStamp")
.HasColumnType("nvarchar(max)");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("bit");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex")
.HasFilter("[NormalizedUserName] IS NOT NULL");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b =>
{
b.Property<string>("UserCode")
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<string>("ClientId")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2");
b.Property<string>("Data")
.IsRequired()
.HasMaxLength(50000)
.HasColumnType("nvarchar(max)");
b.Property<string>("Description")
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<string>("DeviceCode")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<DateTime?>("Expiration")
.IsRequired()
.HasColumnType("datetime2");
b.Property<string>("SessionId")
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("SubjectId")
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.HasKey("UserCode");
b.HasIndex("DeviceCode")
.IsUnique();
b.HasIndex("Expiration");
b.ToTable("DeviceCodes");
});
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b =>
{
b.Property<string>("Key")
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<string>("ClientId")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<DateTime?>("ConsumedTime")
.HasColumnType("datetime2");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2");
b.Property<string>("Data")
.IsRequired()
.HasMaxLength(50000)
.HasColumnType("nvarchar(max)");
b.Property<string>("Description")
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<DateTime?>("Expiration")
.HasColumnType("datetime2");
b.Property<string>("SessionId")
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("SubjectId")
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.HasKey("Key");
b.HasIndex("Expiration");
b.HasIndex("SubjectId", "ClientId", "Type");
b.HasIndex("SubjectId", "SessionId", "Type");
b.ToTable("PersistedGrants");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex")
.HasFilter("[NormalizedName] IS NOT NULL");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.UseIdentityColumn();
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.UseIdentityColumn();
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<string>("ProviderKey")
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<string>("ProviderDisplayName")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("RoleId")
.HasColumnType("nvarchar(450)");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("LoginProvider")
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<string>("Name")
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<string>("Value")
.HasColumnType("nvarchar(max)");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("AntDesign.TestApp.Server.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("AntDesign.TestApp.Server.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("AntDesign.TestApp.Server.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("AntDesign.TestApp.Server.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,12 @@
using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace AntDesign.TestApp.Server.Models
{
public class ApplicationUser : IdentityUser
{
}
}

View File

@ -0,0 +1,42 @@
@page
@model AntDesign.TestApp.Server.Pages.ErrorModel
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Error</title>
<link href="~/css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="~/css/app.css" rel="stylesheet" />
</head>
<body>
<div class="main">
<div class="content px-4">
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,32 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
namespace AntDesign.TestApp.Server.Pages
{
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
public string RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
private readonly ILogger<ErrorModel> _logger;
public ErrorModel(ILogger<ErrorModel> logger)
{
_logger = logger;
}
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}
}

View File

@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace AntDesign.TestApp.Server
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}

View File

@ -0,0 +1,30 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:57346",
"sslPort": 44322
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"AntDesign.TestApp.Server": {
"commandName": "Project",
"dotnetRunMessages": "true",
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -0,0 +1,8 @@
{
"dependencies": {
"mssql1": {
"type": "mssql",
"connectionId": "ConnectionStrings:DefaultConnection"
}
}
}

View File

@ -0,0 +1,8 @@
{
"dependencies": {
"mssql1": {
"type": "mssql.local",
"connectionId": "ConnectionStrings:DefaultConnection"
}
}
}

View File

@ -0,0 +1,82 @@
using AntDesign.TestApp.Server.Data;
using AntDesign.TestApp.Server.Models;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.ResponseCompression;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Linq;
namespace AntDesign.TestApp.Server
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
services.AddAuthentication()
.AddIdentityServerJwt();
services.AddControllersWithViews();
services.AddRazorPages();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseMigrationsEndPoint();
app.UseWebAssemblyDebugging();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
endpoints.MapFallbackToFile("index.html");
});
}
}
}

View File

@ -0,0 +1,14 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"IdentityServer": {
"Key": {
"Type": "Development"
}
}
}

View File

@ -0,0 +1,20 @@
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-AntDesign.Tests.Authentication;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"IdentityServer": {
"Clients": {
"AntDesign.TestApp.Client": {
"Profile": "IdentityServerSPA"
}
}
},
"AllowedHosts": "*"
}