This adds initial support for ASP.NET Core running on .NET 5. Thanks to
the previous refactoring work, there is very little that needed to be
done to support this.

With this change, the wwturl tests all pass against .NET 5. There may be
some issues not captured by this, but looks good for an initial push.
This commit is contained in:
Taylor Southwick 2020-11-19 08:48:04 -08:00 коммит произвёл GitHub
Родитель 88e32e806d
Коммит 8853a7f540
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
13 изменённых файлов: 500 добавлений и 4 удалений

Просмотреть файл

@ -2,6 +2,5 @@
<configuration>
<packageSources>
<add key="nuget" value="https://api.nuget.org/v3/index.json" />
<add key="azuresdk" value="https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-net/nuget/v3/index.json" />
</packageSources>
</configuration>

Просмотреть файл

@ -14,12 +14,22 @@ variables:
steps:
- task: UseDotNet@2
inputs:
packageType: 'sdk'
version: '3.x'
- task: UseDotNet@2
inputs:
packageType: 'sdk'
useGlobalJson: true
- task: DotNetCoreCLI@2
displayName: 'Install GitVersion'
inputs:
command: 'custom'
custom: 'tool'
arguments: 'install --global GitVersion.Tool --version 5.1.2'
arguments: 'install --global GitVersion.Tool --version 5.5.0'
- task: DotNetCoreCLI@2
displayName: 'Update Version'
@ -36,6 +46,13 @@ steps:
feedsToUse: config
nugetConfigPath: NuGet.config
- task: DotNetCoreCLI@2
inputs:
command: 'publish'
publishWebProjects: false
projects: 'src/WWT.Web/WWT.Web.csproj'
arguments: '--runtime win-x64 --self-contained --configuration $(buildConfiguration)'
- task: Npm@1
displayName: npm install
inputs:

Просмотреть файл

@ -1,6 +1,6 @@
{
"sdk": {
"version": "3.1.402",
"rollForward": "minor"
"version": "5.0.100",
"rollForward": "feature"
}
}

Просмотреть файл

@ -0,0 +1,111 @@
using Microsoft.AspNetCore.Http;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using WWT.Providers;
namespace WWT.Web
{
public class AspNetCoreWwtContext : IWwtContext, IRequest, IResponse, IHeaders, IParameters
{
private readonly HttpContext _ctx;
private readonly ICache _cache;
public AspNetCoreWwtContext(HttpContext ctx, ICache cache)
{
_ctx = ctx;
_cache = cache;
}
string IParameters.this[string p] => _ctx.Request.Query[p];
string IHeaders.this[string p] => _ctx.Request.Headers[p];
public ICache Cache => _cache;
public IRequest Request => this;
public IResponse Response => this;
public string MachineName => Environment.MachineName;
string IResponse.ContentType
{
get => _ctx.Response.ContentType;
set => _ctx.Response.ContentType = value;
}
string IResponse.Status
{
get => throw new NotImplementedException();
set => throw new NotImplementedException();
}
int IResponse.StatusCode
{
get => _ctx.Response.StatusCode;
set => _ctx.Response.StatusCode = value;
}
Stream IResponse.OutputStream => _ctx.Response.Body;
int IResponse.Expires
{
get => throw new NotImplementedException();
set => throw new NotImplementedException();
}
string IResponse.CacheControl
{
get => throw new NotImplementedException();
set => _ctx.Response.Headers.Add("Cache-Control", value);
}
IParameters IRequest.Params => this;
string IRequest.GetParams(string name) => _ctx.Request.Query[name];
IHeaders IRequest.Headers => this;
Uri IRequest.Url => new Uri($"{_ctx.Request.Scheme}://{_ctx.Request.Host}{_ctx.Request.Path}");
string IRequest.UserAgent => _ctx.Request.Headers["User-Agent"];
string IRequest.PhysicalPath => throw new NotImplementedException();
Stream IRequest.InputStream => _ctx.Request.Body;
public string MapPath(params string[] path) => throw new NotImplementedException();
void IResponse.AddHeader(string name, string value) => _ctx.Response.Headers.Add(name, value);
void IResponse.Clear()
{
}
void IResponse.ClearHeaders()
{
}
void IResponse.Close()
{
}
bool IRequest.ContainsCookie(string name) => _ctx.Request.Cookies.ContainsKey(name);
void IResponse.End()
{
}
void IResponse.Flush()
{
}
Task IResponse.WriteAsync(string message, CancellationToken token) => _ctx.Response.WriteAsync(message, token);
void IResponse.Redirect(string redirectUri) => _ctx.Response.Redirect(redirectUri);
void IResponse.WriteFile(string path) => throw new NotImplementedException();
}
}

Просмотреть файл

@ -0,0 +1,38 @@
using System;
using System.Collections.Concurrent;
using WWT.Providers;
namespace WWT.Web
{
public class ConcurrentDictionaryCache : ICache
{
private readonly ConcurrentDictionary<string, object> _cache;
public ConcurrentDictionaryCache()
{
_cache = new ConcurrentDictionary<string, object>();
}
public object this[string key]
{
get
{
if (_cache.TryGetValue(key, out var value))
{
return value;
}
return null;
}
set => _cache.AddOrUpdate(key, value, (s, o) => o);
}
public object Add(string key, object value, DateTime absoluteExpiration, TimeSpan slidingExpiration)
=> this[key] = value;
public object Get(string key) => this[key];
public void Remove(string key) => _cache.TryRemove(key, out _);
}
}

