Merge pull request #24387 from dotnet-maestro-bot/merge/release/5.0-preview8-to-master

[automated] Merge branch 'release/5.0-preview8' => 'master'
This commit is contained in:
Safia Abdalla 2020-07-30 12:16:52 -07:00 committed by GitHub
commit e0ef3d2246
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 1327 additions and 469 deletions

View File

@ -305,9 +305,9 @@
<Uri>https://github.com/dotnet/runtime</Uri>
<Sha>0e0e648770e54b12c2fa81a77538ce1a72fca8af</Sha>
</Dependency>
<Dependency Name="Microsoft.DotNet.Arcade.Sdk" Version="5.0.0-beta.20364.3">
<Dependency Name="Microsoft.DotNet.Arcade.Sdk" Version="5.0.0-beta.20377.2">
<Uri>https://github.com/dotnet/arcade</Uri>
<Sha>ff5d4b6c8dbdaeacb6e6159d3f8185118dffd915</Sha>
<Sha>22d6355c4f3c9ac00b0e3abf9d85f2fb07e4787b</Sha>
</Dependency>
<Dependency Name="Microsoft.DotNet.Helix.Sdk" Version="5.0.0-beta.20364.3">
<Uri>https://github.com/dotnet/arcade</Uri>

View File

@ -256,8 +256,8 @@
<IdentityServer4StoragePackageVersion>3.0.0</IdentityServer4StoragePackageVersion>
<IdentityServer4EntityFrameworkStoragePackageVersion>3.0.0</IdentityServer4EntityFrameworkStoragePackageVersion>
<MessagePackPackageVersion>2.1.90</MessagePackPackageVersion>
<MicrosoftIdentityWebPackageVersion>0.2.0-preview</MicrosoftIdentityWebPackageVersion>
<MicrosoftIdentityWebUIPackageVersion>0.2.0-preview</MicrosoftIdentityWebUIPackageVersion>
<MicrosoftIdentityWebPackageVersion>0.2.1-preview</MicrosoftIdentityWebPackageVersion>
<MicrosoftIdentityWebUIPackageVersion>0.2.1-preview</MicrosoftIdentityWebUIPackageVersion>
<MicrosoftGraphPackageVersion>3.8.0</MicrosoftGraphPackageVersion>
<MessagePackAnalyzerPackageVersion>$(MessagePackPackageVersion)</MessagePackAnalyzerPackageVersion>
<MoqPackageVersion>4.10.0</MoqPackageVersion>

View File

@ -30,7 +30,7 @@
},
"msbuild-sdks": {
"Yarn.MSBuild": "1.15.2",
"Microsoft.DotNet.Arcade.Sdk": "5.0.0-beta.20364.3",
"Microsoft.DotNet.Arcade.Sdk": "5.0.0-beta.20377.2",
"Microsoft.DotNet.Helix.Sdk": "5.0.0-beta.20364.3"
}
}

View File

