Gateways Config

配置网关
This commit is contained in:
anjoy8 2021-01-25 19:58:34 +08:00
parent 591e9c36b9
commit 0952a2a4f1
19 changed files with 339 additions and 116 deletions

View File

@ -1,11 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Ocelot" Version="16.0.1" />
</ItemGroup>
</Project>

View File

@ -1,38 +0,0 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
namespace Blog.Core.AdminMvc
{
public class Startup
{
/**
*
*  MVC客户端
*  anson zhang
*
*/
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddOcelot();
}
// 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.UseOcelot().Wait();
}
}
}

View File

@ -1,17 +0,0 @@
{
"Logging": {
"IncludeScopes": false,
"Debug": {
"LogLevel": {
"Default": "Warning"
}
},
"Console": {
"LogLevel": {
"Default": "Warning",
"Microsoft.Hosting.Lifetime": "Debug"
}
}
},
"AllowedHosts": "*"
}

View File

@ -169,7 +169,7 @@
"Enabled": true
},
"Consul": {
"Enabled": false
"Enabled": true
},
"IpRateLimit": {
"Enabled": true

View File

@ -1,4 +1,4 @@
using Blog.Core.AuthHelper.Policys;
using Blog.Core.Model;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;

View File

@ -1,4 +1,4 @@
using Blog.Core.AuthHelper.Policys;
using Blog.Core.Model;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using System;

View File

@ -139,6 +139,16 @@ namespace Blog.Core.Extensions
ConsoleHelper.WriteSuccessLine($"RabbitMQ: True");
}
// Consul 注册服务
if (!Appsettings.app("Middleware", "Consul", "Enabled").ObjToBool())
{
Console.WriteLine($"Consul service: False");
}
else
{
ConsoleHelper.WriteSuccessLine($"Consul service: True");
}
// EventBus 事件总线
if (!Appsettings.app("EventBus", "Enabled").ObjToBool())
{

View File

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Extensions\ApiResponseHandler.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.0" NoWarn="NU1605" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="5.0.0" NoWarn="NU1605" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="5.0.1" />
<PackageReference Include="Ocelot" Version="17.0.0" />
<PackageReference Include="Ocelot.Provider.Consul" Version="17.0.0" />
<PackageReference Include="Ocelot.Provider.Polly" Version="17.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Blog.Core.Extensions\Blog.Core.Extensions.csproj" />
<ProjectReference Include="..\Blog.Core.Model\Blog.Core.Model.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,32 @@
using Blog.Core.Common.HttpContextUser;
using Blog.Core.Model;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
namespace Blog.Core.Gateway.Controllers
{
[Authorize]
[Route("/gateway/[controller]/[action]")]
public class UserController : ControllerBase
{
private readonly IUser _user;
public UserController(IUser user)
{
_user = user;
}
[HttpGet]
public MessageModel<List<Claim>> MyClaims()
{
return new MessageModel<List<Claim>>()
{
success = true,
response = _user.GetClaimsIdentity().ToList()
};
}
}
}

View File

@ -0,0 +1,77 @@
using Blog.Core.Model;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Blog.Core.Gateway.Extensions
{
/// <summary>
/// 这里不需要,目前集成的是 Blog.Core.Extensions 下的接口处理器
/// 但是你可以单独在网关中使用这个。
/// </summary>
public class ApiResponseHandler : DelegatingHandler
{
JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
var contentType = response.Content.Headers.ContentType?.MediaType ?? "";
if (!contentType.Equals("application/json")) return response;
dynamic result = null;
var resultStr = await response.Content.ReadAsStringAsync();
try
{
result = JsonConvert.DeserializeObject<dynamic>(resultStr);
}
catch (Exception)
{
return response;
}
if (result != null && result.code == 500) resultStr = result.msg.ToString();
var apiResponse = new ApiResponse(StatusCode.CODE200).MessageModel;
if (response.StatusCode != HttpStatusCode.OK || result.code == (int)HttpStatusCode.InternalServerError)
{
var exception = new Exception(resultStr);
apiResponse = new ApiResponse(StatusCode.CODE500).MessageModel;
}
else if (result.code == (int)HttpStatusCode.Unauthorized)
{
apiResponse = new ApiResponse(StatusCode.CODE401).MessageModel;
}
else if (result.code == (int)HttpStatusCode.Forbidden)
{
apiResponse = new ApiResponse(StatusCode.CODE403).MessageModel;
}
else
{
}
var statusCode = apiResponse.status == 500 ? HttpStatusCode.InternalServerError
: apiResponse.status == 401 ? HttpStatusCode.Unauthorized
: apiResponse.status == 403 ? HttpStatusCode.Forbidden
: HttpStatusCode.OK;
response.StatusCode = statusCode;
response.Content = new StringContent(JsonConvert.SerializeObject(apiResponse, jsonSerializerSettings), Encoding.UTF8, "application/json");
return response;
}
}
}

