[skip appveyor] #9 Documentation and examples
Added DataAnnotations to all examples. Implemented third example for Flow. Fixed a bug where Start would not give up it's flow lock.
This commit is contained in:
parent
8e0edabeed
commit
7389b5bf06
@ -13,6 +13,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\ExampleHelper.cs\ExampleLib.csproj" />
|
<ProjectReference Include="..\ExampleHelper.cs\ExampleLib.csproj" />
|
||||||
<ProjectReference Include="..\Messaging.TapetiExample\Messaging.TapetiExample.csproj" />
|
<ProjectReference Include="..\Messaging.TapetiExample\Messaging.TapetiExample.csproj" />
|
||||||
|
<ProjectReference Include="..\Tapeti.DataAnnotations\Tapeti.DataAnnotations.csproj" />
|
||||||
<ProjectReference Include="..\Tapeti.SimpleInjector\Tapeti.SimpleInjector.csproj" />
|
<ProjectReference Include="..\Tapeti.SimpleInjector\Tapeti.SimpleInjector.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
21
03-FlowRequestResponse/03-FlowRequestResponse.csproj
Normal file
21
03-FlowRequestResponse/03-FlowRequestResponse.csproj
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||||
|
<RootNamespace>_03_FlowRequestResponse</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="SimpleInjector" Version="4.6.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\ExampleHelper.cs\ExampleLib.csproj" />
|
||||||
|
<ProjectReference Include="..\Messaging.TapetiExample\Messaging.TapetiExample.csproj" />
|
||||||
|
<ProjectReference Include="..\Tapeti.DataAnnotations\Tapeti.DataAnnotations.csproj" />
|
||||||
|
<ProjectReference Include="..\Tapeti.Flow\Tapeti.Flow.csproj" />
|
||||||
|
<ProjectReference Include="..\Tapeti.SimpleInjector\Tapeti.SimpleInjector.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
66
03-FlowRequestResponse/Program.cs
Normal file
66
03-FlowRequestResponse/Program.cs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using ExampleLib;
|
||||||
|
using SimpleInjector;
|
||||||
|
using Tapeti;
|
||||||
|
using Tapeti.DataAnnotations;
|
||||||
|
using Tapeti.Default;
|
||||||
|
using Tapeti.Flow;
|
||||||
|
using Tapeti.SimpleInjector;
|
||||||
|
|
||||||
|
namespace _03_FlowRequestResponse
|
||||||
|
{
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
var container = new Container();
|
||||||
|
var dependencyResolver = new SimpleInjectorDependencyResolver(container);
|
||||||
|
|
||||||
|
container.Register<ILogger, ConsoleLogger>();
|
||||||
|
|
||||||
|
var helper = new ExampleConsoleApp(dependencyResolver);
|
||||||
|
helper.Run(MainAsync);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal static async Task MainAsync(IDependencyResolver dependencyResolver, Func<Task> waitForDone)
|
||||||
|
{
|
||||||
|
var config = new TapetiConfig(dependencyResolver)
|
||||||
|
.WithDataAnnotations()
|
||||||
|
.WithFlow()
|
||||||
|
.RegisterAllControllers()
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
|
||||||
|
using (var connection = new TapetiConnection(config))
|
||||||
|
{
|
||||||
|
// Must be called before using any flow. When using a persistent repository like the
|
||||||
|
// SQL server implementation, you can run any required update scripts (for example, using DbUp)
|
||||||
|
// before calling this Load method.
|
||||||
|
// Call after creating the TapetiConnection, as it modifies the container to inject IPublisher.
|
||||||
|
await dependencyResolver.Resolve<IFlowStore>().Load();
|
||||||
|
|
||||||
|
|
||||||
|
// This creates or updates the durable queue
|
||||||
|
await connection.Subscribe();
|
||||||
|
|
||||||
|
|
||||||
|
var flowStarter = dependencyResolver.Resolve<IFlowStarter>();
|
||||||
|
|
||||||
|
var startData = new SendingFlowController.StartData
|
||||||
|
{
|
||||||
|
RequestStartTime = DateTime.Now,
|
||||||
|
Amount = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
await flowStarter.Start<SendingFlowController, SendingFlowController.StartData>(c => c.StartFlow, startData);
|
||||||
|
|
||||||
|
|
||||||
|
// Wait for the controller to signal that the message has been received
|
||||||
|
await waitForDone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
03-FlowRequestResponse/ReceivingMessageController.cs
Normal file
42
03-FlowRequestResponse/ReceivingMessageController.cs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Messaging.TapetiExample;
|
||||||
|
using Tapeti.Annotations;
|
||||||
|
|
||||||
|
namespace _03_FlowRequestResponse
|
||||||
|
{
|
||||||
|
[MessageController]
|
||||||
|
[DynamicQueue("tapeti.example.03")]
|
||||||
|
public class ReceivingMessageController
|
||||||
|
{
|
||||||
|
// No publisher required, responses can simply be returned
|
||||||
|
public async Task<QuoteResponseMessage> HandleQuoteRequest(QuoteRequestMessage message)
|
||||||
|
{
|
||||||
|
string quote;
|
||||||
|
|
||||||
|
switch (message.Amount)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
// Well, they asked for it... :-)
|
||||||
|
quote = "'";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
quote = "\"";
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// We have to return a response.
|
||||||
|
quote = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just gonna let them wait for a bit, to demonstrate async message handlers
|
||||||
|
await Task.Delay(1000);
|
||||||
|
|
||||||
|
return new QuoteResponseMessage
|
||||||
|
{
|
||||||
|
Quote = quote
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
73
03-FlowRequestResponse/SendingFlowController.cs
Normal file
73
03-FlowRequestResponse/SendingFlowController.cs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
using System;
|
||||||
|
using ExampleLib;
|
||||||
|
using Messaging.TapetiExample;
|
||||||
|
using Tapeti.Annotations;
|
||||||
|
using Tapeti.Flow;
|
||||||
|
using Tapeti.Flow.Annotations;
|
||||||
|
|
||||||
|
namespace _03_FlowRequestResponse
|
||||||
|
{
|
||||||
|
[MessageController]
|
||||||
|
[DynamicQueue("tapeti.example.03")]
|
||||||
|
public class SendingFlowController
|
||||||
|
{
|
||||||
|
private readonly IFlowProvider flowProvider;
|
||||||
|
private readonly IExampleState exampleState;
|
||||||
|
|
||||||
|
|
||||||
|
// Shows how multiple values can be passed to a start method
|
||||||
|
public struct StartData
|
||||||
|
{
|
||||||
|
public DateTime RequestStartTime;
|
||||||
|
public int Amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private and protected fields are lost between method calls because the controller is
|
||||||
|
// recreated when a response arrives. When using a persistent flow repository this may
|
||||||
|
// even be after a restart of the application.
|
||||||
|
private bool nonPersistentState;
|
||||||
|
|
||||||
|
|
||||||
|
// Public fields will be stored.
|
||||||
|
public DateTime RequestStartTime;
|
||||||
|
|
||||||
|
|
||||||
|
public SendingFlowController(IFlowProvider flowProvider, IExampleState exampleState)
|
||||||
|
{
|
||||||
|
this.flowProvider = flowProvider;
|
||||||
|
this.exampleState = exampleState;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Start]
|
||||||
|
public IYieldPoint StartFlow(StartData startData)
|
||||||
|
{
|
||||||
|
nonPersistentState = true;
|
||||||
|
RequestStartTime = startData.RequestStartTime;
|
||||||
|
|
||||||
|
return flowProvider.YieldWithRequestSync<QuoteRequestMessage, QuoteResponseMessage>(
|
||||||
|
new QuoteRequestMessage
|
||||||
|
{
|
||||||
|
Amount = startData.Amount
|
||||||
|
},
|
||||||
|
HandleQuoteResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Continuation]
|
||||||
|
public IYieldPoint HandleQuoteResponse(QuoteResponseMessage message)
|
||||||
|
{
|
||||||
|
if (nonPersistentState)
|
||||||
|
Console.WriteLine("This is not supposed to show. NonPersistentState should not be retained. Someone please check http://www.hasthelargehadroncolliderdestroyedtheworldyet.com.");
|
||||||
|
|
||||||
|
Console.WriteLine("Request start: " + RequestStartTime.ToLongTimeString());
|
||||||
|
Console.WriteLine("Response time: " + DateTime.Now.ToLongTimeString());
|
||||||
|
Console.WriteLine("Quote: " + message.Quote);
|
||||||
|
|
||||||
|
|
||||||
|
exampleState.Done();
|
||||||
|
|
||||||
|
return flowProvider.End();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -24,7 +24,7 @@ namespace ExampleLib
|
|||||||
private readonly TaskCompletionSource<bool> doneSignal = new TaskCompletionSource<bool>();
|
private readonly TaskCompletionSource<bool> doneSignal = new TaskCompletionSource<bool>();
|
||||||
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <param name="dependencyResolver">Uses Tapeti's IDependencyContainer interface so you can easily switch an example to your favourite IoC container</param>
|
||||||
public ExampleConsoleApp(IDependencyContainer dependencyResolver)
|
public ExampleConsoleApp(IDependencyContainer dependencyResolver)
|
||||||
{
|
{
|
||||||
this.dependencyResolver = dependencyResolver;
|
this.dependencyResolver = dependencyResolver;
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\ExampleHelper.cs\ExampleLib.csproj" />
|
<ProjectReference Include="..\..\ExampleHelper.cs\ExampleLib.csproj" />
|
||||||
<ProjectReference Include="..\..\Messaging.TapetiExample\Messaging.TapetiExample.csproj" />
|
<ProjectReference Include="..\..\Messaging.TapetiExample\Messaging.TapetiExample.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Tapeti.DataAnnotations\Tapeti.DataAnnotations.csproj" />
|
||||||
<ProjectReference Include="..\..\Tapeti.SimpleInjector\Tapeti.SimpleInjector.csproj" />
|
<ProjectReference Include="..\..\Tapeti.SimpleInjector\Tapeti.SimpleInjector.csproj" />
|
||||||
<ProjectReference Include="..\..\Tapeti\Tapeti.csproj" />
|
<ProjectReference Include="..\..\Tapeti\Tapeti.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
using System.Threading.Tasks;
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Messaging.TapetiExample;
|
using Messaging.TapetiExample;
|
||||||
using Tapeti;
|
using Tapeti;
|
||||||
|
|
||||||
@ -24,6 +26,20 @@ namespace _01_PublishSubscribe
|
|||||||
{
|
{
|
||||||
Greeting = "Hello world of messaging!"
|
Greeting = "Hello world of messaging!"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Demonstrates what happens when DataAnnotations is enabled
|
||||||
|
// and the message is invalid
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await publisher.Publish(new PublishSubscribeMessage());
|
||||||
|
|
||||||
|
Console.WriteLine("This is not supposed to show. Did you disable the DataAnnotations extension?");
|
||||||
|
}
|
||||||
|
catch (ValidationException e)
|
||||||
|
{
|
||||||
|
Console.WriteLine("As expected, the DataAnnotations check failed: " + e.Message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
|||||||
using ExampleLib;
|
using ExampleLib;
|
||||||
using SimpleInjector;
|
using SimpleInjector;
|
||||||
using Tapeti;
|
using Tapeti;
|
||||||
|
using Tapeti.DataAnnotations;
|
||||||
using Tapeti.Default;
|
using Tapeti.Default;
|
||||||
using Tapeti.SimpleInjector;
|
using Tapeti.SimpleInjector;
|
||||||
|
|
||||||
@ -29,6 +30,7 @@ namespace _01_PublishSubscribe
|
|||||||
internal static async Task MainAsync(IDependencyResolver dependencyResolver, Func<Task> waitForDone)
|
internal static async Task MainAsync(IDependencyResolver dependencyResolver, Func<Task> waitForDone)
|
||||||
{
|
{
|
||||||
var config = new TapetiConfig(dependencyResolver)
|
var config = new TapetiConfig(dependencyResolver)
|
||||||
|
.WithDataAnnotations()
|
||||||
.RegisterAllControllers()
|
.RegisterAllControllers()
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
|
@ -8,4 +8,8 @@
|
|||||||
<PackageReference Include="System.ComponentModel.Annotations" Version="4.5.0" />
|
<PackageReference Include="System.ComponentModel.Annotations" Version="4.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Tapeti.Annotations\Tapeti.Annotations.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -7,7 +7,7 @@ namespace Messaging.TapetiExample
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class PublishSubscribeMessage
|
public class PublishSubscribeMessage
|
||||||
{
|
{
|
||||||
[Required]
|
[Required(ErrorMessage = "Don't be impolite, supply a {0}")]
|
||||||
public string Greeting { get; set; }
|
public string Greeting { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
16
Messaging.TapetiExample/QuoteRequestMessage.cs
Normal file
16
Messaging.TapetiExample/QuoteRequestMessage.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
using Tapeti.Annotations;
|
||||||
|
|
||||||
|
namespace Messaging.TapetiExample
|
||||||
|
{
|
||||||
|
[Request(Response = typeof(QuoteResponseMessage))]
|
||||||
|
public class QuoteRequestMessage
|
||||||
|
{
|
||||||
|
public int Amount { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class QuoteResponseMessage
|
||||||
|
{
|
||||||
|
public string Quote { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -199,30 +199,43 @@ namespace Tapeti.Flow.Default
|
|||||||
if (!(yieldPoint is DelegateYieldPoint executableYieldPoint))
|
if (!(yieldPoint is DelegateYieldPoint executableYieldPoint))
|
||||||
throw new YieldPointException($"Yield point is required in controller {context.Controller.GetType().Name} for method {context.Method.Name}");
|
throw new YieldPointException($"Yield point is required in controller {context.Controller.GetType().Name} for method {context.Method.Name}");
|
||||||
|
|
||||||
var messageContext = context.ControllerMessageContext;
|
FlowContext flowContext = null;
|
||||||
if (messageContext == null || !messageContext.Get(ContextItems.FlowContext, out FlowContext flowContext))
|
var disposeFlowContext = false;
|
||||||
{
|
|
||||||
flowContext = new FlowContext
|
|
||||||
{
|
|
||||||
HandlerContext = context
|
|
||||||
};
|
|
||||||
|
|
||||||
messageContext?.Store(ContextItems.FlowContext, flowContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await executableYieldPoint.Execute(flowContext);
|
var messageContext = context.ControllerMessageContext;
|
||||||
}
|
if (messageContext == null || !messageContext.Get(ContextItems.FlowContext, out flowContext))
|
||||||
catch (YieldPointException e)
|
{
|
||||||
{
|
flowContext = new FlowContext
|
||||||
// Useful for debugging
|
{
|
||||||
e.Data["Tapeti.Controller.Name"] = context.Controller.GetType().FullName;
|
HandlerContext = context
|
||||||
e.Data["Tapeti.Controller.Method"] = context.Method.Name;
|
};
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
flowContext.EnsureStoreOrDeleteIsCalled();
|
// If we ended up here it is because of a Start. No point in storing the new FlowContext
|
||||||
|
// in the messageContext as the yield point is the last to execute.
|
||||||
|
disposeFlowContext = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await executableYieldPoint.Execute(flowContext);
|
||||||
|
}
|
||||||
|
catch (YieldPointException e)
|
||||||
|
{
|
||||||
|
// Useful for debugging
|
||||||
|
e.Data["Tapeti.Controller.Name"] = context.Controller.GetType().FullName;
|
||||||
|
e.Data["Tapeti.Controller.Method"] = context.Method.Name;
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
flowContext.EnsureStoreOrDeleteIsCalled();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (disposeFlowContext)
|
||||||
|
flowContext.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,6 +35,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Messaging.TapetiExample", "
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "02-DeclareDurableQueues", "02-DeclareDurableQueues\02-DeclareDurableQueues.csproj", "{85511282-EF91-4B56-B7DC-9E8706556D6E}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "02-DeclareDurableQueues", "02-DeclareDurableQueues\02-DeclareDurableQueues.csproj", "{85511282-EF91-4B56-B7DC-9E8706556D6E}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "03-FlowRequestResponse", "03-FlowRequestResponse\03-FlowRequestResponse.csproj", "{463A12CE-E221-450D-ADEA-91A599612DFA}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -101,6 +103,10 @@ Global
|
|||||||
{85511282-EF91-4B56-B7DC-9E8706556D6E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{85511282-EF91-4B56-B7DC-9E8706556D6E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{85511282-EF91-4B56-B7DC-9E8706556D6E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{85511282-EF91-4B56-B7DC-9E8706556D6E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{85511282-EF91-4B56-B7DC-9E8706556D6E}.Release|Any CPU.Build.0 = Release|Any CPU
|
{85511282-EF91-4B56-B7DC-9E8706556D6E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{463A12CE-E221-450D-ADEA-91A599612DFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{463A12CE-E221-450D-ADEA-91A599612DFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{463A12CE-E221-450D-ADEA-91A599612DFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{463A12CE-E221-450D-ADEA-91A599612DFA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@ -110,6 +116,7 @@ Global
|
|||||||
{F3B38753-06B4-4932-84B4-A07692AD802D} = {266B9B94-A4D2-41C2-860C-24A7C3B63B56}
|
{F3B38753-06B4-4932-84B4-A07692AD802D} = {266B9B94-A4D2-41C2-860C-24A7C3B63B56}
|
||||||
{D24120D4-50A2-44B6-A4EA-6ADAAEBABA84} = {266B9B94-A4D2-41C2-860C-24A7C3B63B56}
|
{D24120D4-50A2-44B6-A4EA-6ADAAEBABA84} = {266B9B94-A4D2-41C2-860C-24A7C3B63B56}
|
||||||
{85511282-EF91-4B56-B7DC-9E8706556D6E} = {266B9B94-A4D2-41C2-860C-24A7C3B63B56}
|
{85511282-EF91-4B56-B7DC-9E8706556D6E} = {266B9B94-A4D2-41C2-860C-24A7C3B63B56}
|
||||||
|
{463A12CE-E221-450D-ADEA-91A599612DFA} = {266B9B94-A4D2-41C2-860C-24A7C3B63B56}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {B09CC2BF-B2AF-4CB6-8728-5D1D8E5C50FA}
|
SolutionGuid = {B09CC2BF-B2AF-4CB6-8728-5D1D8E5C50FA}
|
||||||
|
Loading…
Reference in New Issue
Block a user