@ -30,6 +30,9 @@ namespace Templates.Test
[QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/20172")]
public async Task BlazorServerTemplateWorks_NoAuth()
{
// Additional arguments are needed. See: https://github.com/dotnet/aspnetcore/issues/24278
Environment.SetEnvironmentVariable("EnableDefaultScopedCssItems", "true");
Project = await ProjectFactory.GetOrCreateProject("blazorservernoauth", Output);
var createResult = await Project.RunDotNetNewAsync("blazorserver");
@ -88,6 +91,9 @@ namespace Templates.Test
[QuarantinedTest]
public async Task BlazorServerTemplateWorks_IndividualAuth(bool useLocalDB)
{
// Additional arguments are needed. See: https://github.com/dotnet/aspnetcore/issues/24278
Environment.SetEnvironmentVariable("EnableDefaultScopedCssItems", "true");
Project = await ProjectFactory.GetOrCreateProject("blazorserverindividual" + (useLocalDB ? "uld" : ""), Output);
var createResult = await Project.RunDotNetNewAsync("blazorserver", auth: "Individual", useLocalDB: useLocalDB);
@ -182,5 +188,30 @@ namespace Templates.Test
Browser.Exists(By.CssSelector("table>tbody>tr"));
Browser.Equal(5, () => Browser.FindElements(By.CssSelector("p+table>tbody>tr")).Count);
}
[Theory]
[QuarantinedTest]
[InlineData("IndividualB2C", null)]
[InlineData("IndividualB2C", new string[] { "--called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })]
[InlineData("SingleOrg", null)]
[InlineData("SingleOrg", new string[] { "--called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })]
[InlineData("SingleOrg", new string[] { "--calls-graph" })]
public async Task BlazorServerTemplat_IdentityWeb_BuildAndPublish(string auth, string[] args)
{
Project = await ProjectFactory.GetOrCreateProject("blazorserveridweb" + Guid.NewGuid().ToString().Substring(0, 10).ToLower(), Output);
var createResult = await Project.RunDotNetNewAsync("blazorserver", auth: auth, args: args);
Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", Project, createResult));
var publishResult = await Project.RunDotNetPublishAsync();
Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", Project, publishResult));
// Run dotnet build after publish. The reason is that one uses Config = Debug and the other uses Config = Release
// The output from publish will go into bin/Release/netcoreappX.Y/publish and won't be affected by calling build
// later, while the opposite is not true.
var buildResult = await Project.RunDotNetBuildAsync();
Assert.True(0 == buildResult.ExitCode, ErrorMessages.GetFailedProcessMessage("build", Project, buildResult));
}
}
}

View File

@ -43,6 +43,9 @@ namespace Templates.Test
[Fact]
public async Task BlazorWasmStandaloneTemplate_Works()
{
// Additional arguments are needed. See: https://github.com/dotnet/aspnetcore/issues/24278
Environment.SetEnvironmentVariable("EnableDefaultScopedCssItems", "true");
var project = await ProjectFactory.GetOrCreateProject("blazorstandalone", Output);
project.RuntimeIdentifier = "browser-wasm";
@ -81,6 +84,9 @@ namespace Templates.Test
[QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/20172")]
public async Task BlazorWasmHostedTemplate_Works()
{
// Additional arguments are needed. See: https://github.com/dotnet/aspnetcore/issues/24278
Environment.SetEnvironmentVariable("EnableDefaultScopedCssItems", "true");
var project = await ProjectFactory.GetOrCreateProject("blazorhosted", Output);
var createResult = await project.RunDotNetNewAsync("blazorwasm", args: new[] { "--hosted" });
@ -135,6 +141,9 @@ namespace Templates.Test
[QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/23992")]
public async Task BlazorWasmStandalonePwaTemplate_Works()
{
// Additional arguments are needed. See: https://github.com/dotnet/aspnetcore/issues/24278
Environment.SetEnvironmentVariable("EnableDefaultScopedCssItems", "true");
var project = await ProjectFactory.GetOrCreateProject("blazorstandalonepwa", Output);
project.RuntimeIdentifier = "browser-wasm";
@ -174,6 +183,9 @@ namespace Templates.Test
[Fact]
public async Task BlazorWasmHostedPwaTemplate_Works()
{
// Additional arguments are needed. See: https://github.com/dotnet/aspnetcore/issues/24278
Environment.SetEnvironmentVariable("EnableDefaultScopedCssItems", "true");
var project = await ProjectFactory.GetOrCreateProject("blazorhostedpwa", Output);
var createResult = await project.RunDotNetNewAsync("blazorwasm", args: new[] { "--hosted", "--pwa" });
@ -269,6 +281,9 @@ namespace Templates.Test
private async Task BlazorWasmHostedTemplate_IndividualAuth_Works(bool useLocalDb)
{
// Additional arguments are needed. See: https://github.com/dotnet/aspnetcore/issues/24278
Environment.SetEnvironmentVariable("EnableDefaultScopedCssItems", "true");
var project = await ProjectFactory.GetOrCreateProject("blazorhostedindividual" + (useLocalDb ? "uld" : ""), Output);
var createResult = await project.RunDotNetNewAsync("blazorwasm", args: new[] { "--hosted", "-au", "Individual", useLocalDb ? "-uld" : "" });
@ -336,6 +351,9 @@ namespace Templates.Test
[QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/23639")]
public async Task BlazorWasmStandaloneTemplate_IndividualAuth_Works()
{
// Additional arguments are needed. See: https://github.com/dotnet/aspnetcore/issues/24278
Environment.SetEnvironmentVariable("EnableDefaultScopedCssItems", "true");
var project = await ProjectFactory.GetOrCreateProject("blazorstandaloneindividual", Output);
project.RuntimeIdentifier = "browser-wasm";
@ -403,6 +421,27 @@ namespace Templates.Test
"--default-scope", "full",
"--app-id-uri", "ApiUri",
"--api-client-id", "1234123413241324"),
new TemplateInstance(
"blazorwasmhostedaadgraph", "-ho",
"-au", "SingleOrg",
"--calls-graph",
"--domain", "my-domain",
"--tenant-id", "tenantId",
"--client-id", "clientId",
"--default-scope", "full",
"--app-id-uri", "ApiUri",
"--api-client-id", "1234123413241324"),
new TemplateInstance(
"blazorwasmhostedaadapi", "-ho",
"-au", "SingleOrg",
"--called-api-url", "\"https://graph.microsoft.com\"",
"--called-api-scopes", "user.readwrite",
"--domain", "my-domain",
"--tenant-id", "tenantId",
"--client-id", "clientId",
"--default-scope", "full",
"--app-id-uri", "ApiUri",
"--api-client-id", "1234123413241324"),
new TemplateInstance(
"blazorwasmstandaloneaadb2c",
"-au", "IndividualB2C",

View File

@ -17,14 +17,15 @@
<!--#endif -->
<!--#if (IndividualAuth || OrganizationalAuth) -->
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.AzureAD.UI" Version="${MicrosoftAspNetCoreAuthenticationAzureADUIPackageVersion}" Condition="'$(OrganizationalAuth)' == 'True'" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.AzureADB2C.UI" Version="${MicrosoftAspNetCoreAuthenticationAzureADB2CUIPackageVersion}" Condition="'$(IndividualB2CAuth)' == 'True'" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="${MicrosoftAspNetCoreDiagnosticsEntityFrameworkCorePackageVersion}" Condition=" '$(IndividualLocalAuth)' == 'True' " />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="${MicrosoftAspNetCoreIdentityEntityFrameworkCorePackageVersion}" Condition=" '$(IndividualLocalAuth)' == 'True' " />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="${MicrosoftAspNetCoreIdentityUIPackageVersion}" Condition=" '$(IndividualLocalAuth)' == 'True' " />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="${MicrosoftEntityFrameworkCoreSqlServerPackageVersion}" Condition=" '$(IndividualLocalAuth)' == 'True' AND '$(UseLocalDB)' == 'True'" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="${MicrosoftEntityFrameworkCoreSqlitePackageVersion}" Condition=" '$(IndividualLocalAuth)' == 'True' AND '$(UseLocalDB)' != 'True'" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="${MicrosoftEntityFrameworkCoreToolsPackageVersion}" Condition=" '$(IndividualLocalAuth)' == 'True' " />
<PackageReference Include="Microsoft.Identity.Web" Version="${MicrosoftIdentityWebPackageVersion}" Condition=" '$(IndividualB2CAuth)' == 'True' OR '$(OrganizationalAuth)' == 'True'" />
<PackageReference Include="Microsoft.Identity.Web.UI" Version="${MicrosoftIdentityWebUIPackageVersion}" Condition=" '$(IndividualB2CAuth)' == 'True' OR '$(OrganizationalAuth)' == 'True'" />
<PackageReference Include="Microsoft.Graph" Version="${MicrosoftGraphPackageVersion}" Condition=" '$(GenerateGraph)' == 'True' "/>
</ItemGroup>
<!--#endif -->

View File

@ -38,8 +38,9 @@
<!--#endif -->
<!--#if (OrganizationalAuth || IndividualB2CAuth) -->
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.AzureAD.UI" Version="${MicrosoftAspNetCoreAuthenticationAzureADUIPackageVersion}" Condition="'$(OrganizationalAuth)' == 'True'" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.AzureADB2C.UI" Version="${MicrosoftAspNetCoreAuthenticationAzureADB2CUIPackageVersion}" Condition="'$(IndividualB2CAuth)' == 'True'" />
<PackageReference Include="Microsoft.Identity.Web" Version="${MicrosoftIdentityWebPackageVersion}" />
<PackageReference Include="Microsoft.Identity.Web.UI" Version="${MicrosoftIdentityWebUIPackageVersion}" />
<PackageReference Include="Microsoft.Graph" Version="${MicrosoftGraphPackageVersion}" Condition=" '$(GenerateGraph)' == 'True' "/>
</ItemGroup>
<!--#endif -->

View File

@ -67,6 +67,18 @@
"NoHttps": {
"longName": "no-https",
"shortName": ""
},
"CalledApiUrl": {
"longName": "called-api-url",
"shortName": ""
},
"CalledApiScopes": {
"longName": "called-api-scopes",
"shortName": ""
},
"CallsMicrosoftGraph": {
"longName": "calls-graph",
"shortName": ""
}
},
"usageExamples": [

View File

@ -127,6 +127,52 @@
"Shared/LoginDisplay.IndividualB2CAuth.razor",
"Shared/LoginDisplay.OrganizationalAuth.razor"
]
},
{
"condition": "(!GenerateApi)",
"exclude": [
"Services/DownstreamWebApi.cs",
"Pages/CallWebApi.razor"
]
},
{
"condition": "(!GenerateGraph)",
"exclude": [
"Services/MicrosoftGraphServiceExtensions.cs",
"Services/TokenAcquisitionCredentialProvider.cs",
"Shared/NavMenu.CallsMicrosoftGraph.razor",
"Pages/ShowProfile.razor"
]
},
{
"condition": "(!GenerateApiOrGraph)",
"rename": {
"Shared/NavMenu.NoGraphOrApi.razor": "Shared/NavMenu.razor"
},
"exclude": [
"Shared/NavMenu.CallsMicrosoftGraph.razor",
"Shared/NavMenu.CallsWebApi.razor"
]
},
{
"condition": "(GenerateGraph)",
"rename": {
"Shared/NavMenu.CallsMicrosoftGraph.razor": "Shared/NavMenu.razor"
},
"exclude": [
"Shared/NavMenu.NoGraphOrApi.razor",
"Shared/NavMenu.CallsWebApi.razor"
]
},
{
"condition": "(GenerateApi)",
"rename": {
"Shared/NavMenu.CallsWebApi.razor": "Shared/NavMenu.razor"
},
"exclude": [
"Shared/NavMenu.NoGraphOrApi.razor",
"Shared/NavMenu.CallsMicrosoftGraph.razor"
]
}
]
}
@ -174,21 +220,28 @@
"SignUpSignInPolicyId": {
"type": "parameter",
"datatype": "string",
"defaultValue": "",
"defaultValue": "b2c_1_susi",
"replaces": "MySignUpSignInPolicyId",
"description": "The sign-in and sign-up policy ID for this project (use with IndividualB2C auth)."
},
"SignedOutCallbackPath": {
"type": "parameter",
"datatype": "string",
"defaultValue": "/signout/B2C_1_susi",
"replaces": "/signout/MySignUpSignInPolicyId",
"description": "The global signout callback (use with IndividualB2C auth)."
},
"ResetPasswordPolicyId": {
"type": "parameter",
"datatype": "string",
"defaultValue": "",
"defaultValue": "b2c_1_reset",
"replaces": "MyResetPasswordPolicyId",
"description": "The reset password policy ID for this project (use with IndividualB2C auth)."
},
"EditProfilePolicyId": {
"type": "parameter",
"datatype": "string",
"defaultValue": "",
"defaultValue": "b2c_1_edit_profile",
"replaces": "MyEditProfilePolicyId",
"description": "The edit profile policy ID for this project (use with IndividualB2C auth)."
},
@ -352,6 +405,37 @@
"format": "yyyy"
}
},
"CalledApiUrl": {
"type": "parameter",
"datatype": "string",
"replaces": "[WebApiUrl]",
"defaultValue" : "https://graph.microsoft.com/beta",
"description": "URL of the API to call from the web app. This option only applies if --auth SingleOrg, --auth MultiOrg or --auth IndividualB2C is specified."
},
"CallsMicrosoftGraph": {
"type": "parameter",
"datatype": "bool",
"defaultValue": "false",
"description": "Specifies if the web app calls Microsoft Graph. This option only applies if --auth SingleOrg or --auth MultiOrg is specified."
},
"CalledApiScopes": {
"type": "parameter",
"datatype": "string",
"replaces" : "user.read",
"description": "Scopes to request to call the API from the web app. This option only applies if --auth SingleOrg, --auth MultiOrg or --auth IndividualB2C is specified."
},
"GenerateApi": {
"type": "computed",
"value": "((IndividualB2CAuth || OrganizationalAuth) && (CalledApiUrl != \"https://graph.microsoft.com/beta\" || CalledApiScopes != \"user.read\"))"
},
"GenerateGraph": {
"type": "computed",
"value": "(OrganizationalAuth && CallsMicrosoftGraph)"
},
"GenerateApiOrGraph": {
"type": "computed",
"value": "(GenerateApi || GenerateGraph)"
},
"skipRestore": {
"type": "parameter",
"datatype": "bool",

View File

@ -7,10 +7,10 @@
@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>
<a class="nav-link text-dark" asp-area="MicrosoftIdentity" 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">
<form class="form-inline" asp-area="MicrosoftIdentity" asp-page="/Account/Logout" asp-route-returnUrl="/" method="post">
<button type="submit" class="nav-link btn btn-link text-dark">Logout</button>
</form>
</li>
@ -18,10 +18,10 @@
else
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Register">Register</a>
<a class="nav-link text-dark" asp-area="MicrosoftIdentity" asp-page="/Account/Register">Register</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login">Login</a>
<a class="nav-link text-dark" asp-area="MicrosoftIdentity" asp-page="/Account/Login">Login</a>
</li>
}
</ul>

View File

@ -0,0 +1,37 @@
@page "/callwebapi"
@using BlazorServerWeb_CSharp
@using Microsoft.Identity.Web
@inject IDownstreamWebApi downstreamAPI
@inject MicrosoftIdentityConsentAndConditionalAccessHandler ConsentHandler
<h1>Call an API</h1>
<p>This component demonstrates fetching data from a Web API.</p>
@if (apiResult == null)
{
<p><em>Loading...</em></p>
}
else
{
<h2>API Result</h2>
@apiResult
}
@code {
private string apiResult;
protected override async Task OnInitializedAsync()
{
try
{
apiResult = await downstreamAPI.CallWebApiAsync("me");
}
catch (Exception ex)
{
ConsentHandler.HandleException(ex);
}
}
}

View File

@ -0,0 +1,84 @@
@page "/showprofile"
@using Microsoft.Identity.Web
@using Microsoft.Graph
@inject Microsoft.Graph.GraphServiceClient GraphServiceClient
@inject MicrosoftIdentityConsentAndConditionalAccessHandler ConsentHandler
<h1>Me</h1>
<p>This component demonstrates fetching data from a service.</p>
@if (user == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table table-striped table-condensed" style="font-family: monospace">
<tr>
<th>Property</th>
<th>Value</th>
</tr>
<tr>
<td>Name</td>
<td>@user.DisplayName</td>
</tr>
<tr>
<td>Photo</td>
<td>
@{
if (photo != null)
{
<img style="margin: 5px 0; width: 150px" src="data:image/jpeg;base64, @photo" />
}
else
{
<h3>NO PHOTO</h3>
<p>Check user profile in Azure Active Directory to add a photo.</p>
}
}
</td>
</tr>
</table>
}
@code {
User user;
string photo;
protected override async Task OnInitializedAsync()
{
try
{
user = await GraphServiceClient.Me.Request().GetAsync();
photo = await GetPhoto();
}
catch (Exception ex)
{
ConsentHandler.HandleException(ex);
}
}
protected async Task<string> GetPhoto()
{
string photo;
try
{
using (var photoStream = await GraphServiceClient.Me.Photo.Content.Request().GetAsync())
{
byte[] photoByte = ((System.IO.MemoryStream)photoStream).ToArray();
photo = Convert.ToBase64String(photoByte);
this.StateHasChanged();
}
}
catch (Exception)
{
photo = null;
}
return photo;
}
}

View File

@ -14,11 +14,10 @@
<base href="~/" />
<link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
<link href="css/site.css" rel="stylesheet" />
<link href="_content/BlazorServerWeb-CSharp/_framework/scoped.styles.css" rel="stylesheet" />
</head>
<body>
<app>
<component type="typeof(App)" render-mode="ServerPrerendered" />
</app>
<component type="typeof(App)" render-mode="ServerPrerendered" />
<div id="blazor-error-ui">
<environment include="Staging,Production">

View File

@ -1,9 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;

View File

@ -0,0 +1,72 @@
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Identity.Web;
namespace BlazorServerWeb_CSharp
{
public interface IDownstreamWebApi
{
Task<string> CallWebApiAsync(string relativeEndpoint = "", string[] requiredScopes = null);
}
public static class DownstreamWebApiExtensions
{
public static void AddDownstreamWebApiService(this IServiceCollection services, IConfiguration configuration)
{
// https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests
services.AddHttpClient<IDownstreamWebApi, DownstreamWebApi>();
}
}
public class DownstreamWebApi : IDownstreamWebApi
{
private readonly ITokenAcquisition _tokenAcquisition;
private readonly IConfiguration _configuration;
private readonly HttpClient _httpClient;
public DownstreamWebApi(
ITokenAcquisition tokenAcquisition,
IConfiguration configuration,
HttpClient httpClient)
{
_tokenAcquisition = tokenAcquisition;
_configuration = configuration;
_httpClient = httpClient;
}
/// <summary>
/// Calls the Web API with the required scopes
/// </summary>
/// <param name="requireScopes">[Optional] Scopes required to call the Web API. If
/// not specified, uses scopes from the configuration</param>
/// <param name="relativeEndpoint">Endpoint relative to the CalledApiUrl configuration</param>
/// <returns>A JSON string representing the result of calling the Web API</returns>
public async Task<string> CallWebApiAsync(string relativeEndpoint = "", string[] requiredScopes = null)
{
string[] scopes = requiredScopes ?? _configuration["CalledApi:CalledApiScopes"]?.Split(' ');
string apiUrl = (_configuration["CalledApi:CalledApiUrl"] as string)?.TrimEnd('/') + $"/{relativeEndpoint}";
string accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(scopes);
HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, apiUrl);
httpRequestMessage.Headers.Add("Authorization", $"bearer {accessToken}");
string apiResult;
var response = await _httpClient.SendAsync(httpRequestMessage);
if (response.StatusCode == HttpStatusCode.OK)
{
apiResult = await response.Content.ReadAsStringAsync();
}
else
{
apiResult = $"Error calling the API '{apiUrl}'";
}
return apiResult;
}
}
}

View File

@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Graph;
using Microsoft.Identity.Web;
namespace BlazorServerWeb_CSharp
{
public static class MicrosoftGraphServiceExtensions
{
/// <summary>
/// Adds the Microsoft Graph client as a singleton.
/// </summary>
/// <param name="services">Service collection.</param>
/// <param name="initialScopes">Initial scopes.</param>
/// <param name="graphBaseUrl">Base URL for Microsoft graph. This can be
/// changed for instance for applications running in national clouds</param>
public static IServiceCollection AddMicrosoftGraph(this IServiceCollection services,
IEnumerable<string> initialScopes,
string graphBaseUrl = "https://graph.microsoft.com/v1.0")
{
services.AddTokenAcquisition(true);
services.AddSingleton<GraphServiceClient, GraphServiceClient>(serviceProvider =>
{
var tokenAquisitionService = serviceProvider.GetService<ITokenAcquisition>();
GraphServiceClient client = string.IsNullOrWhiteSpace(graphBaseUrl) ?
new GraphServiceClient(new TokenAcquisitionCredentialProvider(tokenAquisitionService, initialScopes)) :
new GraphServiceClient(graphBaseUrl, new TokenAcquisitionCredentialProvider(tokenAquisitionService, initialScopes));
return client;
});
return services;
}
}
}

View File

@ -0,0 +1,27 @@
using Microsoft.Graph;
using Microsoft.Identity.Web;
using System.Collections;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
namespace BlazorServerWeb_CSharp
{
internal class TokenAcquisitionCredentialProvider : IAuthenticationProvider
{
public TokenAcquisitionCredentialProvider(ITokenAcquisition tokenAcquisition, IEnumerable<string> initialScopes)
{
_tokenAcquisition = tokenAcquisition;
_initialScopes = initialScopes;
}
ITokenAcquisition _tokenAcquisition;
IEnumerable<string> _initialScopes;
public async Task AuthenticateRequestAsync(HttpRequestMessage request)
{
request.Headers.Add("Authorization",
$"Bearer {await _tokenAcquisition.GetAccessTokenForUserAsync(_initialScopes)}");
}
}
}

View File

@ -1,21 +1,21 @@
@using Microsoft.AspNetCore.Authentication.AzureADB2C.UI
@using Microsoft.Identity.Web
@using Microsoft.Extensions.Options
@inject IOptionsMonitor<AzureADB2COptions> AzureADB2COptions
@inject IOptionsMonitor<MicrosoftIdentityOptions> microsoftIdentityOptions
<AuthorizeView>
<Authorized>
@if (canEditProfile)
{
<a href="AzureADB2C/Account/EditProfile">Hello, @context.User.Identity.Name!</a>
<a href="MicrosoftIdentity/Account/EditProfile">Hello, @context.User.Identity.Name!</a>
}
else
{
<text>Hello, @context.User.Identity.Name!</text>
}
<a href="AzureADB2C/Account/SignOut">Log out</a>
<a href="MicrosoftIdentity/Account/SignOut">Log out</a>
</Authorized>
<NotAuthorized>
<a href="AzureADB2C/Account/SignIn">Log in</a>
<a href="MicrosoftIdentity/Account/SignIn">Log in</a>
</NotAuthorized>
</AuthorizeView>
@ -24,7 +24,7 @@
protected override void OnInitialized()
{
var options = AzureADB2COptions.Get(AzureADB2CDefaults.AuthenticationScheme);
var options = microsoftIdentityOptions.CurrentValue;
canEditProfile = !string.IsNullOrEmpty(options.EditProfilePolicyId);
}
}

View File

@ -1,9 +1,9 @@
<AuthorizeView>
<Authorized>
Hello, @context.User.Identity.Name!
<a href="AzureAD/Account/SignOut">Log out</a>
<a href="MicrosoftIdentity/Account/SignOut">Log out</a>
</Authorized>
<NotAuthorized>
<a href="AzureAD/Account/SignIn">Log in</a>
<a href="MicrosoftIdentity/Account/SignIn">Log in</a>
</NotAuthorized>
</AuthorizeView>

View File

@ -1,16 +1,18 @@
@inherits LayoutComponentBase
<div class="sidebar">
<NavMenu />
</div>
<div class="main">
<div class="top-row px-4 auth">
<LoginDisplay />
<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<div class="content px-4">
@Body
<div class="main">
<div class="top-row px-4 auth">
<LoginDisplay />
<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
</div>
<div class="content px-4">
@Body
</div>
</div>
</div>

View File

@ -1,15 +1,17 @@
@inherits LayoutComponentBase
<div class="sidebar">
<NavMenu />
</div>
<div class="main">
<div class="top-row px-4">
<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<div class="content px-4">
@Body
<div class="main">
<div class="top-row px-4">
<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
</div>
<div class="content px-4">
@Body
</div>
</div>
</div>

View File

@ -0,0 +1,70 @@
.page {
position: relative;
display: flex;
flex-direction: column;
}
.main {
flex: 1;
}
.sidebar {
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}
.top-row {
background-color: #f7f7f7;
border-bottom: 1px solid #d6d5d5;
justify-content: flex-end;
height: 3.5rem;
display: flex;
align-items: center;
}
.top-row ::deep a, .top-row .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
}
.top-row a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 767.98px) {
.top-row:not(.auth) {
display: none;
}
.top-row.auth {
justify-content: space-between;
}
.top-row a, .top-row .btn-link {
margin-left: 0;
}
}
@media (min-width: 768px) {
.page {
flex-direction: row;
}
.sidebar {
width: 250px;
height: 100vh;
position: sticky;
top: 0;
}
.top-row {
position: sticky;
top: 0;
z-index: 1;
}
.main > div {
padding-left: 2rem !important;
padding-right: 1.5rem !important;
}
}

View File

@ -0,0 +1,42 @@
<div class="top-row pl-4 navbar navbar-dark">
<a class="navbar-brand" href="">BlazorServerWeb-CSharp</a>
<button class="navbar-toggler" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
</div>
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="counter">
<span class="oi oi-plus" aria-hidden="true"></span> Counter
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="fetchdata">
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="showprofile">
<span class="oi oi-list-rich" aria-hidden="true"></span> Show profile
</NavLink>
</li>
</ul>
</div>
@code {
private bool collapseNavMenu = true;
private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;
private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
}
}

View File

@ -0,0 +1,42 @@
<div class="top-row pl-4 navbar navbar-dark">
<a class="navbar-brand" href="">BlazorServerWeb-CSharp</a>
<button class="navbar-toggler" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
</div>
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="counter">
<span class="oi oi-plus" aria-hidden="true"></span> Counter
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="fetchdata">
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="callwebapi">
<span class="oi oi-list-rich" aria-hidden="true"></span> Call Web API
</NavLink>
</li>
</ul>
</div>
@code {
private bool collapseNavMenu = true;
private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;
private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
}
}

View File

@ -0,0 +1,62 @@
.navbar-toggler {
background-color: rgba(255, 255, 255, 0.1);
}
.top-row {
height: 3.5rem;
background-color: rgba(0,0,0,0.4);
}
.navbar-brand {
font-size: 1.1rem;
}
.oi {
width: 2rem;
font-size: 1.1rem;
vertical-align: text-top;
top: -2px;
}
.nav-item {
font-size: 0.9rem;
padding-bottom: 0.5rem;
}
.nav-item:first-of-type {
padding-top: 1rem;
}
.nav-item:last-of-type {
padding-bottom: 1rem;
}
.nav-item ::deep a {
color: #d7d7d7;
border-radius: 4px;
height: 3rem;
display: flex;
align-items: center;
line-height: 3rem;
}
.nav-item ::deep a.active {
background-color: rgba(255,255,255,0.25);
color: white;
}
.nav-item ::deep a:hover {
background-color: rgba(255,255,255,0.1);
color: white;
}
@media (min-width: 768px) {
.navbar-toggler {
display: none;
}
.collapse {
/* Never collapse the sidebar for wide screens */
display: block;
}
}

View File

@ -4,17 +4,16 @@ using System.Linq;
using System.Threading.Tasks;
#if (OrganizationalAuth || IndividualB2CAuth)
using Microsoft.AspNetCore.Authentication;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.UI;
using Microsoft.Identity.Web.TokenCacheProviders.InMemory;
#endif
#if (OrganizationalAuth)
using Microsoft.AspNetCore.Authentication.AzureAD.UI;
#if (MultiOrgAuth)
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
#endif
using Microsoft.AspNetCore.Authorization;
#endif
#if (IndividualB2CAuth)
using Microsoft.AspNetCore.Authentication.AzureADB2C.UI;
#endif
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components;
#if (IndividualLocalAuth)
@ -42,6 +41,9 @@ using Microsoft.IdentityModel.Tokens;
using BlazorServerWeb_CSharp.Areas.Identity;
#endif
using BlazorServerWeb_CSharp.Data;
#if (GenerateGraph)
using Microsoft.Graph;
#endif
namespace BlazorServerWeb_CSharp
{
@ -70,59 +72,39 @@ namespace BlazorServerWeb_CSharp
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
#elif (OrganizationalAuth)
#pragma warning disable CS0618 // Type or member is obsolete
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
#pragma warning restore CS0618 // Type or member is obsolete
#if (MultiOrgAuth)
#pragma warning disable CS0618 // Type or member is obsolete
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
#pragma warning restore CS0618 // Type or member is obsolete
{
options.TokenValidationParameters = new TokenValidationParameters
{
// Instead of using the default validation (validating against a single issuer value, as we do in
// line of business apps), we inject our own multitenant validation logic
ValidateIssuer = false,
// If the app is meant to be accessed by entire organizations, add your issuer validation logic here.
//IssuerValidator = (issuer, securityToken, validationParameters) => {
// if (myIssuerValidationLogic(issuer)) return issuer;
//}
};
options.Events = new OpenIdConnectEvents
{
OnTicketReceived = context =>
{
// If your authentication logic is based on users then add your logic here
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
context.Response.Redirect("/Error");
context.HandleResponse(); // Suppress the exception
return Task.CompletedTask;
},
// If your application needs to authenticate single users, add your user validation below.
//OnTokenValidated = context =>
//{
// return myUserValidationLogic(context.Ticket.Principal);
//}
};
});
#if (GenerateApiOrGraph)
string[] scopes = Configuration.GetValue<string>("CalledApi:CalledApiScopes")?.Split(' ');
#endif
#if (GenerateApiOrGraph)
services.AddMicrosoftWebAppAuthentication(Configuration, "AzureAd")
.AddMicrosoftWebAppCallsWebApi(Configuration, scopes, "AzureAd")
.AddInMemoryTokenCaches();
#else
services.AddMicrosoftWebAppAuthentication(Configuration, "AzureAd");
#endif
#if (GenerateApi)
services.AddDownstreamWebApiService(Configuration);
#endif
#if (GenerateGraph)
services.AddMicrosoftGraph(scopes, Configuration.GetValue<string>("CalledApi:CalledApiUrl"));
#endif
#elif (IndividualB2CAuth)
#pragma warning disable CS0618 // Type or member is obsolete
services.AddAuthentication(AzureADB2CDefaults.AuthenticationScheme)
.AddAzureADB2C(options => Configuration.Bind("AzureAdB2C", options));
#pragma warning restore CS0618 // Type or member is obsolete
#if (GenerateApi)
string[] scopes = Configuration.GetValue<string>("CalledApi:CalledApiScopes")?.Split(' ');
#endif
#if (OrganizationalAuth)
services.AddControllersWithViews();
#if (GenerateApi)
services.AddMicrosoftWebAppAuthentication(Configuration, "AzureAdB2C")
.AddMicrosoftWebAppCallsWebApi(Configuration, scopes, "AzureAdB2C")
.AddInMemoryTokenCaches();
services.AddDownstreamWebApiService(Configuration);
#else
services.AddMicrosoftWebAppAuthentication(Configuration, "AzureAdB2C");
#endif
#endif
#if (OrganizationalAuth || IndividualB2CAuth)
services.AddControllersWithViews()
.AddMicrosoftIdentityUI();
services.AddAuthorization(options =>
{
@ -132,7 +114,12 @@ namespace BlazorServerWeb_CSharp
#endif
services.AddRazorPages();
#if (OrganizationalAuth || IndividualB2CAuth)
services.AddServerSideBlazor()
.AddMicrosoftIdentityConsentHandler();
#else
services.AddServerSideBlazor();
#endif
#if (IndividualLocalAuth)
services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<IdentityUser>>();
#endif

View File

@ -5,6 +5,12 @@
// "ClientId": "11111111-1111-1111-11111111111111111",
// "CallbackPath": "/signin-oidc",
// "Domain": "qualified.domain.name",
// "SignedOutCallbackPath": "/signout/MySignUpSignInPolicyId",
//#if (GenerateApi)
// "ClientSecret": "secret-from-app-registration",
// "ClientCertificates" : [
// ],
//#endif
// "SignUpSignInPolicyId": "MySignUpSignInPolicyId",
// "ResetPasswordPolicyId": "MyResetPasswordPolicyId",
// "EditProfilePolicyId": "MyEditProfilePolicyId"
@ -19,18 +25,36 @@
// "TenantId": "22222222-2222-2222-2222-222222222222",
//#endif
// "ClientId": "11111111-1111-1111-11111111111111111",
//#if (GenerateApiOrGraph)
// "ClientSecret": "secret-from-app-registration",
// "ClientCertificates" : [
// ],
//#endif
// "CallbackPath": "/signin-oidc"
// },
//#endif
////#endif
////#if (GenerateApiOrGraph)
// "CalledApi": {
// /*
// 'CalledApiScopes' contains space separated scopes of the Web API you want to call. This can be:
// - a scope for a V2 application (for instance api://b3682cc7-8b30-4bd2-aaba-080c6bf0fd31/access_as_user)
// - a scope corresponding to a V1 application (for instance <App ID URI>/.default, where <App ID URI> is the
// App ID URI of a legacy v1 Web application
// Applications are registered in the https://portal.azure.com portal.
// */
// "CalledApiScopes": "user.read",
// "CalledApiUrl": "[WebApiUrl]"
// },
////#endif
////#if (IndividualLocalAuth)
// "ConnectionStrings": {
////#if (UseLocalDB)
//#if (UseLocalDB)
// "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-BlazorServerWeb-CSharp-53bc9b9d-9d6a-45d4-8429-2a2761773502;Trusted_Connection=True;MultipleActiveResultSets=true"
////#else
//#else
// "DefaultConnection": "DataSource=app.db;Cache=Shared"
//#endif
// },
//#endif
////#endif
"Logging": {
"LogLevel": {
"Default": "Information",

View File

@ -14,98 +14,10 @@ a, .btn-link {
border-color: #1861ac;
}
app {
position: relative;
display: flex;
flex-direction: column;
}
.top-row {
height: 3.5rem;
display: flex;
align-items: center;
z-index: 10;
}
.main {
flex: 1;
}
.main .top-row {
background-color: #f7f7f7;
border-bottom: 1px solid #d6d5d5;
justify-content: flex-end;
}
.main .top-row > a, .main .top-row .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
}
.main .top-row a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
.sidebar {
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}
.sidebar .top-row {
background-color: rgba(0,0,0,0.4);
}
.sidebar .navbar-brand {
font-size: 1.1rem;
}
.sidebar .oi {
width: 2rem;
font-size: 1.1rem;
vertical-align: text-top;
top: -2px;
}
.sidebar .nav-item {
font-size: 0.9rem;
padding-bottom: 0.5rem;
}
.sidebar .nav-item:first-of-type {
padding-top: 1rem;
}
.sidebar .nav-item:last-of-type {
padding-bottom: 1rem;
}
.sidebar .nav-item a {
color: #d7d7d7;
border-radius: 4px;
height: 3rem;
display: flex;
align-items: center;
line-height: 3rem;
}
.sidebar .nav-item a.active {
background-color: rgba(255,255,255,0.25);
color: white;
}
.sidebar .nav-item a:hover {
background-color: rgba(255,255,255,0.1);
color: white;
}
.content {
padding-top: 1.1rem;
}
.navbar-toggler {
background-color: rgba(255, 255, 255, 0.1);
}
.valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050;
}
@ -136,50 +48,3 @@ app {
right: 0.75rem;
top: 0.5rem;
}
@media (max-width: 767.98px) {
.main .top-row:not(.auth) {
display: none;
}
.main .top-row.auth {
justify-content: space-between;
}
.main .top-row a, .main .top-row .btn-link {
margin-left: 0;
}
}
@media (min-width: 768px) {
app {
flex-direction: row;
}
.sidebar {
width: 250px;
height: 100vh;
position: sticky;
top: 0;
}
.main .top-row {
position: sticky;
top: 0;
z-index: 1;
}
.main > div {
padding-left: 2rem !important;
padding-right: 1.5rem !important;
}
.navbar-toggler {
display: none;
}
.sidebar .collapse {
/* Never collapse the sidebar for wide screens */
display: block;
}
}

View File

@ -77,6 +77,18 @@
"NoHttps": {
"longName": "no-https",
"shortName": ""
},
"CalledApiUrl": {
"longName": "called-api-url",
"shortName": ""
},
"CalledApiScopes": {
"longName": "called-api-scopes",
"shortName": ""
},
"CallsMicrosoftGraph": {
"longName": "calls-graph",
"shortName": ""
}
}
}

View File

@ -211,6 +211,19 @@
"Server/Controllers/OidcConfigurationController.cs",
"Server/Models/ApplicationUser.cs"
]
},
{
"condition": "(Hosted && !GenerateApi)",
"exclude": [
"Server/Services/DownstreamWebApi.cs"
]
},
{
"condition": "(Hosted &&!GenerateGraph)",
"exclude": [
"Server/Services/MicrosoftGraphServiceExtensions.cs",
"Server/Services/TokenAcquisitionCredentialProvider.cs"
]
}
]
}
@ -297,7 +310,7 @@
"datatype": "string",
"defaultValue": "https://login.microsoftonline.com/",
"replaces": "https:////login.microsoftonline.com/",
"description": "The Azure Active Directory instance to connect to (use with SingleOrg)."
"description": "The Azure Active Directory instance to connect to (use with SingleOrg auth)."
},
"ClientId": {
"type": "parameter",
@ -452,6 +465,37 @@
"parameters": {
"format": "yyyy"
}
},
"CalledApiUrl": {
"type": "parameter",
"datatype": "string",
"replaces": "[WebApiUrl]",
"defaultValue" : "https://graph.microsoft.com/v1.0/me",
"description": "URL of the API to call from the web app. This option only applies if --auth SingleOrg, --auth MultiOrg or --auth IndividualB2C without and ASP.NET Core host is specified."
},
"CallsMicrosoftGraph": {
"type": "parameter",
"datatype": "bool",
"defaultValue": "false",
"description": "Specifies if the web app calls Microsoft Graph. This option only applies if --auth SingleOrg or --auth MultiOrg is specified."
},
"CalledApiScopes": {
"type": "parameter",
"datatype": "string",
"replaces" : "user.read",
"description": "Scopes to request to call the API from the web app. This option only applies if --auth SingleOrg, --auth MultiOrg or --auth IndividualB2C without and ASP.NET Core host is specified."
},
"GenerateApi": {
"type": "computed",
"value": "(( (IndividualB2CAuth && !Hosted) || OrganizationalAuth) && (CalledApiUrl != \"https://graph.microsoft.com/v1.0/me\" || CalledApiScopes != \"user.read\"))"
},
"GenerateGraph": {
"type": "computed",
"value": "(OrganizationalAuth && CallsMicrosoftGraph)"
},
"GenerateApiOrGraph": {
"type": "computed",
"value": "(GenerateApi || GenerateGraph)"
}
},
"tags": {

View File

@ -22,7 +22,7 @@ namespace ComponentsWebAssembly_CSharp
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
builder.RootComponents.Add<App>("#app");
#if (!Hosted || NoAuth)
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

View File

@ -1,16 +1,18 @@
@inherits LayoutComponentBase
<div class="sidebar">
<NavMenu />
</div>
<div class="main">
<div class="top-row px-4 auth">
<LoginDisplay />
<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<div class="content px-4">
@Body
<div class="main">
<div class="top-row px-4 auth">
<LoginDisplay />
<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
</div>
<div class="content px-4">
@Body
</div>
</div>
</div>

View File

@ -1,15 +1,17 @@
@inherits LayoutComponentBase
<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 class="page">
<div class="sidebar">
<NavMenu />
</div>
<div class="content px-4">
@Body
<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>
</div>
</div>

View File

@ -0,0 +1,70 @@
.page {
position: relative;
display: flex;
flex-direction: column;
}
.main {
flex: 1;
}
.sidebar {
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}
.top-row {
background-color: #f7f7f7;
border-bottom: 1px solid #d6d5d5;
justify-content: flex-end;
height: 3.5rem;
display: flex;
align-items: center;
}
.top-row ::deep a, .top-row .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
}
.top-row a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 767.98px) {
.top-row:not(.auth) {
display: none;
}
.top-row.auth {
justify-content: space-between;
}
.top-row a, .top-row .btn-link {
margin-left: 0;
}
}
@media (min-width: 768px) {
.page {
flex-direction: row;
}
.sidebar {
width: 250px;
height: 100vh;
position: sticky;
top: 0;
}
.top-row {
position: sticky;
top: 0;
z-index: 1;
}
.main > div {
padding-left: 2rem !important;
padding-right: 1.5rem !important;
}
}