61
src/WWT.Web/Program.cs Normal file
Просмотреть файл

@ -0,0 +1,61 @@
using Azure.Core;
using Azure.Identity;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
namespace WWT.Web
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
private static TokenCredential _credential;
private static TokenCredential BuildTokenCredential(IConfiguration config)
{
if (_credential is null)
{
// This configuration is used to to identify the tenant of the KeyVault. If the user
// is not in the same tenant, then the token will be incorrect. The error message
// you'll see will be similar to: AKV10032: Invalid issuer
var tenant = config["DefaultCredentialTenantId"];
_credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions
{
VisualStudioCodeTenantId = tenant,
VisualStudioTenantId = tenant
});
}
return _credential;
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((context, services) =>
{
services.AddSingleton(BuildTokenCredential(context.Configuration));
})
.ConfigureAppConfiguration((context, config) =>
{
var builtConfig = config.Build();
var keyVaultName = builtConfig["KeyVaultName"];
if (!string.IsNullOrEmpty(keyVaultName))
{
var uri = new Uri($"https://{keyVaultName}.vault.azure.net/");
config.AddAzureKeyVault(uri, BuildTokenCredential(builtConfig));
}
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}

Просмотреть файл

@ -0,0 +1,37 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:40998",
"sslPort": 44319
}
},
"profiles": {
"WWT.Web (Dev)": {
"commandName": "Project",
"dotnetRunMessages": "true",
"launchBrowser": false,
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"WWT.Web (Prod)": {
"commandName": "Project",
"dotnetRunMessages": "true",
"launchBrowser": false,
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Production"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": false,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

146
src/WWT.Web/Startup.cs Normal file
Просмотреть файл

@ -0,0 +1,146 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
using System;
using WWT.Azure;
using WWT.Providers;
using WWT.Tours;
using WWTWebservices;
namespace WWT.Web
{
public partial class Startup
{
private readonly IConfiguration _configuration;
public Startup(IConfiguration configuration)
{
_configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddDefaultPolicy(builder =>
{
builder
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
});
services.AddApplicationInsightsTelemetry(options =>
{
options.InstrumentationKey = _configuration["APPINSIGHTS_INSTRUMENTATIONKEY"];
});
services.AddRequestProviders(options =>
{
options.WwtToursDBConnectionString = _configuration["WWTToursDBConnectionString"];
});
services
.AddAzureServices(options =>
{
options.StorageAccount = _configuration["AzurePlateFileStorageAccount"];
options.MarsStorageAccount = _configuration["MarsStorageAccount"];
})
.AddPlateFiles(options =>
{
_configuration.Bind(options);
options.Container = _configuration["PlateFileContainer"];
})
.AddThumbnails(options =>
{
options.ContainerName = _configuration["ThumbnailContainer"];
options.Default = _configuration["DefaultThumbnail"];
})
.AddCatalog(options =>
{
options.ContainerName = _configuration["CatalogContainer"];
})
.AddTours(options =>
{
options.ContainerName = _configuration["TourContainer"];
})
.AddTiles(options =>
{
options.ContainerName = _configuration["ImagesTilerContainer"];
});
services
.AddCaching(options =>
{
_configuration.Bind(options);
options.RedisCacheConnectionString = _configuration["RedisConnectionString"];
options.SlidingExpiration = TimeSpan.Parse(_configuration["SlidingExpiration"]);
})
.CacheType<IMandelbrot>(m => m.Add(nameof(IMandelbrot.CreateMandelbrot)))
.CacheType<IVirtualEarthDownloader>(plates => plates.Add(nameof(IVirtualEarthDownloader.DownloadVeTileAsync)))
.CacheType<IOctTileMapBuilder>(plates => plates.Add(nameof(IOctTileMapBuilder.GetOctTileAsync)))
.CacheType<IPlateTilePyramid>(plates => plates.Add(nameof(IPlateTilePyramid.GetStreamAsync)))
.CacheType<IThumbnailAccessor>(plates => plates
.Add(nameof(IThumbnailAccessor.GetThumbnailStreamAsync))
.Add(nameof(IThumbnailAccessor.GetDefaultThumbnailStreamAsync)))
.CacheType<ITourAccessor>(plates => plates
.Add(nameof(ITourAccessor.GetAuthorThumbnailAsync))
.Add(nameof(ITourAccessor.GetTourAsync))
.Add(nameof(ITourAccessor.GetTourThumbnailAsync)));
services.AddLogging(builder =>
{
builder.AddFilter("Swick.Cache", LogLevel.Trace);
builder.AddDebug();
});
}
// 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.UseCors();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
MapWwtEndpoints(endpoints);
});
}
private static void MapWwtEndpoints(IEndpointRouteBuilder endpoints)
{
var cache = new ConcurrentDictionaryCache();
var endpointManager = endpoints.ServiceProvider.GetRequiredService<EndpointManager>();
var @public = new CacheControlHeaderValue { Public = true };
var nocache = new CacheControlHeaderValue { NoCache = true };
foreach (var (endpoint, providerType) in endpointManager)
{
endpoints.MapGet(endpoint, ctx =>
{
var provider = (RequestProvider)ctx.RequestServices.GetRequiredService(providerType);
ctx.Response.ContentType = provider.ContentType;
ctx.Response.GetTypedHeaders().CacheControl = provider.IsCacheable ? @public : nocache;
return provider.RunAsync(new AspNetCoreWwtContext(ctx, cache), ctx.RequestAborted);
});
}
}
}
}