View File

@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Builder;
using Ocelot.Middleware;
using System.Threading.Tasks;
namespace Blog.Core.Gateway.Extensions
{
public static class OcelotMildd
{
public static async Task<IApplicationBuilder> UseOcelotMildd(this IApplicationBuilder app)
{
await app.UseOcelot();
return app;
}
}
}

View File

@ -1,14 +1,6 @@
{
"Routes": [
{
"DownstreamPathTemplate": "/api/{url}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 8081
}
],
"UpstreamPathTemplate": "/gateway/api/{url}",
"UpstreamHttpMethod": [
"Get",
@ -18,17 +10,17 @@
],
"LoadBalancerOptions": {
"Type": "RoundRobin"
}
},
{
"DownstreamPathTemplate": "/is4api/{url}",
},
"DownstreamPathTemplate": "/api/{url}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 5004
"Port": 8081
}
],
]
},
{
"UpstreamPathTemplate": "/gateway/is4api/{url}",
"UpstreamHttpMethod": [
"Get",
@ -38,10 +30,23 @@
],
"LoadBalancerOptions": {
"Type": "RoundRobin"
}
},
"DownstreamPathTemplate": "/is4api/{url}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 5004
}
]
}
],
"GlobalConfiguration": {
"BaseUrl": "http://localhost:9000"
"BaseUrl": "http://localhost:9000",
"ServiceDiscoveryProvider": {
"Host": "localhost",
"Port": 8500,
"Type": "Consul"
}
}
}

View File