View File

@ -0,0 +1,62 @@
.navbar-toggler {
background-color: rgba(255, 255, 255, 0.1);
}
.top-row {
height: 3.5rem;
background-color: rgba(0,0,0,0.4);
}
.navbar-brand {
font-size: 1.1rem;
}
.oi {
width: 2rem;
font-size: 1.1rem;
vertical-align: text-top;
top: -2px;
}
.nav-item {
font-size: 0.9rem;
padding-bottom: 0.5rem;
}
.nav-item:first-of-type {
padding-top: 1rem;
}
.nav-item:last-of-type {
padding-bottom: 1rem;
}
.nav-item ::deep a {
color: #d7d7d7;
border-radius: 4px;
height: 3rem;
display: flex;
align-items: center;
line-height: 3rem;
}
.nav-item ::deep a.active {
background-color: rgba(255,255,255,0.25);
color: white;
}
.nav-item ::deep a:hover {
background-color: rgba(255,255,255,0.1);
color: white;
}
@media (min-width: 768px) {
.navbar-toggler {
display: none;
}
.collapse {
/* Never collapse the sidebar for wide screens */
display: block;
}
}

View File

@ -14,98 +14,10 @@ a, .btn-link {
border-color: #1861ac;
}
app {
position: relative;
display: flex;
flex-direction: column;
}
.top-row {
height: 3.5rem;
display: flex;
align-items: center;
z-index: 10;
}
.main {
flex: 1;
}
.main .top-row {
background-color: #f7f7f7;
border-bottom: 1px solid #d6d5d5;
justify-content: flex-end;
}
.main .top-row > a, .main .top-row .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
}
.main .top-row a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
.sidebar {
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}
.sidebar .top-row {
background-color: rgba(0,0,0,0.4);
}
.sidebar .navbar-brand {
font-size: 1.1rem;
}
.sidebar .oi {
width: 2rem;
font-size: 1.1rem;
vertical-align: text-top;
top: -2px;
}
.sidebar .nav-item {
font-size: 0.9rem;
padding-bottom: 0.5rem;
}
.sidebar .nav-item:first-of-type {
padding-top: 1rem;
}
.sidebar .nav-item:last-of-type {
padding-bottom: 1rem;
}
.sidebar .nav-item a {
color: #d7d7d7;
border-radius: 4px;
height: 3rem;
display: flex;
align-items: center;
line-height: 3rem;
}
.sidebar .nav-item a.active {
background-color: rgba(255,255,255,0.25);
color: white;
}
.sidebar .nav-item a:hover {
background-color: rgba(255,255,255,0.1);
color: white;
}
.content {
padding-top: 1.1rem;
}
.navbar-toggler {
background-color: rgba(255, 255, 255, 0.1);
}
.valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050;
}
@ -130,56 +42,9 @@ app {
z-index: 1000;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
@media (max-width: 767.98px) {
.main .top-row:not(.auth) {
display: none;
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
.main .top-row.auth {
justify-content: space-between;
}
.main .top-row a, .main .top-row .btn-link {
margin-left: 0;
}
}
@media (min-width: 768px) {
app {
flex-direction: row;
}
.sidebar {
width: 250px;
height: 100vh;
position: sticky;
top: 0;
}
.main .top-row {
position: sticky;
top: 0;
z-index: 1;
}
.main > div {
padding-left: 2rem !important;
padding-right: 1.5rem !important;
}
.navbar-toggler {
display: none;
}
.sidebar .collapse {
/* Never collapse the sidebar for wide screens */
display: block;
}
}

View File

@ -8,6 +8,7 @@
<base href="/" />
<link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="css/app.css" rel="stylesheet" />
<link href="_framework/scoped.styles.css" rel="stylesheet" />
<!--#if PWA -->
<link href="manifest.json" rel="manifest" />
<link rel="apple-touch-icon" sizes="512x512" href="icon-512.png" />
@ -15,7 +16,7 @@
</head>
<body>
<app>Loading...</app>
<div id="app">Loading...</div>
<div id="blazor-error-ui">
An unhandled error has occurred.

View File

@ -1,13 +1,25 @@
using ComponentsWebAssembly_CSharp.Shared;
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
#if (!NoAuth)
using Microsoft.AspNetCore.Authorization;
#endif
#if (GenerateApi)
using Microsoft.Extensions.Configuration;
using Microsoft.Identity.Web;
using System.Net;
using System.Net.Http;
#endif
#if (GenerateGraph)
using Microsoft.Graph;
#endif
using Microsoft.AspNetCore.Mvc;
#if (OrganizationalAuth || IndividualB2CAuth)
using Microsoft.Identity.Web.Resource;
#endif
using Microsoft.Extensions.Logging;
using ComponentsWebAssembly_CSharp.Shared;
namespace ComponentsWebAssembly_CSharp.Server.Controllers
{
@ -23,16 +35,28 @@ namespace ComponentsWebAssembly_CSharp.Server.Controllers
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> logger;
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
// The Web API will only accept tokens 1) for users, and 2) having the access_as_user scope for this API
static readonly string[] scopeRequiredByApi = new string[] { "access_as_user" };
#if (GenerateApi)
private readonly IDownstreamWebApi _downstreamWebApi;
public WeatherForecastController(ILogger<WeatherForecastController> logger,
IDownstreamWebApi downstreamWebApi)
{
this.logger = logger;
_logger = logger;
_downstreamWebApi = downstreamWebApi;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
public async Task<IEnumerable<WeatherForecast>> Get()
{
HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
string downstreamApiResult = await _downstreamWebApi.CallWebApiAsync();
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
@ -42,5 +66,54 @@ namespace ComponentsWebAssembly_CSharp.Server.Controllers
})
.ToArray();
}
#elseif (GenerateGraph)
private readonly GraphServiceClient _graphServiceClient;
public WeatherForecastController(ILogger<WeatherForecastController> logger,
GraphServiceClient graphServiceClient)
{
_logger = logger;
_graphServiceClient = graphServiceClient;
}
[HttpGet]
public async Task<IEnumerable<WeatherForecast>> Get()
{
HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
var user = await _graphServiceClient.Me.Request().GetAsync();
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();
}
#else
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
#if (OrganizationalAuth || IndividualB2CAuth)
HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
#endif
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();
}
#endif
}
}

