Add .NET 5 project head (#198)
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:
Родитель
88e32e806d
Коммит
8853a7f540
|
@ -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 _);
|
||||
}
|
||||
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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": ""
|
||||
}
|
|
@ -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}
|
||||
|
|
Загрузка…
Ссылка в новой задаче