@@ -11,6 +11,7 @@ namespace JT808.DotNetty.Core.Codecs | |||
{ | |||
protected override void Decode(IChannelHandlerContext context, DatagramPacket message, List<object> output) | |||
{ | |||
if (!message.Content.IsReadable()) return; | |||
IByteBuffer byteBuffer = message.Content; | |||
byte[] buffer = new byte[byteBuffer.ReadableBytes]; | |||
byteBuffer.ReadBytes(buffer); | |||
@@ -18,9 +18,18 @@ namespace JT808.DotNetty.Core | |||
Task.Run(()=> { | |||
while (true) | |||
{ | |||
byte[] buffer = new byte[100]; | |||
tcpClient.GetStream().Read(buffer, 0, 100); | |||
Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " "+string.Join(" ", buffer)); | |||
try | |||
{ | |||
byte[] buffer = new byte[100]; | |||
tcpClient.GetStream().Read(buffer, 0, 100); | |||
Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " " + string.Join(" ", buffer)); | |||
} | |||
catch | |||
{ | |||
} | |||
Thread.Sleep(1000); | |||
} | |||
}); | |||
@@ -15,11 +15,21 @@ namespace JT808.DotNetty.Core | |||
{ | |||
udpClient = new UdpClient(); | |||
udpClient.Connect(remoteAddress); | |||
Task.Run(() => { | |||
Task.Run(() => | |||
{ | |||
while (true) | |||
{ | |||
string tmp = string.Join(" ", udpClient.Receive(ref remoteAddress)); | |||
Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " " + tmp); | |||
try | |||
{ | |||
string tmp = string.Join(" ", udpClient.Receive(ref remoteAddress)); | |||
Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " " + tmp); | |||
Thread.Sleep(1000); | |||
} | |||
catch | |||
{ | |||
} | |||
Thread.Sleep(1000); | |||
} | |||
}); | |||
@@ -22,19 +22,10 @@ namespace JT808.DotNetty.Internal | |||
{ | |||
var session = jT808SessionManager.GetSession(terminalPhoneNo); | |||
if (session != null) | |||
{ | |||
if (session.Channel.Open) | |||
{ | |||
session.Channel.WriteAndFlushAsync(Unpooled.WrappedBuffer(data)); | |||
resultDto.Code = JT808ResultCode.Ok; | |||
resultDto.Data = true; | |||
} | |||
else | |||
{ | |||
resultDto.Code = JT808ResultCode.Ok; | |||
resultDto.Data = false; | |||
resultDto.Message = "offline"; | |||
} | |||
{ | |||
session.Channel.WriteAndFlushAsync(Unpooled.WrappedBuffer(data)); | |||
resultDto.Code = JT808ResultCode.Ok; | |||
resultDto.Data = true; | |||
} | |||
else | |||
{ | |||
@@ -24,18 +24,9 @@ namespace JT808.DotNetty.Internal | |||
var session = jT808SessionManager.GetSession(terminalPhoneNo); | |||
if (session != null) | |||
{ | |||
if (session.Channel.Open) | |||
{ | |||
session.Channel.WriteAndFlushAsync(new DatagramPacket(Unpooled.WrappedBuffer(data), session.Sender)); | |||
resultDto.Code = JT808ResultCode.Ok; | |||
resultDto.Data = true; | |||
} | |||
else | |||
{ | |||
resultDto.Code = JT808ResultCode.Ok; | |||
resultDto.Data = false; | |||
resultDto.Message = "offline"; | |||
} | |||
session.Channel.WriteAndFlushAsync(new DatagramPacket(Unpooled.WrappedBuffer(data), session.Sender)); | |||
resultDto.Code = JT808ResultCode.Ok; | |||
resultDto.Data = true; | |||
} | |||
else | |||
{ | |||
@@ -123,13 +123,16 @@ namespace JT808.DotNetty.Core | |||
//todo: 设备离线可以进行通知 | |||
//todo: 使用Redis 发布订阅 | |||
var terminalPhoneNos = SessionIdDict.Where(w => w.Value.Channel.Id == channel.Id).Select(s => s.Key).ToList(); | |||
foreach (var key in terminalPhoneNos) | |||
if (terminalPhoneNos.Count > 0) | |||
{ | |||
SessionIdDict.TryRemove(key, out JT808TcpSession jT808SessionRemove); | |||
} | |||
string nos = string.Join(",", terminalPhoneNos); | |||
logger.LogInformation($">>>{nos} Channel Remove."); | |||
jT808SessionPublishing.PublishAsync(JT808Constants.SessionOffline,nos); | |||
foreach (var key in terminalPhoneNos) | |||
{ | |||
SessionIdDict.TryRemove(key, out JT808TcpSession jT808SessionRemove); | |||
} | |||
string nos = string.Join(",", terminalPhoneNos); | |||
logger.LogInformation($">>>{nos} Channel Remove."); | |||
jT808SessionPublishing.PublishAsync(JT808Constants.SessionOffline, nos); | |||
} | |||
} | |||
public IEnumerable<JT808TcpSession> GetAll() | |||
@@ -5,6 +5,7 @@ using System.Collections.Generic; | |||
using System.Linq; | |||
using JT808.DotNetty.Abstractions; | |||
using JT808.DotNetty.Core.Metadata; | |||
using DotNetty.Transport.Channels; | |||
namespace JT808.DotNetty.Core | |||
{ | |||
@@ -51,20 +52,38 @@ namespace JT808.DotNetty.Core | |||
} | |||
public void TryAdd(JT808UdpSession appSession) | |||
{ | |||
// 解决了设备号跟通道绑定到一起,不需要用到通道本身的SessionId | |||
// 不管设备下发更改了设备终端号,只要是没有在内存中就当是新的 | |||
// 存在的问题: | |||
// 1.原先老的如何销毁 | |||
// 2.这时候用的通道是相同的,设备终端是不同的 | |||
// 当设备主动或者服务器断开以后,可以释放,这点内存忽略不计,况且更改设备号不是很频繁。 | |||
if (SessionIdDict.TryAdd(appSession.TerminalPhoneNo, appSession)) | |||
{ | |||
//1.先判断是否在缓存里面 | |||
if(SessionIdDict.TryGetValue(appSession.TerminalPhoneNo,out JT808UdpSession jT808UdpSession)) | |||
{ | |||
//使用场景: | |||
//部标的超长待机设备,不会像正常的设备一样一直连着,可能10几分钟连上了,然后发完就关闭连接, | |||
//这时候想下发数据需要知道设备什么时候上线,在这边做通知最好不过了。 | |||
//todo: 有设备关联上来可以进行通知 例如:使用Redis发布订阅 | |||
jT808SessionPublishing.PublishAsync(JT808Constants.SessionOnline, appSession.TerminalPhoneNo); | |||
//处理缓存 | |||
//判断设备的终结点是否相同 | |||
if (jT808UdpSession.Sender.Equals(appSession.Sender)) | |||
{ | |||
//相同 更新最后上线时间 | |||
//每次使用最新的通道 | |||
//将设备第一次上线时间赋值给当前上线的时间 | |||
appSession.StartTime = jT808UdpSession.StartTime; | |||
SessionIdDict.TryUpdate(appSession.TerminalPhoneNo, appSession, appSession); | |||
} | |||
else | |||
{ | |||
//不同 算成新设备上来并且推送通知 | |||
SessionIdDict.TryUpdate(appSession.TerminalPhoneNo, appSession, appSession); | |||
jT808SessionPublishing.PublishAsync(JT808Constants.SessionOnline, appSession.TerminalPhoneNo); | |||
} | |||
} | |||
else | |||
{ | |||
//添加缓存 | |||
if (SessionIdDict.TryAdd(appSession.TerminalPhoneNo, appSession)) | |||
{ | |||
//使用场景: | |||
//部标的超长待机设备,不会像正常的设备一样一直连着,可能10几分钟连上了,然后发完就关闭连接, | |||
//这时候想下发数据需要知道设备什么时候上线,在这边做通知最好不过了。 | |||
//todo: 有设备关联上来可以进行通知 例如:使用Redis发布订阅 | |||
jT808SessionPublishing.PublishAsync(JT808Constants.SessionOnline, appSession.TerminalPhoneNo); | |||
} | |||
} | |||
} | |||
@@ -99,6 +118,23 @@ namespace JT808.DotNetty.Core | |||
} | |||
} | |||
public void RemoveSessionByChannel(IChannel channel) | |||
{ | |||
//todo: 设备离线可以进行通知 | |||
//todo: 使用Redis 发布订阅 | |||
var terminalPhoneNos = SessionIdDict.Where(w => w.Value.Channel.Id == channel.Id).Select(s => s.Key).ToList(); | |||
if (terminalPhoneNos.Count > 0) | |||
{ | |||
foreach (var key in terminalPhoneNos) | |||
{ | |||
SessionIdDict.TryRemove(key, out JT808UdpSession jT808SessionRemove); | |||
} | |||
string nos = string.Join(",", terminalPhoneNos); | |||
logger.LogInformation($">>>{nos} Channel Remove."); | |||
jT808SessionPublishing.PublishAsync(JT808Constants.SessionOffline, nos); | |||
} | |||
} | |||
public IEnumerable<JT808UdpSession> GetAll() | |||
{ | |||
return SessionIdDict.Select(s => s.Value).ToList(); | |||
@@ -15,7 +15,9 @@ | |||
</ItemGroup> | |||
<ItemGroup> | |||
<ProjectReference Include="..\JT808.DotNetty\JT808.DotNetty.csproj" /> | |||
<ProjectReference Include="..\JT808.DotNetty.Tcp\JT808.DotNetty.Tcp.csproj" /> | |||
<ProjectReference Include="..\JT808.DotNetty.Udp\JT808.DotNetty.Udp.csproj" /> | |||
<ProjectReference Include="..\JT808.DotNetty.WebApi\JT808.DotNetty.WebApi.csproj" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
@@ -1,22 +0,0 @@ | |||
using JT808.DotNetty.Metadata; | |||
using Microsoft.Extensions.Logging; | |||
namespace JT808.DotNetty.Hosting | |||
{ | |||
public class JT808MsgIdCustomHandler : JT808MsgIdHandlerBase | |||
{ | |||
private readonly ILogger<JT808MsgIdCustomHandler> logger; | |||
public JT808MsgIdCustomHandler( | |||
ILoggerFactory loggerFactory, | |||
JT808SessionManager sessionManager) : base(sessionManager) | |||
{ | |||
logger = loggerFactory.CreateLogger<JT808MsgIdCustomHandler>(); | |||
} | |||
public override JT808Response Msg0x0102(JT808Request request) | |||
{ | |||
logger.LogDebug("Msg0x0102"); | |||
return base.Msg0x0102(request); | |||
} | |||
} | |||
} |
@@ -1,4 +1,8 @@ | |||
using Microsoft.Extensions.Configuration; | |||
using JT808.DotNetty.Core; | |||
using JT808.DotNetty.Tcp; | |||
using JT808.DotNetty.Udp; | |||
using JT808.DotNetty.WebApi; | |||
using Microsoft.Extensions.Configuration; | |||
using Microsoft.Extensions.DependencyInjection; | |||
using Microsoft.Extensions.DependencyInjection.Extensions; | |||
using Microsoft.Extensions.Hosting; | |||
@@ -24,16 +28,17 @@ namespace JT808.DotNetty.Hosting | |||
.ConfigureLogging((context, logging) => | |||
{ | |||
logging.AddConsole(); | |||
logging.SetMinimumLevel(LogLevel.Debug); | |||
logging.SetMinimumLevel(LogLevel.Trace); | |||
}) | |||
.ConfigureServices((hostContext, services) => | |||
{ | |||
services.AddSingleton<ILoggerFactory, LoggerFactory>(); | |||
services.AddSingleton(typeof(ILogger<>), typeof(Logger<>)); | |||
// 自定义消息处理业务 | |||
services.Replace(new ServiceDescriptor(typeof(JT808MsgIdHandlerBase), typeof(JT808MsgIdCustomHandler), ServiceLifetime.Singleton)); | |||
}) | |||
.UseJT808Host(); | |||
services.AddJT808Core(hostContext.Configuration) | |||
.AddJT808TcpHost() | |||
.AddJT808UdpHost() | |||
.AddJT808WebApiHost(); | |||
}); | |||
await serverHostBuilder.RunConsoleAsync(); | |||
} | |||
@@ -13,6 +13,8 @@ | |||
} | |||
}, | |||
"JT808Configuration": { | |||
"Port": 6565 | |||
"TcpPort": 12808, | |||
"UdpPort": 12818, | |||
"WebApiPort": 12828 | |||
} | |||
} |
@@ -77,5 +77,18 @@ namespace JT808.DotNetty.Tcp.Test | |||
var result2 = jT808SessionServiceDefaultImpl.RemoveByTerminalPhoneNo("123456789001"); | |||
var result3 = jT808SessionServiceDefaultImpl.GetAll(); | |||
} | |||
[TestMethod] | |||
public void Test3() | |||
{ | |||
// 判断通道是否关闭 | |||
IJT808TcpSessionService jT808SessionServiceDefaultImpl = ServiceProvider.GetService<IJT808TcpSessionService>(); | |||
JT808TcpSessionManager jT808TcpSessionManager = ServiceProvider.GetService<JT808TcpSessionManager>(); | |||
var result1 = jT808SessionServiceDefaultImpl.GetAll(); | |||
SimpleTcpClient1.Down(); | |||
Thread.Sleep(5000); | |||
var session = jT808TcpSessionManager.GetSession("123456789001"); | |||
Thread.Sleep(100000); | |||
} | |||
} | |||
} |
@@ -33,6 +33,10 @@ namespace JT808.DotNetty.Udp.Test | |||
public JT808SessionServiceTest() | |||
{ | |||
JT808SimpleUdpClient SimpleUdpClient11 = new JT808SimpleUdpClient(new IPEndPoint(IPAddress.Parse("157.255.57.82"), 12818)); | |||
JT808Package jT808Package11 = JT808.Protocol.Enums.JT808MsgId.终端心跳.Create("123456789001"); | |||
SimpleUdpClient11.WriteAsync(JT808Serializer.Serialize(jT808Package11)); | |||
SimpleUdpClient1 = new JT808SimpleUdpClient(endPoint); | |||
SimpleUdpClient2 = new JT808SimpleUdpClient(endPoint); | |||
SimpleUdpClient3 = new JT808SimpleUdpClient(endPoint); | |||
@@ -75,5 +79,18 @@ namespace JT808.DotNetty.Udp.Test | |||
var result2 = jT808SessionServiceDefaultImpl.RemoveByTerminalPhoneNo("123456789001"); | |||
var result3 = jT808SessionServiceDefaultImpl.GetAll(); | |||
} | |||
[TestMethod] | |||
public void Test3() | |||
{ | |||
// 判断通道是否关闭 | |||
IJT808UdpSessionService jT808SessionServiceDefaultImpl = ServiceProvider.GetService<IJT808UdpSessionService>(); | |||
JT808UdpSessionManager jT808UdpSessionManager = ServiceProvider.GetService<JT808UdpSessionManager>(); | |||
var result1 = jT808SessionServiceDefaultImpl.GetAll(); | |||
SimpleUdpClient1.Down(); | |||
var session = jT808UdpSessionManager.GetSession("123456789001"); | |||
var result3 = jT808UdpSessionManager.GetAll(); | |||
Thread.Sleep(100000); | |||
} | |||
} | |||
} |
@@ -9,6 +9,7 @@ using JT808.DotNetty.Abstractions; | |||
using JT808.DotNetty.Core.Services; | |||
using JT808.DotNetty.Core; | |||
using JT808.DotNetty.Core.Handlers; | |||
using System.Threading.Tasks; | |||
namespace JT808.DotNetty.Udp.Handlers | |||
{ | |||
@@ -85,5 +86,8 @@ namespace JT808.DotNetty.Udp.Handlers | |||
} | |||
} | |||
} | |||
public override void ChannelReadComplete(IChannelHandlerContext context) => context.Flush(); | |||
} | |||
} |
@@ -15,7 +15,7 @@ namespace JT808.DotNetty.Udp | |||
{ | |||
public static IServiceCollection AddJT808UdpHost(this IServiceCollection serviceDescriptors) | |||
{ | |||
serviceDescriptors.TryAddSingleton<JT808UdpSessionManager>(); | |||
serviceDescriptors.TryAddSingleton<JT808UdpSessionManager>(); | |||
serviceDescriptors.TryAddSingleton<JT808UdpAtomicCounterService>(); | |||
serviceDescriptors.TryAddSingleton<JT808MsgIdUdpHandlerBase, JT808MsgIdDefaultUdpHandler>(); | |||
serviceDescriptors.TryAddScoped<JT808UdpDecoder>(); | |||
@@ -55,7 +55,7 @@ namespace JT808.DotNetty.Udp | |||
{ | |||
IChannelPipeline pipeline = channel.Pipeline; | |||
using (var scope = serviceProvider.CreateScope()) | |||
{ | |||
{ | |||
pipeline.AddLast("jt808UdpDecoder", scope.ServiceProvider.GetRequiredService<JT808UdpDecoder>()); | |||
pipeline.AddLast("jt808UdpService", scope.ServiceProvider.GetRequiredService<JT808UdpServerHandler>()); | |||
} | |||
@@ -21,15 +21,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT808.DotNetty.WebApi", "JT | |||
EndProject | |||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT808.DotNetty.Core.Test", "JT808.DotNetty.Tests\JT808.DotNetty.Core.Test\JT808.DotNetty.Core.Test.csproj", "{1C4CCE9B-761B-4581-B5DA-5B6D83572D56}" | |||
EndProject | |||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT808.DotNetty.Test", "JT808.DotNetty.Test\JT808.DotNetty.Test.csproj", "{7BF06DFC-5F7B-4DEB-8005-B460ADF65B95}" | |||
EndProject | |||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT808.DotNetty", "JT808.DotNetty\JT808.DotNetty.csproj", "{042C719D-9545-44C4-9EFE-500C1DA5DAC2}" | |||
EndProject | |||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT808.DotNetty.Tcp.Test", "JT808.DotNetty.Tests\JT808.DotNetty.Tcp.Test\JT808.DotNetty.Tcp.Test.csproj", "{AEF1E1E2-C861-4268-86F6-6F376FAF79A7}" | |||
EndProject | |||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT808.DotNetty.Udp.Test", "JT808.DotNetty.Tests\JT808.DotNetty.Udp.Test\JT808.DotNetty.Udp.Test.csproj", "{E503BFD8-D90A-4610-97C7-5B9A0497303B}" | |||
EndProject | |||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JT808.DotNetty.WebApi.Test", "JT808.DotNetty.Tests\JT808.DotNetty.WebApi.Test\JT808.DotNetty.WebApi.Test.csproj", "{EDE77A29-0840-450C-8B08-2D3388845AE5}" | |||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT808.DotNetty.WebApi.Test", "JT808.DotNetty.Tests\JT808.DotNetty.WebApi.Test\JT808.DotNetty.WebApi.Test.csproj", "{EDE77A29-0840-450C-8B08-2D3388845AE5}" | |||
EndProject | |||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT808.DotNetty.Hosting", "JT808.DotNetty.Hosting\JT808.DotNetty.Hosting.csproj", "{2E95C08A-B512-4252-A412-84E4EDCAE717}" | |||
EndProject | |||
Global | |||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | |||
@@ -65,14 +63,6 @@ Global | |||
{1C4CCE9B-761B-4581-B5DA-5B6D83572D56}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{1C4CCE9B-761B-4581-B5DA-5B6D83572D56}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{1C4CCE9B-761B-4581-B5DA-5B6D83572D56}.Release|Any CPU.Build.0 = Release|Any CPU | |||
{7BF06DFC-5F7B-4DEB-8005-B460ADF65B95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{7BF06DFC-5F7B-4DEB-8005-B460ADF65B95}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{7BF06DFC-5F7B-4DEB-8005-B460ADF65B95}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{7BF06DFC-5F7B-4DEB-8005-B460ADF65B95}.Release|Any CPU.Build.0 = Release|Any CPU | |||
{042C719D-9545-44C4-9EFE-500C1DA5DAC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{042C719D-9545-44C4-9EFE-500C1DA5DAC2}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{042C719D-9545-44C4-9EFE-500C1DA5DAC2}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{042C719D-9545-44C4-9EFE-500C1DA5DAC2}.Release|Any CPU.Build.0 = Release|Any CPU | |||
{AEF1E1E2-C861-4268-86F6-6F376FAF79A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{AEF1E1E2-C861-4268-86F6-6F376FAF79A7}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{AEF1E1E2-C861-4268-86F6-6F376FAF79A7}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
@@ -85,6 +75,10 @@ Global | |||
{EDE77A29-0840-450C-8B08-2D3388845AE5}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{EDE77A29-0840-450C-8B08-2D3388845AE5}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{EDE77A29-0840-450C-8B08-2D3388845AE5}.Release|Any CPU.Build.0 = Release|Any CPU | |||
{2E95C08A-B512-4252-A412-84E4EDCAE717}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{2E95C08A-B512-4252-A412-84E4EDCAE717}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{2E95C08A-B512-4252-A412-84E4EDCAE717}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{2E95C08A-B512-4252-A412-84E4EDCAE717}.Release|Any CPU.Build.0 = Release|Any CPU | |||
EndGlobalSection | |||
GlobalSection(SolutionProperties) = preSolution | |||
HideSolutionNode = FALSE | |||
@@ -92,11 +86,10 @@ Global | |||
GlobalSection(NestedProjects) = preSolution | |||
{9FCA2EE9-8253-41AA-A64C-9883413864F9} = {B5A80356-5AF6-449F-9D8B-3C1BBB9D2443} | |||
{1C4CCE9B-761B-4581-B5DA-5B6D83572D56} = {3BD7FF02-8516-4A77-A385-9FDCDD792E22} | |||
{7BF06DFC-5F7B-4DEB-8005-B460ADF65B95} = {B5A80356-5AF6-449F-9D8B-3C1BBB9D2443} | |||
{042C719D-9545-44C4-9EFE-500C1DA5DAC2} = {B5A80356-5AF6-449F-9D8B-3C1BBB9D2443} | |||
{AEF1E1E2-C861-4268-86F6-6F376FAF79A7} = {3BD7FF02-8516-4A77-A385-9FDCDD792E22} | |||
{E503BFD8-D90A-4610-97C7-5B9A0497303B} = {3BD7FF02-8516-4A77-A385-9FDCDD792E22} | |||
{EDE77A29-0840-450C-8B08-2D3388845AE5} = {3BD7FF02-8516-4A77-A385-9FDCDD792E22} | |||
{2E95C08A-B512-4252-A412-84E4EDCAE717} = {3BD7FF02-8516-4A77-A385-9FDCDD792E22} | |||
EndGlobalSection | |||
GlobalSection(ExtensibilityGlobals) = postSolution | |||
SolutionGuid = {FC0FFCEA-E1EF-4C97-A1C5-F89418B6834B} | |||