View File

@ -0,0 +1,72 @@
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Identity.Web;
namespace ComponentsWebAssembly_CSharp.Server
{
public interface IDownstreamWebApi
{
Task<string> CallWebApiAsync(string relativeEndpoint = "", string[] requiredScopes = null);
}
public static class DownstreamWebApiExtensions
{
public static void AddDownstreamWebApiService(this IServiceCollection services, IConfiguration configuration)
{
// https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests
services.AddHttpClient<IDownstreamWebApi, DownstreamWebApi>();
}
}
public class DownstreamWebApi : IDownstreamWebApi
{
private readonly ITokenAcquisition _tokenAcquisition;
private readonly IConfiguration _configuration;
private readonly HttpClient _httpClient;
public DownstreamWebApi(
ITokenAcquisition tokenAcquisition,
IConfiguration configuration,
HttpClient httpClient)
{
_tokenAcquisition = tokenAcquisition;
_configuration = configuration;
_httpClient = httpClient;
}
/// <summary>
/// Calls the Web API with the required scopes
/// </summary>
/// <param name="requireScopes">[Optional] Scopes required to call the Web API. If
/// not specified, uses scopes from the configuration</param>
/// <param name="relativeEndpoint">Endpoint relative to the CalledApiUrl configuration</param>
/// <returns>A JSON string representing the result of calling the Web API</returns>
public async Task<string> CallWebApiAsync(string relativeEndpoint = "", string[] requiredScopes = null)
{
string[] scopes = requiredScopes ?? _configuration["CalledApi:CalledApiScopes"]?.Split(' ');
string apiUrl = (_configuration["CalledApi:CalledApiUrl"] as string)?.TrimEnd('/') + $"/{relativeEndpoint}";
string accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(scopes);
HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, apiUrl);
httpRequestMessage.Headers.Add("Authorization", $"bearer {accessToken}");
string apiResult;
var response = await _httpClient.SendAsync(httpRequestMessage);
if (response.StatusCode == HttpStatusCode.OK)
{
apiResult = await response.Content.ReadAsStringAsync();
}
else
{
apiResult = $"Error calling the API '{apiUrl}'";
}
return apiResult;
}
}
}