Просмотреть файл

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<UserSecretsId>a8e75023-0c2f-49d9-ad36-1d5f83df30d7</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.0.2" />
<PackageReference Include="Azure.Identity" Version="1.2.3" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.13.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WWT.Azure\WWT.Azure.csproj" />
<ProjectReference Include="..\WWT.Caching\WWT.Caching.csproj" />
<ProjectReference Include="..\WWT.Providers\WWT.Providers.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

Просмотреть файл

@ -0,0 +1,30 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"Swick.Cache": "Trace"
},
"ApplicationInsights": {
"LogLevel": {
"Default": "Trace"
}
}
},
"AllowedHosts": "*",
"AzurePlateFileStorageAccount": "https://127.0.0.1:10000/devstoreaccount1",
"MarsStorageAccount": "https://127.0.0.1:10000/devstoreaccount1",
"PlateFileContainer": "coredata",
"KnownPlateFile": "known_plate_files.txt",
"ThumbnailContainer": "thumbnails",
"DefaultThumbnail": "star",
"CatalogContainer": "catalog",
"TourContainer": "coretours",
"ImagesTilerContainer": "imagestiler",
"RedisConnectionString": "",
"UseCaching": "false",
"SlidingExpiration": "1.00:00:00",
"APPINSIGHTS_INSTRUMENTATIONKEY": ""
}

21
wwt-website-net5.slnf Normal file
Просмотреть файл

@ -0,0 +1,21 @@
{
"solution": {
"path": "wwt-website.sln",
"projects": [
"src\\WWT.Azure\\WWT.Azure.csproj",
"src\\WWT.Caching\\WWT.Caching.csproj",
"src\\WWT.Catalog\\WWT.Catalog.csproj",
"src\\WWT.Imaging\\WWT.Imaging.csproj",
"src\\WWT.Maps\\WWT.Maps.csproj",
"src\\WWT.PlateFiles\\WWT.PlateFiles.csproj",
"src\\WWT.Providers\\WWT.Providers.csproj",
"src\\WWT.Tours\\WWT.Tours.csproj",
"src\\WWT.Web\\WWT.Web.csproj",
"tests\\WWT.Azure.Tests\\WWT.Azure.Tests.csproj",
"tests\\WWT.Caching.Tests\\WWT.Caching.Tests.csproj",
"tests\\WWT.PlateFiles.Tests\\WWT.PlateFiles.Tests.csproj",
"tests\\WWT.Providers.Tests\\WWT.Providers.Tests.csproj",
"tools\\PlateManager\\PlateManager.csproj"
]
}
}

Просмотреть файл

@ -59,6 +59,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WWT.Tours", "src\WWT.Tours\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WWT.Catalog", "src\WWT.Catalog\WWT.Catalog.csproj", "{C5F256EE-8ECC-46F8-BD80-5D19CF55E7AA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WWT.Web", "src\WWT.Web\WWT.Web.csproj", "{1C293ECE-171A-4F1C-B9EE-79C3EAA6D518}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -125,6 +127,10 @@ Global
{C5F256EE-8ECC-46F8-BD80-5D19CF55E7AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C5F256EE-8ECC-46F8-BD80-5D19CF55E7AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C5F256EE-8ECC-46F8-BD80-5D19CF55E7AA}.Release|Any CPU.Build.0 = Release|Any CPU
{1C293ECE-171A-4F1C-B9EE-79C3EAA6D518}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1C293ECE-171A-4F1C-B9EE-79C3EAA6D518}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1C293ECE-171A-4F1C-B9EE-79C3EAA6D518}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1C293ECE-171A-4F1C-B9EE-79C3EAA6D518}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -145,6 +151,7 @@ Global
{3FBE3897-A98D-4066-8B6C-3F603A23DFB2} = {EFF21404-6CEA-49EB-9402-C6643AC2564F}
{0A5B7416-B9CB-438F-8DC9-C9D3B096896B} = {F12286DB-AF41-4661-A1C6-EB3259CB9B41}
{C5F256EE-8ECC-46F8-BD80-5D19CF55E7AA} = {F12286DB-AF41-4661-A1C6-EB3259CB9B41}
{1C293ECE-171A-4F1C-B9EE-79C3EAA6D518} = {F12286DB-AF41-4661-A1C6-EB3259CB9B41}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {80DABDD3-0D48-43AD-893B-B09A38FCFC72}