@ -1,6 +1,6 @@
{
"profiles": {
"Blog.Core.AdminMvc": {
"Blog.Core.Gateway": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "http://localhost:9000",

View File

@ -0,0 +1,76 @@
using Blog.Core.Common;
using Blog.Core.Extensions;
using Blog.Core.Gateway.Extensions;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Ocelot.DependencyInjection;
using Ocelot.Provider.Consul;
namespace Blog.Core.AdminMvc
{
public class Startup
{
/**
*
* 
*  http://localhost:9000/gateway/user/MyClaims
*  http://localhost:9000/gateway/api/blog
*  http://localhost:9000/gateway/is4api/GetAchieveUsers
*  anson zhang
*
*/
public Startup(IConfiguration configuration, IWebHostEnvironment env)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(new Appsettings(Configuration));
services.AddAuthentication_JWTSetup();
services.AddAuthorization(options =>
{
options.AddPolicy("GW", policy => policy.RequireRole("GW").Build());
});
services.AddControllers();
services.AddHttpContextSetup();
services.AddCorsSetup();
services.AddOcelot().AddConsul();
}
// 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.UseRouting();
app.UseAuthorization();
app.UseCors(Appsettings.app(new string[] { "Startup", "Cors", "PolicyName" }));
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseOcelotMildd().Wait();
}
}
}

View File

@ -0,0 +1,31 @@
{
"Logging": {
"IncludeScopes": false,
"Debug": {
"LogLevel": {
"Default": "Warning"
}
},
"Console": {
"LogLevel": {
"Default": "Warning",
"Microsoft.Hosting.Lifetime": "Debug"
}
}
},
"AllowedHosts": "*",
"Startup": {
"Cors": {
"PolicyName": "CorsIpAccess",
"EnableAllIPs": false,
"IPs": "http://127.0.0.1:2364,http://localhost:2364"
}
},
"Audience": {
"Secret": "sdfsdfsrty45634kkhllghtdgdfss345t678fs",
"SecretFile": "C:\\my-file\\blog.core.audience.secret.txt",
"Issuer": "Blog.Core",
"Audience": "wr"
}
}

View File

@ -1,11 +1,10 @@
using Blog.Core.Model;
namespace Blog.Core.AuthHelper.Policys

namespace Blog.Core.Model
{
public class ApiResponse
{
public int Status { get; set; } = 404;
public string Value { get; set; } = "No Found";
public int Status { get; set; } = 200;
public string Value { get; set; } = "";
public MessageModel<string> MessageModel = new MessageModel<string>() { };
public ApiResponse(StatusCode apiCode, string msg = null)
@ -24,6 +23,12 @@ namespace Blog.Core.AuthHelper.Policys
Value = "很抱歉,您的访问权限等级不够,联系管理员!";
}
break;
case StatusCode.CODE404:
{
Status = 404;
Value = "资源不存在!";
}
break;
case StatusCode.CODE500:
{
Status = 500;
@ -36,13 +41,14 @@ namespace Blog.Core.AuthHelper.Policys
{
status = Status,
msg = Value,
success = false
success = apiCode != StatusCode.CODE200
};
}
}
public enum StatusCode
{
CODE200,
CODE401,
CODE403,
CODE404,

View File

@ -15,17 +15,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Core.Services", "Blog.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Core.Common", "Blog.Core.Common\Blog.Core.Common.csproj", "{97D32A49-994C-44C5-A167-51E71D173B6F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Core.FrameWork", "Blog.Core.FrameWork\Blog.Core.FrameWork.csproj", "{44A2006E-3EFC-4179-B400-866178C66556}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Core.Tasks", "Blog.Core.Tasks\Blog.Core.Tasks.csproj", "{F8E9FA1F-4079-4F62-B717-E389BC0014E8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Core.Extensions", "Blog.Core.Extensions\Blog.Core.Extensions.csproj", "{558F1B39-07E4-4FAB-BE7E-5B6104607064}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Core.ConsoleApp", "Blog.Core.ConsoleApp\Blog.Core.ConsoleApp.csproj", "{BB9FBBDF-B112-44F2-ABB7-9C31CFB619FE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Core.AdminMvc", "Blog.Core.AdminMvc\Blog.Core.AdminMvc.csproj", "{06D885F3-6352-4BF6-B826-DEA742DFFBD7}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D9833F24-7BD0-486F-B028-F1FD098AA1E1}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{D9833F24-7BD0-486F-B028-F1FD098AA1E1}"
ProjectSection(SolutionItems) = preProject
.dockerignore = .dockerignore
.editorconfig = .editorconfig
@ -44,7 +38,19 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{EDA8901E
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Core.Tests", "Blog.Core.Tests\Blog.Core.Tests.csproj", "{300A8113-8033-4184-BE28-FC48D8349CD0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Blog.Core.EventBus", "Blog.Core.EventBus\Blog.Core.EventBus.csproj", "{E4D54281-8812-4C4D-AAE6-1365F172020B}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Gateways", "Gateways", "{E2BD7D4D-9ED5-41CD-8401-C3FB26F203BB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Core.Gateway", "Blog.Core.Gateway\Blog.Core.Gateway.csproj", "{FC3D5C02-DED1-4A39-A8D9-A2EE1BE5A775}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Generators", "Generators", "{047A9723-9AAC-42E3-8C69-B3835F15FF96}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Core.FrameWork", "Blog.Core.FrameWork\Blog.Core.FrameWork.csproj", "{52D318A2-F44E-4CB7-8DD4-483357D4333F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "EventBus", "EventBus", "{A592C96A-4E44-4F2A-AC21-30683AF6C493}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Core.EventBus", "Blog.Core.EventBus\Blog.Core.EventBus.csproj", "{17C9E9DC-E926-4C90-9025-3DAC55D7EDA3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Core.ConsoleApp", "Blog.Core.ConsoleApp\Blog.Core.ConsoleApp.csproj", "{0B3265A9-6716-4D28-8648-C64D5E692ACA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -76,10 +82,6 @@ Global
{97D32A49-994C-44C5-A167-51E71D173B6F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{97D32A49-994C-44C5-A167-51E71D173B6F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{97D32A49-994C-44C5-A167-51E71D173B6F}.Release|Any CPU.Build.0 = Release|Any CPU
{44A2006E-3EFC-4179-B400-866178C66556}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{44A2006E-3EFC-4179-B400-866178C66556}.Debug|Any CPU.Build.0 = Debug|Any CPU
{44A2006E-3EFC-4179-B400-866178C66556}.Release|Any CPU.ActiveCfg = Release|Any CPU
{44A2006E-3EFC-4179-B400-866178C66556}.Release|Any CPU.Build.0 = Release|Any CPU
{F8E9FA1F-4079-4F62-B717-E389BC0014E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F8E9FA1F-4079-4F62-B717-E389BC0014E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F8E9FA1F-4079-4F62-B717-E389BC0014E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -88,28 +90,36 @@ Global
{558F1B39-07E4-4FAB-BE7E-5B6104607064}.Debug|Any CPU.Build.0 = Debug|Any CPU
{558F1B39-07E4-4FAB-BE7E-5B6104607064}.Release|Any CPU.ActiveCfg = Release|Any CPU
{558F1B39-07E4-4FAB-BE7E-5B6104607064}.Release|Any CPU.Build.0 = Release|Any CPU
{BB9FBBDF-B112-44F2-ABB7-9C31CFB619FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BB9FBBDF-B112-44F2-ABB7-9C31CFB619FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BB9FBBDF-B112-44F2-ABB7-9C31CFB619FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BB9FBBDF-B112-44F2-ABB7-9C31CFB619FE}.Release|Any CPU.Build.0 = Release|Any CPU
{06D885F3-6352-4BF6-B826-DEA742DFFBD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{06D885F3-6352-4BF6-B826-DEA742DFFBD7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{06D885F3-6352-4BF6-B826-DEA742DFFBD7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{06D885F3-6352-4BF6-B826-DEA742DFFBD7}.Release|Any CPU.Build.0 = Release|Any CPU
{300A8113-8033-4184-BE28-FC48D8349CD0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{300A8113-8033-4184-BE28-FC48D8349CD0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{300A8113-8033-4184-BE28-FC48D8349CD0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{300A8113-8033-4184-BE28-FC48D8349CD0}.Release|Any CPU.Build.0 = Release|Any CPU
{E4D54281-8812-4C4D-AAE6-1365F172020B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E4D54281-8812-4C4D-AAE6-1365F172020B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E4D54281-8812-4C4D-AAE6-1365F172020B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E4D54281-8812-4C4D-AAE6-1365F172020B}.Release|Any CPU.Build.0 = Release|Any CPU
{FC3D5C02-DED1-4A39-A8D9-A2EE1BE5A775}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FC3D5C02-DED1-4A39-A8D9-A2EE1BE5A775}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FC3D5C02-DED1-4A39-A8D9-A2EE1BE5A775}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC3D5C02-DED1-4A39-A8D9-A2EE1BE5A775}.Release|Any CPU.Build.0 = Release|Any CPU
{52D318A2-F44E-4CB7-8DD4-483357D4333F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{52D318A2-F44E-4CB7-8DD4-483357D4333F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{52D318A2-F44E-4CB7-8DD4-483357D4333F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{52D318A2-F44E-4CB7-8DD4-483357D4333F}.Release|Any CPU.Build.0 = Release|Any CPU
{17C9E9DC-E926-4C90-9025-3DAC55D7EDA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{17C9E9DC-E926-4C90-9025-3DAC55D7EDA3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{17C9E9DC-E926-4C90-9025-3DAC55D7EDA3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{17C9E9DC-E926-4C90-9025-3DAC55D7EDA3}.Release|Any CPU.Build.0 = Release|Any CPU
{0B3265A9-6716-4D28-8648-C64D5E692ACA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0B3265A9-6716-4D28-8648-C64D5E692ACA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0B3265A9-6716-4D28-8648-C64D5E692ACA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0B3265A9-6716-4D28-8648-C64D5E692ACA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{300A8113-8033-4184-BE28-FC48D8349CD0} = {EDA8901E-541E-4ADC-B71E-59697D5F9549}
{FC3D5C02-DED1-4A39-A8D9-A2EE1BE5A775} = {E2BD7D4D-9ED5-41CD-8401-C3FB26F203BB}
{52D318A2-F44E-4CB7-8DD4-483357D4333F} = {047A9723-9AAC-42E3-8C69-B3835F15FF96}
{17C9E9DC-E926-4C90-9025-3DAC55D7EDA3} = {A592C96A-4E44-4F2A-AC21-30683AF6C493}
{0B3265A9-6716-4D28-8648-C64D5E692ACA} = {047A9723-9AAC-42E3-8C69-B3835F15FF96}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AB40D0C5-E3EA-4A9B-86C2-38F0BB33FC04}