View File

@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Graph;
using Microsoft.Identity.Web;
namespace ComponentsWebAssembly_CSharp.Server
{
public static class MicrosoftGraphServiceExtensions
{
/// <summary>
/// Adds the Microsoft Graph client as a singleton.
/// </summary>
/// <param name="services">Service collection.</param>
/// <param name="initialScopes">Initial scopes.</param>
/// <param name="graphBaseUrl">Base URL for Microsoft graph. This can be
/// changed for instance for applications running in national clouds</param>
public static IServiceCollection AddMicrosoftGraph(this IServiceCollection services,
IEnumerable<string> initialScopes,
string graphBaseUrl = "https://graph.microsoft.com/v1.0")
{
services.AddTokenAcquisition(true);
services.AddSingleton<GraphServiceClient, GraphServiceClient>(serviceProvider =>
{
var tokenAquisitionService = serviceProvider.GetService<ITokenAcquisition>();
GraphServiceClient client = string.IsNullOrWhiteSpace(graphBaseUrl) ?
new GraphServiceClient(new TokenAcquisitionCredentialProvider(tokenAquisitionService, initialScopes)) :
new GraphServiceClient(graphBaseUrl, new TokenAcquisitionCredentialProvider(tokenAquisitionService, initialScopes));
return client;
});
return services;
}
}
}

