Add gRPC server example with file download
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
bin/
|
||||||
|
obj/
|
||||||
1
TestGrpc.Test/GlobalUsings.cs
Normal file
1
TestGrpc.Test/GlobalUsings.cs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
global using Xunit;
|
||||||
37
TestGrpc.Test/TestGrpc.Test.csproj
Normal file
37
TestGrpc.Test/TestGrpc.Test.csproj
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<IsTestProject>true</IsTestProject>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="../TestGrpc/TestGrpc.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Google.Protobuf" Version="3.28.2" />
|
||||||
|
<PackageReference Include="Grpc.Net.Client" Version="2.66.0" />
|
||||||
|
<PackageReference Include="Grpc.Tools" Version="2.66.0">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="8.0.8" />
|
||||||
|
<PackageReference Include="Moq" Version="4.20.72" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||||
|
<PackageReference Include="xunit" Version="2.9.1" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
233
TestGrpc.Test/UnitTest1.cs
Normal file
233
TestGrpc.Test/UnitTest1.cs
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
using Grpc.Core;
|
||||||
|
using Grpc.Net.Client;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.TestHost;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
namespace TestGrpc.Test;
|
||||||
|
|
||||||
|
public class UnitTest1 : IntegrationTestBase
|
||||||
|
{
|
||||||
|
public UnitTest1(GrpcTestFixture<Startup> fixture, ITestOutputHelper outputHelper)
|
||||||
|
: base(fixture, outputHelper)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Test1()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var client = new Greeter.GreeterClient(Channel);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
using var response = client.DownloadFile(new FileRequest { FilePath = "Files/test.txt" });
|
||||||
|
using var memoryStream = new MemoryStream();
|
||||||
|
|
||||||
|
var receivedFileName = string.Empty;
|
||||||
|
await foreach (var chunk in response.ResponseStream.ReadAllAsync())
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(chunk.FileName))
|
||||||
|
{
|
||||||
|
receivedFileName = chunk.FileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
memoryStream.Write(chunk.Chunk.Span);
|
||||||
|
}
|
||||||
|
|
||||||
|
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||||
|
using var reader = new StreamReader(memoryStream);
|
||||||
|
while (!reader.EndOfStream)
|
||||||
|
{
|
||||||
|
var line = await reader.ReadLineAsync();
|
||||||
|
Console.WriteLine(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("test.txt", receivedFileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ForwardingLoggerProvider : ILoggerProvider
|
||||||
|
{
|
||||||
|
private readonly LogMessage _logAction;
|
||||||
|
|
||||||
|
public ForwardingLoggerProvider(LogMessage logAction)
|
||||||
|
{
|
||||||
|
_logAction = logAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ILogger CreateLogger(string categoryName)
|
||||||
|
{
|
||||||
|
return new ForwardingLogger(categoryName, _logAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ForwardingLogger : ILogger
|
||||||
|
{
|
||||||
|
private readonly string _categoryName;
|
||||||
|
private readonly LogMessage _logAction;
|
||||||
|
|
||||||
|
public ForwardingLogger(string categoryName, LogMessage logAction)
|
||||||
|
{
|
||||||
|
_categoryName = categoryName;
|
||||||
|
_logAction = logAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IDisposable BeginScope<TState>(TState state)
|
||||||
|
{
|
||||||
|
return null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsEnabled(LogLevel logLevel)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
|
||||||
|
{
|
||||||
|
_logAction(logLevel, _categoryName, eventId, formatter(state, exception), exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class GrpcTestContext<TStartup> : IDisposable where TStartup : class
|
||||||
|
{
|
||||||
|
private readonly Stopwatch _stopwatch;
|
||||||
|
private readonly GrpcTestFixture<TStartup> _fixture;
|
||||||
|
private readonly ITestOutputHelper _outputHelper;
|
||||||
|
|
||||||
|
public GrpcTestContext(GrpcTestFixture<TStartup> fixture, ITestOutputHelper outputHelper)
|
||||||
|
{
|
||||||
|
_stopwatch = Stopwatch.StartNew();
|
||||||
|
_fixture = fixture;
|
||||||
|
_outputHelper = outputHelper;
|
||||||
|
_fixture.LoggedMessage += WriteMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteMessage(LogLevel logLevel, string category, EventId eventId, string message, Exception? exception)
|
||||||
|
{
|
||||||
|
var log = $"{_stopwatch.Elapsed.TotalSeconds:N3}s {category} - {logLevel}: {message}";
|
||||||
|
if (exception != null)
|
||||||
|
{
|
||||||
|
log += Environment.NewLine + exception.ToString();
|
||||||
|
}
|
||||||
|
_outputHelper.WriteLine(log);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_fixture.LoggedMessage -= WriteMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public delegate void LogMessage(LogLevel logLevel, string categoryName, EventId eventId, string message, Exception? exception);
|
||||||
|
|
||||||
|
public class GrpcTestFixture<TStartup> : IDisposable where TStartup : class
|
||||||
|
{
|
||||||
|
private TestServer? _server;
|
||||||
|
private IHost? _host;
|
||||||
|
private HttpMessageHandler? _handler;
|
||||||
|
private Action<IWebHostBuilder>? _configureWebHost;
|
||||||
|
|
||||||
|
public event LogMessage? LoggedMessage;
|
||||||
|
|
||||||
|
public GrpcTestFixture()
|
||||||
|
{
|
||||||
|
LoggerFactory = new LoggerFactory();
|
||||||
|
LoggerFactory.AddProvider(new ForwardingLoggerProvider((logLevel, category, eventId, message, exception) =>
|
||||||
|
{
|
||||||
|
LoggedMessage?.Invoke(logLevel, category, eventId, message, exception);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ConfigureWebHost(Action<IWebHostBuilder> configure)
|
||||||
|
{
|
||||||
|
_configureWebHost = configure;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureServer()
|
||||||
|
{
|
||||||
|
if (_host == null)
|
||||||
|
{
|
||||||
|
var builder = new HostBuilder()
|
||||||
|
.ConfigureServices(services =>
|
||||||
|
{
|
||||||
|
services.AddSingleton<ILoggerFactory>(LoggerFactory);
|
||||||
|
})
|
||||||
|
.ConfigureWebHostDefaults(webHost =>
|
||||||
|
{
|
||||||
|
webHost
|
||||||
|
.UseTestServer()
|
||||||
|
.UseStartup<TStartup>();
|
||||||
|
|
||||||
|
_configureWebHost?.Invoke(webHost);
|
||||||
|
});
|
||||||
|
_host = builder.Start();
|
||||||
|
_server = _host.GetTestServer();
|
||||||
|
_handler = _server.CreateHandler();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LoggerFactory LoggerFactory { get; }
|
||||||
|
|
||||||
|
public HttpMessageHandler Handler
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
EnsureServer();
|
||||||
|
return _handler!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_handler?.Dispose();
|
||||||
|
_host?.Dispose();
|
||||||
|
_server?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IDisposable GetTestContext(ITestOutputHelper outputHelper)
|
||||||
|
{
|
||||||
|
return new GrpcTestContext<TStartup>(this, outputHelper);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IntegrationTestBase : IClassFixture<GrpcTestFixture<Startup>>, IDisposable
|
||||||
|
{
|
||||||
|
private GrpcChannel? _channel;
|
||||||
|
private IDisposable? _testContext;
|
||||||
|
|
||||||
|
protected GrpcTestFixture<Startup> Fixture { get; set; }
|
||||||
|
|
||||||
|
protected ILoggerFactory LoggerFactory => Fixture.LoggerFactory;
|
||||||
|
|
||||||
|
protected GrpcChannel Channel => _channel ??= CreateChannel();
|
||||||
|
|
||||||
|
protected GrpcChannel CreateChannel()
|
||||||
|
{
|
||||||
|
return GrpcChannel.ForAddress("http://localhost", new GrpcChannelOptions
|
||||||
|
{
|
||||||
|
LoggerFactory = LoggerFactory,
|
||||||
|
HttpHandler = Fixture.Handler
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public IntegrationTestBase(GrpcTestFixture<Startup> fixture, ITestOutputHelper outputHelper)
|
||||||
|
{
|
||||||
|
Fixture = fixture;
|
||||||
|
_testContext = Fixture.GetTestContext(outputHelper);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_testContext?.Dispose();
|
||||||
|
_channel = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
2
TestGrpc/Files/test.txt
Normal file
2
TestGrpc/Files/test.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Hello world
|
||||||
|
This is a test
|
||||||
16
TestGrpc/Program.cs
Normal file
16
TestGrpc/Program.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
namespace TestGrpc;
|
||||||
|
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
CreateHostBuilder(args).Build().Run();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||||
|
Host.CreateDefaultBuilder(args)
|
||||||
|
.ConfigureWebHostDefaults(webBuilder =>
|
||||||
|
{
|
||||||
|
webBuilder.UseStartup<Startup>();
|
||||||
|
});
|
||||||
|
}
|
||||||
23
TestGrpc/Properties/launchSettings.json
Normal file
23
TestGrpc/Properties/launchSettings.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||||
|
"profiles": {
|
||||||
|
"http": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": false,
|
||||||
|
"applicationUrl": "http://localhost:5210",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"https": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": false,
|
||||||
|
"applicationUrl": "https://localhost:7004;http://localhost:5210",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
TestGrpc/Protos/greet.proto
Normal file
19
TestGrpc/Protos/greet.proto
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
option csharp_namespace = "TestGrpc";
|
||||||
|
|
||||||
|
package greet;
|
||||||
|
|
||||||
|
service Greeter {
|
||||||
|
rpc DownloadFile (FileRequest) returns (stream ChunkMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
message FileRequest {
|
||||||
|
string FilePath = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ChunkMsg {
|
||||||
|
string FileName = 1;
|
||||||
|
int64 FileSize = 2;
|
||||||
|
bytes Chunk = 3;
|
||||||
|
}
|
||||||
49
TestGrpc/Services/GreeterService.cs
Normal file
49
TestGrpc/Services/GreeterService.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
using Google.Protobuf;
|
||||||
|
using Grpc.Core;
|
||||||
|
using TestGrpc;
|
||||||
|
|
||||||
|
namespace TestGrpc.Services;
|
||||||
|
|
||||||
|
public class GreeterService : Greeter.GreeterBase
|
||||||
|
{
|
||||||
|
private readonly ILogger<GreeterService> _logger;
|
||||||
|
public GreeterService(ILogger<GreeterService> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task DownloadFile(FileRequest request, IServerStreamWriter<ChunkMsg> responseStream, ServerCallContext context)
|
||||||
|
{
|
||||||
|
string filePath = request.FilePath;
|
||||||
|
|
||||||
|
if (!File.Exists(filePath))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileInfo = new FileInfo(filePath);
|
||||||
|
|
||||||
|
var chunk = new ChunkMsg
|
||||||
|
{
|
||||||
|
FileName = Path.GetFileName(filePath),
|
||||||
|
FileSize = fileInfo.Length
|
||||||
|
};
|
||||||
|
|
||||||
|
int fileChunkSize = 64 * 1024;
|
||||||
|
|
||||||
|
byte[] fileByteArray = File.ReadAllBytes(filePath);
|
||||||
|
byte[] fileChunk = new byte[fileChunkSize];
|
||||||
|
int fileOffset = 0;
|
||||||
|
|
||||||
|
while (fileOffset < fileByteArray.Length && !context.CancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
int length = Math.Min(fileChunkSize, fileByteArray.Length - fileOffset);
|
||||||
|
Buffer.BlockCopy(fileByteArray, fileOffset, fileChunk, 0, length);
|
||||||
|
fileOffset += length;
|
||||||
|
ByteString byteString = ByteString.CopyFrom(fileChunk);
|
||||||
|
|
||||||
|
chunk.Chunk = byteString;
|
||||||
|
await responseStream.WriteAsync(chunk).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
TestGrpc/Startup.cs
Normal file
26
TestGrpc/Startup.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using TestGrpc.Services;
|
||||||
|
|
||||||
|
namespace TestGrpc;
|
||||||
|
|
||||||
|
public class Startup
|
||||||
|
{
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddGrpc(o => o.EnableDetailedErrors = true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||||
|
{
|
||||||
|
if (env.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseDeveloperExceptionPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseRouting();
|
||||||
|
|
||||||
|
app.UseEndpoints(endpoints =>
|
||||||
|
{
|
||||||
|
endpoints.MapGrpcService<GreeterService>();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
22
TestGrpc/TestGrpc.csproj
Normal file
22
TestGrpc/TestGrpc.csproj
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Protobuf Include="Protos\greet.proto" GrpcServices="Both" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="Files/test.txt" CopyToOutputDirectory="PreserveNewest" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="CsvHelper" Version="33.0.1" />
|
||||||
|
<PackageReference Include="Grpc.AspNetCore" Version="2.66.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
8
TestGrpc/appsettings.Development.json
Normal file
8
TestGrpc/appsettings.Development.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
TestGrpc/appsettings.json
Normal file
14
TestGrpc/appsettings.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*",
|
||||||
|
"Kestrel": {
|
||||||
|
"EndpointDefaults": {
|
||||||
|
"Protocols": "Http2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
temp.sln
Normal file
28
temp.sln
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.0.31903.59
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestGrpc", "TestGrpc\TestGrpc.csproj", "{D742F0F4-75F3-4FD8-8F61-3ED0396ACA1E}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestGrpc.Test", "TestGrpc.Test\TestGrpc.Test.csproj", "{222D86CD-BBE1-4297-8FD8-0805EEC5B676}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{D742F0F4-75F3-4FD8-8F61-3ED0396ACA1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{D742F0F4-75F3-4FD8-8F61-3ED0396ACA1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{D742F0F4-75F3-4FD8-8F61-3ED0396ACA1E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{D742F0F4-75F3-4FD8-8F61-3ED0396ACA1E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{222D86CD-BBE1-4297-8FD8-0805EEC5B676}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{222D86CD-BBE1-4297-8FD8-0805EEC5B676}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{222D86CD-BBE1-4297-8FD8-0805EEC5B676}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{222D86CD-BBE1-4297-8FD8-0805EEC5B676}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
Reference in New Issue
Block a user