View File

@ -0,0 +1,27 @@
using Microsoft.Graph;
using Microsoft.Identity.Web;
using System.Collections;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
namespace ComponentsWebAssembly_CSharp.Server
{
internal class TokenAcquisitionCredentialProvider : IAuthenticationProvider
{
public TokenAcquisitionCredentialProvider(ITokenAcquisition tokenAcquisition, IEnumerable<string> initialScopes)
{
_tokenAcquisition = tokenAcquisition;
_initialScopes = initialScopes;
}
ITokenAcquisition _tokenAcquisition;
IEnumerable<string> _initialScopes;
public async Task AuthenticateRequestAsync(HttpRequestMessage request)
{
request.Headers.Add("Authorization",
$"Bearer {await _tokenAcquisition.GetAccessTokenForUserAsync(_initialScopes)}");
}
}
}

View File

@ -1,17 +1,10 @@
#if (OrganizationalAuth || IndividualB2CAuth || IndividualLocalAuth)
using Microsoft.AspNetCore.Authentication;
#endif
#if (OrganizationalAuth)
using Microsoft.AspNetCore.Authentication.AzureAD.UI;
#endif
#if (IndividualB2CAuth)
using Microsoft.AspNetCore.Authentication.AzureADB2C.UI;
#endif
using Microsoft.AspNetCore.Builder;
#if (IndividualLocalAuth)
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI;
#if (OrganizationalAuth || IndividualB2CAuth)
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.TokenCacheProviders.InMemory;
#endif
#if (RequiresHttps)
using Microsoft.AspNetCore.HttpsPolicy;
@ -29,6 +22,9 @@ using System.Linq;
using ComponentsWebAssembly_CSharp.Server.Data;
using ComponentsWebAssembly_CSharp.Server.Models;
#endif
#if (GenerateGraph)
using Microsoft.Graph;
#endif
namespace ComponentsWebAssembly_CSharp.Server
{
@ -47,13 +43,13 @@ namespace ComponentsWebAssembly_CSharp.Server
{
#if (IndividualLocalAuth)
services.AddDbContext<ApplicationDbContext>(options =>
#if (UseLocalDB)
#if (UseLocalDB)
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
#else
#else
options.UseSqlite(
Configuration.GetConnectionString("DefaultConnection")));
#endif
#endif
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
@ -65,15 +61,32 @@ namespace ComponentsWebAssembly_CSharp.Server
.AddIdentityServerJwt();
#endif
#if (OrganizationalAuth)
#pragma warning disable CS0618 // Type or member is obsolete
services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
.AddAzureADBearer(options => Configuration.Bind("AzureAd", options));
#pragma warning restore CS0618 // Type or member is obsolete
#if (GenerateApiOrGraph)
// Adds Microsoft Identity platform (AAD v2.0) support to protect this Api
services.AddMicrosoftWebApiAuthentication(Configuration, "AzureAd")
.AddMicrosoftWebApiCallsWebApi(Configuration, "AzureAd")
.AddInMemoryTokenCaches();
#else
// Adds Microsoft Identity platform (AAD v2.0) support to protect this Api
services.AddMicrosoftWebApiAuthentication(Configuration, "AzureAd");
#endif
#if (GenerateApi)
services.AddDownstreamWebApiService(Configuration);
#endif
#if (GenerateGraph)
services.AddMicrosoftGraph(Configuration.GetValue<string>("CalledApi:CalledApiScopes")?.Split(' '),
Configuration.GetValue<string>("CalledApi:CalledApiUrl"));
#endif
#elif (IndividualB2CAuth)
#pragma warning disable CS0618 // Type or member is obsolete
services.AddAuthentication(AzureADB2CDefaults.BearerAuthenticationScheme)
.AddAzureADB2CBearer(options => Configuration.Bind("AzureAdB2C", options));
#pragma warning restore CS0618 // Type or member is obsolete
#if (GenerateApi)
services.AddMicrosoftWebApiAuthentication(Configuration, "AzureAdB2C")
.AddMicrosoftWebApiCallsWebApi(Configuration, "AzureAdB2C")
.AddInMemoryTokenCaches();
services.AddDownstreamWebApiService(Configuration);
#else
services.AddMicrosoftWebApiAuthentication(Configuration, "AzureAdB2C");
#endif
#endif
services.AddControllersWithViews();

View File

@ -12,23 +12,51 @@
// "Instance": "https:////aadB2CInstance.b2clogin.com/",
// "ClientId": "11111111-1111-1111-11111111111111111",
// "Domain": "qualified.domain.name",
//#if (GenerateApi)
// "ClientSecret": "secret-from-app-registration",
// "ClientCertificates" : [
// ],
//#endif
// "SignUpSignInPolicyId": "MySignUpSignInPolicyId"
// },
////#elseif (OrganizationalAuth)
// "AzureAd": {
//#if (!SingleOrgAuth)
// "Instance": "https:////login.microsoftonline.com/common",
//#else
// "Instance": "https:////login.microsoftonline.com/",
// "Domain": "qualified.domain.name",
// "TenantId": "22222222-2222-2222-2222-222222222222",
//#endif
// "ClientId": "11111111-1111-1111-11111111111111111",
//#if (GenerateApiOrGraph)
// "ClientSecret": "secret-from-app-registration",
// "ClientCertificates" : [
// ],
//#endif
// "CallbackPath": "/signin-oidc"
// },
////#endif
////#if (GenerateApiOrGraph)
// "CalledApi": {
// /*
// 'CalledApiScopes' contains space separated scopes of the Web API you want to call. This can be:
// - a scope for a V2 application (for instance api://b3682cc7-8b30-4bd2-aaba-080c6bf0fd31/access_as_user)
// - a scope corresponding to a V1 application (for instance <App ID URI>/.default, where <App ID URI> is the
// App ID URI of a legacy v1 Web application
// Applications are registered in the https://portal.azure.com portal.
// */
// "CalledApiScopes": "user.read",
// "CalledApiUrl": "[WebApiUrl]"
// },
////#endif
"Logging": {
"LogLevel": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
}
},
////#if (IndividualLocalAuth)
// "IdentityServer": {
// "Clients": {

View File

@ -71,13 +71,12 @@ namespace Company.WebApplication1
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
#elif (OrganizationalAuth)
services.AddMicrosoftWebAppAuthentication(Configuration, "AzureAd")
#if (GenerateApiOrGraph)
.AddMicrosoftWebAppCallsWebApi(Configuration,
"AzureAd")
.AddInMemoryTokenCaches();
services.AddMicrosoftWebAppAuthentication(Configuration, "AzureAd")
.AddMicrosoftWebAppCallsWebApi(Configuration, "AzureAd")
.AddInMemoryTokenCaches();
#else
;
services.AddMicrosoftWebAppAuthentication(Configuration, "AzureAd");
#endif
#if (GenerateApi)
services.AddDownstreamWebApiService(Configuration);
@ -87,15 +86,14 @@ namespace Company.WebApplication1
Configuration.GetValue<string>("CalledApi:CalledApiUrl"));
#endif
#elif (IndividualB2CAuth)
services.AddMicrosoftWebAppAuthentication(Configuration, "AzureAdB2C")
#if (GenerateApi)
.AddMicrosoftWebAppCallsWebApi(Configuration,
"AzureAdB2C")
.AddInMemoryTokenCaches();
services.AddMicrosoftWebAppAuthentication(Configuration, "AzureAdB2C")
.AddMicrosoftWebAppCallsWebApi(Configuration, "AzureAdB2C")
.AddInMemoryTokenCaches();
services.AddDownstreamWebApiService(Configuration);
#else
;
services.AddMicrosoftWebAppAuthentication(Configuration, "AzureAdB2C");
#endif
#endif
#if (OrganizationalAuth)
@ -106,11 +104,11 @@ namespace Company.WebApplication1
options.FallbackPolicy = options.DefaultPolicy;
});
services.AddRazorPages()
.AddMvcOptions(options => {})
.AddMicrosoftIdentityUI();
.AddMvcOptions(options => {})
.AddMicrosoftIdentityUI();
#elif (IndividualB2CAuth)
services.AddRazorPages()
.AddMicrosoftIdentityUI();
.AddMicrosoftIdentityUI();
#else
services.AddRazorPages();
#endif

View File

@ -71,13 +71,12 @@ namespace Company.WebApplication1
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
#elif (OrganizationalAuth)
services.AddMicrosoftWebAppAuthentication(Configuration, "AzureAd")
#if (GenerateApiOrGraph)
.AddMicrosoftWebAppCallsWebApi(Configuration,
"AzureAd")
.AddInMemoryTokenCaches();
services.AddMicrosoftWebAppAuthentication(Configuration, "AzureAd")
.AddMicrosoftWebAppCallsWebApi(Configuration, "AzureAd")
.AddInMemoryTokenCaches();
#else
;
services.AddMicrosoftWebAppAuthentication(Configuration, "AzureAd");
#endif
#if (GenerateApi)
services.AddDownstreamWebApiService(Configuration);
@ -87,15 +86,14 @@ namespace Company.WebApplication1
Configuration.GetValue<string>("CalledApi:CalledApiUrl"));
#endif
#elif (IndividualB2CAuth)
services.AddMicrosoftWebAppAuthentication(Configuration, "AzureAdB2C")
#if (GenerateApi)
.AddMicrosoftWebAppCallsWebApi(Configuration,
"AzureAdB2C")
.AddInMemoryTokenCaches();
services.AddMicrosoftWebAppAuthentication(Configuration, "AzureAdB2C")
.AddMicrosoftWebAppCallsWebApi(Configuration, "AzureAdB2C")
.AddInMemoryTokenCaches();
services.AddDownstreamWebApiService(Configuration);
#else
;
services.AddMicrosoftWebAppAuthentication(Configuration, "AzureAdB2C");
#endif
#endif
#if (OrganizationalAuth)
@ -112,7 +110,7 @@ namespace Company.WebApplication1
#endif
#if (OrganizationalAuth || IndividualB2CAuth)
services.AddRazorPages()
.AddMicrosoftIdentityUI();
.AddMicrosoftIdentityUI();
#endif
}

View File

@ -41,15 +41,15 @@ namespace Company.WebApplication1
public void ConfigureServices(IServiceCollection services)
{
#if (OrganizationalAuth)
#if (GenerateApiOrGraph)
// Adds Microsoft Identity platform (AAD v2.0) support to protect this Api
services.AddMicrosoftWebApiAuthentication(Configuration, "AzureAd")
#if (GenerateApiOrGraph)
.AddMicrosoftWebApiCallsWebApi(Configuration,
"AzureAd")
.AddInMemoryTokenCaches();
.AddMicrosoftWebApiCallsWebApi(Configuration, "AzureAd")
.AddInMemoryTokenCaches();
#else
;
// Adds Microsoft Identity platform (AAD v2.0) support to protect this Api
services.AddMicrosoftWebApiAuthentication(Configuration, "AzureAd");
#endif
#if (GenerateApi)
services.AddDownstreamWebApiService(Configuration);
@ -59,15 +59,14 @@ namespace Company.WebApplication1
Configuration.GetValue<string>("CalledApi:CalledApiUrl"));
#endif
#elif (IndividualB2CAuth)
services.AddMicrosoftWebApiAuthentication(Configuration, "AzureAdB2C")
#if (GenerateApi)
.AddMicrosoftWebApiCallsWebApi(Configuration,
"AzureAdB2C")
.AddInMemoryTokenCaches();
services.AddMicrosoftWebApiAuthentication(Configuration, "AzureAdB2C")
.AddMicrosoftWebApiCallsWebApi(Configuration, "AzureAdB2C")
.AddInMemoryTokenCaches();
services.AddDownstreamWebApiService(Configuration);
#else
;
services.AddMicrosoftWebApiAuthentication(Configuration, "AzureAdB2C");
#endif
#endif

View File

@ -2,7 +2,6 @@
<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<RestoreAdditionalProjectSources>${RestoreAdditionalProjectSources}</RestoreAdditionalProjectSources>
<!-- This sets an option which prevents the tests from rolling forward into a newer shared framework. -->
<UserRuntimeConfig>$(MSBuildThisFileDirectory)runtimeconfig.norollforward.json</UserRuntimeConfig>
</PropertyGroup>

View File

@ -921,7 +921,9 @@
"Properties/launchSettings.json",
"Shared/LoginDisplay.razor",
"Shared/MainLayout.razor",
"Shared/MainLayout.razor.css",
"Shared/NavMenu.razor",
"Shared/NavMenu.razor.css",
"Shared/SurveyPrompt.razor",
"wwwroot/favicon.ico",
"wwwroot/css/site.css",
@ -958,7 +960,9 @@
"Properties/launchSettings.json",
"Shared/LoginDisplay.razor",
"Shared/MainLayout.razor",
"Shared/MainLayout.razor.css",
"Shared/NavMenu.razor",
"Shared/NavMenu.razor.css",
"Shared/SurveyPrompt.razor",
"wwwroot/favicon.ico",
"wwwroot/css/site.css",
@ -995,7 +999,9 @@
"Properties/launchSettings.json",
"Shared/LoginDisplay.razor",
"Shared/MainLayout.razor",
"Shared/MainLayout.razor.css",
"Shared/NavMenu.razor",
"Shared/NavMenu.razor.css",
"Shared/SurveyPrompt.razor",
"wwwroot/favicon.ico",
"wwwroot/css/site.css",
@ -1031,7 +1037,9 @@
"Pages/_Host.cshtml",
"Properties/launchSettings.json",
"Shared/MainLayout.razor",
"Shared/MainLayout.razor.css",
"Shared/NavMenu.razor",
"Shared/NavMenu.razor.css",
"Shared/SurveyPrompt.razor",
"wwwroot/favicon.ico",
"wwwroot/css/site.css",
@ -1068,7 +1076,9 @@
"Properties/launchSettings.json",
"Shared/LoginDisplay.razor",
"Shared/MainLayout.razor",
"Shared/MainLayout.razor.css",
"Shared/NavMenu.razor",
"Shared/NavMenu.razor.css",
"Shared/SurveyPrompt.razor",
"wwwroot/favicon.ico",
"wwwroot/css/site.css",
@ -1105,7 +1115,9 @@
"Properties/launchSettings.json",
"Shared/LoginDisplay.razor",
"Shared/MainLayout.razor",
"Shared/MainLayout.razor.css",
"Shared/NavMenu.razor",
"Shared/NavMenu.razor.css",
"Shared/SurveyPrompt.razor",
"wwwroot/favicon.ico",
"wwwroot/css/site.css",

View File

@ -58,6 +58,7 @@ Integration with static web assets:
<_ScopedCssExtension>.rz.scp.css</_ScopedCssExtension>
<ResolveStaticWebAssetsInputsDependsOn>$(ResolveStaticWebAssetsInputsDependsOn);_CollectAllScopedCssAssets;AddScopedCssBundle</ResolveStaticWebAssetsInputsDependsOn>
<ResolveCurrentProjectStaticWebAssetsInputsDependsOn>$(ResolveCurrentProjectStaticWebAssetsInputsDependsOn);_AddGeneratedScopedCssFiles</ResolveCurrentProjectStaticWebAssetsInputsDependsOn>
<GetCurrentProjectStaticWebAssetsDependsOn Condition="'$(UseBlazorWebAssembly)' == 'true'">$(GetCurrentProjectStaticWebAssetsDependsOn);IncludeScopedCssBundle;</GetCurrentProjectStaticWebAssetsDependsOn>
</PropertyGroup>
<Target Name="ResolveScopedCssInputs">
@ -68,7 +69,7 @@ Integration with static web assets:
NOTE: This target is called as part of an incremental build scenario in VS. Do not perform any work
outside of calculating RazorComponent items in this target.
-->
<DiscoverDefaultScopedCssItems Condition="'$(EnableDefaultScopedCssItems)'=='true'" Content="@(None);@(Content)">
<DiscoverDefaultScopedCssItems Condition="'$(EnableDefaultScopedCssItems)' == 'true'" Content="@(None);@(Content)">
<Output TaskParameter="DiscoveredScopedCssInputs" ItemName="_DiscoveredScopedCssInputs" />
</DiscoverDefaultScopedCssItems>
@ -207,6 +208,33 @@ Integration with static web assets:
</ItemGroup>
</Target>
<Target Name="IncludeScopedCssBundle" Condition="'$(ScopedCssDisableBundling)' != 'true'" DependsOnTargets="_CollectAllScopedCssAssets;_AddGeneratedScopedCssFiles">
<PropertyGroup>
<_ScopedCssOutputPath>$(_ScopedCssIntermediatePath)_framework\scoped.styles.css</_ScopedCssOutputPath>
<_ScopedCssOutputFullPath>$([System.IO.Path]::Combine('$(MSBuildProjectFileDirectory)', '$(_ScopedCssIntermediatePath)_framework\scoped.styles.css'))</_ScopedCssOutputFullPath>
</PropertyGroup>
<ItemGroup>
<!-- When bundling is enabled we want to remove all identified generated scoped css files from the list of static web assets so that
they are not copied to the output folder. -->
<StaticWebAsset Remove="@(_AllScopedCss)" Condition="'$(ScopedCssDisableBundling)' != 'true'" />
<!-- https://github.com/dotnet/aspnetcore/issues/24245 -->
<StaticWebAsset Include="$(_ScopedCssOutputPath)" Condition="@(_AllScopedCss) != ''">
<SourceType></SourceType>
<SourceId>$(PackageId)</SourceId>
<ContentRoot>$(_ScopedCssIntermediatePath)</ContentRoot>
<BasePath>$(StaticWebAssetBasePath)</BasePath>
<RelativePath>_framework/scoped.styles.css</RelativePath>
</StaticWebAsset>
<_ExternalStaticWebAsset Include="$(_ScopedCssOutputPath)" Condition="@(_AllScopedCss) != ''">
<SourceType>generated</SourceType>
<SourceId>$(PackageId)</SourceId>
<ContentRoot>$(_ScopedCssIntermediatePath)</ContentRoot>
<BasePath>$(StaticWebAssetBasePath)</BasePath>
<RelativePath>_framework/scoped.styles.css</RelativePath>
</_ExternalStaticWebAsset>
</ItemGroup>
</Target>
<Target Name="BundleScopedCssFiles" Condition="'$(ScopedCssDisableBundling)' != 'true' and '@(_AllScopedCss)' != ''" BeforeTargets="GetCopyToOutputDirectoryItems;_StaticWebAssetsComputeFilesToPublish" DependsOnTargets="_GenerateScopedCssFiles">
<!-- Incrementalism is built into the task itself. -->
<ConcatenateCssFiles FilesToProcess="@(_AllScopedCss)" OutputFile="$(_ScopedCssOutputPath)" />