Upgraded to .NET 6

Implemented Tapeti compatible publishing
WIP: example generator from Tapeti message classes (UI still hidden)
This commit is contained in:
Mark van Renswoude 2021-12-15 10:50:45 +01:00
parent 229fc9415d
commit 057cac4e22
28 changed files with 1165 additions and 288 deletions

View File

@ -1,164 +0,0 @@
using System;
using System.Collections.Immutable;
using System.IO;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using System.Text;
using Newtonsoft.Json.Linq;
namespace ParseTapetiMessagesPrototype
{
public static class MetadataReaderMessageParser
{
public static void ParseAssembly(string classLibraryFilename)
{
try
{
using var fileStream = new FileStream(classLibraryFilename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using var peReader = new PEReader(fileStream);
var metadataReader = peReader.GetMetadataReader();
// ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator
foreach (var typeDefinitionHandle in metadataReader.TypeDefinitions)
{
var typeDefinition = metadataReader.GetTypeDefinition(typeDefinitionHandle);
HandleTypeDefinition(metadataReader, typeDefinition);
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
private static void HandleTypeDefinition(MetadataReader metadataReader, TypeDefinition typeDefinition)
{
var typeNamespace = metadataReader.GetString(typeDefinition.Namespace);
var typeName = metadataReader.GetString(typeDefinition.Name);
// For this prototype, filter out anything not ending in Message
// Might want to show a full tree in PettingZoo since this is just a convention
if (!typeName.EndsWith("Message"))
return;
Console.WriteLine($"{typeNamespace}.{typeName}");
var example = new JObject();
// ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator
foreach (var propertyDefinitionHandle in typeDefinition.GetProperties())
{
// TODO get properties from base class
var propertyDefinition = metadataReader.GetPropertyDefinition(propertyDefinitionHandle);
HandlePropertyDefinition(metadataReader, propertyDefinition, example);
}
Console.WriteLine(example.ToString());
Console.WriteLine();
}
private static void HandlePropertyDefinition(MetadataReader metadataReader, PropertyDefinition propertyDefinition, JObject targetObject)
{
var fieldName = metadataReader.GetString(propertyDefinition.Name);
var signature = propertyDefinition.DecodeSignature(new JsonSignatureProvider(), null);
targetObject.Add(fieldName, signature.ReturnType);
}
private class JsonSignatureProvider : ISignatureTypeProvider<JToken, object>
{
public JToken GetPrimitiveType(PrimitiveTypeCode typeCode)
{
return typeCode switch
{
PrimitiveTypeCode.Boolean => false,
PrimitiveTypeCode.Byte or
PrimitiveTypeCode.Int16 or
PrimitiveTypeCode.Int32 or
PrimitiveTypeCode.Int64 or
PrimitiveTypeCode.IntPtr or
PrimitiveTypeCode.SByte or
PrimitiveTypeCode.UInt16 or
PrimitiveTypeCode.UInt32 or
PrimitiveTypeCode.UInt64 or
PrimitiveTypeCode.UIntPtr => 0,
PrimitiveTypeCode.Char or
PrimitiveTypeCode.String => "",
PrimitiveTypeCode.Double or
PrimitiveTypeCode.Single => 0.0,
// TODO recurse
PrimitiveTypeCode.Object => "OBJECT",
_ => $"Unsupported primitive type code: {typeCode}"
};
}
public JToken GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind = 0) => "typedef";
public JToken GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind = 0)
{
var typeReference = reader.GetTypeReference(handle);
var typeName = reader.GetString(typeReference.Name);
return typeName;
}
public JToken GetTypeFromSpecification(MetadataReader reader, object genericContext, TypeSpecificationHandle handle, byte rawTypeKind = 0) => "typespec";
public JToken GetSZArrayType(JToken elementType) => new JValue(elementType + "[]");
public JToken GetPointerType(JToken elementType) => null;
public JToken GetByReferenceType(JToken elementType) => null;
public JToken GetGenericMethodParameter(object genericContext, int index) => "!!" + index;
public JToken GetGenericTypeParameter(object genericContext, int index) => "!" + index;
public JToken GetPinnedType(JToken elementType) => elementType + " pinned";
public JToken GetGenericInstantiation(JToken genericType, ImmutableArray<JToken> typeArguments) => genericType + "<" + string.Join(",", typeArguments) + ">";
public JToken GetModifiedType(JToken modifierType, JToken unmodifiedType, bool isRequired) => unmodifiedType + (isRequired ? " modreq(" : " modopt(") + modifierType + ")";
public JToken GetArrayType(JToken elementType, ArrayShape shape)
{
var builder = new StringBuilder();
builder.Append(elementType);
builder.Append('[');
for (int i = 0; i < shape.Rank; i++)
{
int lowerBound = 0;
if (i < shape.LowerBounds.Length)
{
lowerBound = shape.LowerBounds[i];
builder.Append(lowerBound);
}
builder.Append("...");
if (i < shape.Sizes.Length)
{
builder.Append(lowerBound + shape.Sizes[i] - 1);
}
if (i < shape.Rank - 1)
{
builder.Append(',');
}
}
builder.Append(']');
return builder.ToString();
}
public JToken GetFunctionPointerType(MethodSignature<JToken> signature) => "methodptr(something)";
}
}
}

View File

@ -1,12 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>
</Project>

View File

@ -1,21 +0,0 @@
namespace ParseTapetiMessagesPrototype
{
public class Program
{
public static void Main()
{
const string classLibraryFilename = "D:\\Temp\\lib\\netstandard2.0\\Messaging.Relatie.dll";
// There are advantages to using the MetadataReader, for example no code is run (LoadAssemblyForReflection is no longer
// supported in .NET Core) and the assembly is not locked at all. This comes at the cost of complexity however, so
// this prototype explores both options.
//
// In the final version perhaps we can work around loading the assembly into our own process by spawning a new process
// to convert it into metadata used by the main process.
//MetadataReaderMessageParser.ParseAssembly(classLibraryFilename);
AssemblyLoaderMessageParser.ParseAssembly(classLibraryFilename);
}
}
}

View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
namespace PettingZoo.Core.Generator
{
public interface IExampleSource : IDisposable
{
IExampleFolder GetRootFolder();
}
public interface IExampleFolder
{
public string Name { get; }
public IReadOnlyList<IExampleFolder> Folders { get; }
public IReadOnlyList<IExampleMessage> Messages { get; }
}
public interface IExampleMessage
{
string Generate();
}
}

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>

View File

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PettingZoo.Core\PettingZoo.Core.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,187 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using Newtonsoft.Json;
using PettingZoo.Core.Generator;
namespace PettingZoo.Tapeti
{
public class TapetiClassLibraryExampleSource : IExampleSource
{
private readonly string classLibraryFilename;
private readonly IEnumerable<string> extraAssemblies;
private Lazy<AssemblySource> assemblySource;
public TapetiClassLibraryExampleSource(string classLibraryFilename, IEnumerable<string> extraAssemblies)
{
this.classLibraryFilename = classLibraryFilename;
this.extraAssemblies = extraAssemblies;
assemblySource = new Lazy<AssemblySource>(AssemblySourceFactory);
}
public void Dispose()
{
if (assemblySource.IsValueCreated)
assemblySource.Value.Dispose();
GC.SuppressFinalize(this);
}
public IExampleFolder GetRootFolder()
{
return assemblySource.Value.RootFolder;
}
private AssemblySource AssemblySourceFactory()
{
var runtimeAssemblies = Directory.GetFiles(RuntimeEnvironment.GetRuntimeDirectory(), "*.dll");
var paths = runtimeAssemblies
.Concat(extraAssemblies)
.Append(classLibraryFilename);
// TODO can we use a custom resolver to detect missing references?
var resolver = new PathAssemblyResolver(paths);
var loadContext = new MetadataLoadContext(resolver);
try
{
var assembly = loadContext.LoadFromAssemblyPath(classLibraryFilename);
var rootFolder = new Folder(@"Root");
foreach (var assemblyType in assembly.GetTypes())
AddType(assemblyType, rootFolder);
return new AssemblySource
{
LoadContext = loadContext,
RootFolder = rootFolder
};
}
catch
{
loadContext.Dispose();
throw;
}
}
private void AddType(Type type, Folder rootFolder)
{
if (!type.IsClass)
return;
var assemblyName = type.Assembly.GetName().Name + ".";
var typeNamespace = type.Namespace ?? "";
if (typeNamespace.StartsWith(assemblyName))
typeNamespace = typeNamespace.Substring(assemblyName.Length);
var folder = CreateFolder(rootFolder, typeNamespace);
folder.AddMessage(new Message(type));
}
private static Folder CreateFolder(Folder rootFolder, string typeNamespace)
{
var parts = typeNamespace.Split('.');
if (parts.Length == 0)
return rootFolder;
var folder = rootFolder;
foreach (var part in parts)
folder = folder.CreateFolder(part);
return folder;
}
private class Folder : IExampleFolder
{
private readonly List<Folder> folders = new();
private readonly List<IExampleMessage> messages = new();
public string Name { get; }
public IReadOnlyList<IExampleFolder> Folders => folders;
public IReadOnlyList<IExampleMessage> Messages => messages;
public Folder(string name)
{
Name = name;
}
public Folder CreateFolder(string name)
{
var folder = folders.FirstOrDefault(f => f.Name == name);
if (folder != null)
return folder;
folder = new Folder(name);
folders.Add(folder);
return folder;
}
public void AddMessage(IExampleMessage message)
{
messages.Add(message);
}
}
private class Message : IExampleMessage
{
private readonly Type type;
public Message(Type type)
{
this.type = type;
}
public string Generate()
{
/*
We can't create an instance of the type to serialize easily, as most will depend on
assemblies not included in the NuGet package, so we'll parse the Type ourselves.
This is still much easier than using MetadataReader, as we can more easily check against
standard types like Nullable.
The only external dependencies should be the attributes, like [RequiredGuid]. The messaging models
themselves should not inherit from classes outside of their assembly, or include properties
with types from other assemblies. With that assumption, walking the class structure should be safe.
The extraAssemblies passed to TapetiClassLibraryExampleSource can also be used to give it a better chance.
*/
var serialized = TypeToJObjectConverter.Convert(type);
return serialized.ToString(Formatting.Indented);
}
}
private class AssemblySource : IDisposable
{
public MetadataLoadContext LoadContext { get; init; }
public IExampleFolder RootFolder { get; init; }
public void Dispose()
{
LoadContext.Dispose();
}
}
}
}

View File

@ -2,66 +2,21 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Newtonsoft.Json.Linq;
namespace ParseTapetiMessagesPrototype
namespace PettingZoo.Tapeti
{
public static class AssemblyLoaderMessageParser
internal class TypeToJObjectConverter
{
public static void ParseAssembly(string classLibraryFilename)
{
var loadContext = new AssemblyLoadContext(null, true);
try
{
var assembly = loadContext.LoadFromAssemblyPath(classLibraryFilename);
foreach (var assemblyType in assembly.GetTypes())
HandleType(assemblyType);
}
finally
{
loadContext.Unload();
}
}
private static void HandleType(Type type)
{
if (!type.IsClass)
return;
// For this prototype, filter out anything not ending in Message
// Might want to show a full tree in PettingZoo since this is just a convention
if (!type.Name.EndsWith("Message") || type.Name != "RelatieUpdateMessage")
return;
Console.WriteLine($"{type.Namespace}.{type.Name}");
// We can't create an instance of the type to serialize easily, as most will depend on
// assemblies not included in the NuGet package, so we'll parse the Type ourselves.
// This is still slightly easier than using MetadataReader, as we can more easily check against
// standard types like Nullable.
//
// The only external dependencies should be the attributes, like [RequiredGuid]. The messaging models
// themselves should not inherit from classes outside of their assembly, or include properties
// with types from other assemblies. With that assumption, walking the class structure should be safe.
var serialized = TypeToJObject(type);
Console.WriteLine(serialized);
Console.WriteLine("");
}
private static JObject TypeToJObject(Type type)
public static JObject Convert(Type type)
{
var result = new JObject();
foreach (var propertyInfo in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
// Note: unfortunately we can not call GetCustomAttributes here, as that would
// trigger assemblies not included in the package to be loaded
var value = PropertyToJToken(propertyInfo.PropertyType);
result.Add(propertyInfo.Name, value);
}
@ -82,38 +37,38 @@ namespace ParseTapetiMessagesPrototype
{ typeof(float), 0.0 },
{ typeof(bool), false }
};
private static JToken PropertyToJToken(Type propertyType)
{
var actualType = Nullable.GetUnderlyingType(propertyType) ?? propertyType;
// String is also a class
if (actualType == typeof(string))
return "";
if (actualType.IsClass)
{
// IEnumerable<T>
var enumerableInterface = actualType.GetInterfaces()
.FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>));
if (enumerableInterface != null)
return new JArray(TypeToJObject(enumerableInterface.GetGenericArguments()[0]));
return TypeToJObject(actualType);
return new JArray(Convert(enumerableInterface.GetGenericArguments()[0]));
return Convert(actualType);
}
if (actualType.IsArray)
return new JArray(TypeToJObject(actualType.GetElementType()));
return new JArray(Convert(actualType.GetElementType()));
if (actualType.IsEnum)
return Enum.GetNames(actualType).FirstOrDefault();
// Special cases for runtime generated values
if (actualType == typeof(DateTime))
{
@ -129,5 +84,7 @@ namespace ParseTapetiMessagesPrototype
? mappedToken
: $"(unknown type: {actualType.Name})";
}
}
}

View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31911.196
# Visual Studio Version 17
VisualStudioVersion = 17.0.31912.275
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PettingZoo", "PettingZoo\PettingZoo.csproj", "{24819D09-C747-4356-B686-D9DE9CAA6F59}"
EndProject
@ -14,7 +14,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PettingZoo.Core", "PettingZ
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PettingZoo.RabbitMQ", "PettingZoo.RabbitMQ\PettingZoo.RabbitMQ.csproj", "{220149F3-A8D6-44ED-B3B6-DFE506EB018A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ParseTapetiMessagesPrototype", "ParseTapetiMessagesPrototype\ParseTapetiMessagesPrototype.csproj", "{B06DDB4F-04D1-4325-9F7B-5FBA0AAE47E7}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PettingZoo.Tapeti", "PettingZoo.Tapeti\PettingZoo.Tapeti.csproj", "{1763AB04-59D9-4663-B207-D6302FFAACD5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -34,10 +34,10 @@ Global
{220149F3-A8D6-44ED-B3B6-DFE506EB018A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{220149F3-A8D6-44ED-B3B6-DFE506EB018A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{220149F3-A8D6-44ED-B3B6-DFE506EB018A}.Release|Any CPU.Build.0 = Release|Any CPU
{B06DDB4F-04D1-4325-9F7B-5FBA0AAE47E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B06DDB4F-04D1-4325-9F7B-5FBA0AAE47E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B06DDB4F-04D1-4325-9F7B-5FBA0AAE47E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B06DDB4F-04D1-4325-9F7B-5FBA0AAE47E7}.Release|Any CPU.Build.0 = Release|Any CPU
{1763AB04-59D9-4663-B207-D6302FFAACD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1763AB04-59D9-4663-B207-D6302FFAACD5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1763AB04-59D9-4663-B207-D6302FFAACD5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1763AB04-59D9-4663-B207-D6302FFAACD5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0-windows</TargetFramework>
<TargetFramework>net6.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<Authors>Mark van Renswoude</Authors>
<Product>Petting Zoo</Product>
@ -46,6 +46,11 @@
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
</Compile>
<Compile Update="UI\Example\ExamplePickerDialogStrings.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>ExamplePickerDialogStrings.resx</DependentUpon>
</Compile>
<Compile Update="UI\Main\MainWindowStrings.Designer.cs">
<DependentUpon>MainWindowStrings.resx</DependentUpon>
<DesignTime>True</DesignTime>
@ -56,6 +61,14 @@
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
</Compile>
<Compile Update="UI\Tab\Publisher\TapetiPublisherViewStrings.Designer.cs">
<DependentUpon>TapetiPublisherViewStrings.resx</DependentUpon>
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
</Compile>
<Compile Update="UI\Tab\Publisher\TapetiPublisherView.xaml.cs">
<SubType>Code</SubType>
</Compile>
<Compile Update="UI\Tab\Publisher\RawPublisherViewStrings.Designer.cs">
<DependentUpon>RawPublisherViewStrings.resx</DependentUpon>
<DesignTime>True</DesignTime>
@ -78,6 +91,10 @@
<LastGenOutput>ConnectionWindowStrings.Designer.cs</LastGenOutput>
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="UI\Example\ExamplePickerDialogStrings.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>ExamplePickerDialogStrings.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="UI\Main\MainWindowStrings.resx">
<LastGenOutput>MainWindowStrings.Designer.cs</LastGenOutput>
<Generator>PublicResXFileCodeGenerator</Generator>
@ -86,6 +103,10 @@
<LastGenOutput>SubscribeWindowStrings.Designer.cs</LastGenOutput>
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="UI\Tab\Publisher\TapetiPublisherViewStrings.resx">
<LastGenOutput>TapetiPublisherViewStrings.Designer.cs</LastGenOutput>
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="UI\Tab\Publisher\RawPublisherViewStrings.resx">
<LastGenOutput>RawPublisherViewStrings.Designer.cs</LastGenOutput>
<Generator>PublicResXFileCodeGenerator</Generator>
@ -113,6 +134,10 @@
<Page Update="UI\Tab\Publisher\PublisherView.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
</Page>
<Page Update="UI\Tab\Publisher\TapetiPublisherView.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
<SubType>Designer</SubType>
</Page>
<Page Update="UI\Tab\Subscriber\SubscriberView.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
</Page>

View File

@ -67,8 +67,9 @@
<Style x:Key="RoutingKey">
</Style>
<Style x:Key="TypeSelection" TargetType="{x:Type FrameworkElement}">
<Style x:Key="TypeSelection" TargetType="{x:Type ToggleButton}">
<Setter Property="Margin" Value="0 0 8 0" />
<Setter Property="Padding" Value="8 4 8 4" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>

View File

@ -0,0 +1,12 @@
<Window x:Class="PettingZoo.UI.Example.ExamplePickerDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:example="clr-namespace:PettingZoo.UI.Example"
mc:Ignorable="d"
Title="{x:Static example:ExamplePickerDialogStrings.WindowTitle}" Height="800" Width="600">
<Grid>
</Grid>
</Window>

View File

@ -0,0 +1,15 @@
using System.Windows;
namespace PettingZoo.UI.Example
{
/// <summary>
/// Interaction logic for ExamplePickerDialog.xaml
/// </summary>
public partial class ExamplePickerDialog : Window
{
public ExamplePickerDialog()
{
InitializeComponent();
}
}
}

View File

@ -0,0 +1,72 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace PettingZoo.UI.Example {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class ExamplePickerDialogStrings {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal ExamplePickerDialogStrings() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PettingZoo.UI.Example.ExamplePickerDialogStrings", typeof(ExamplePickerDialogStrings).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to Select example.
/// </summary>
public static string WindowTitle {
get {
return ResourceManager.GetString("WindowTitle", resourceCulture);
}
}
}
}

View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="WindowTitle" xml:space="preserve">
<value>Select example</value>
</data>
</root>

View File

@ -0,0 +1,6 @@
namespace PettingZoo.UI.Example
{
public class ExamplePickerDialogViewModel
{
}
}

View File

@ -37,8 +37,7 @@ namespace PettingZoo.UI.Main
private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
{
// TODO support command-line parameters for easier testing
//viewModel.ConnectCommand.Execute(null);
viewModel.ConnectCommand.Execute(null);
}

View File

@ -15,10 +15,9 @@
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Grid.Row="0">
<Label Content="{x:Static res:PublisherViewStrings.LabelMessageType}" Style="{StaticResource TypeSelection}" />
<RadioButton Content="{x:Static res:PublisherViewStrings.OptionMessageTypeRaw}" Style="{StaticResource TypeSelection}" IsChecked="{Binding MessageTypeRaw}" />
<RadioButton Content="{x:Static res:PublisherViewStrings.OptionMessageTypeTapeti}" Style="{StaticResource TypeSelection}" IsChecked="{Binding MessageTypeTapeti}" />
<StackPanel Orientation="Horizontal" Grid.Row="0" HorizontalAlignment="Center">
<ToggleButton Content="{x:Static res:PublisherViewStrings.OptionMessageTypeRaw}" Style="{StaticResource TypeSelection}" IsChecked="{Binding MessageTypeRaw}" />
<ToggleButton Content="{x:Static res:PublisherViewStrings.OptionMessageTypeTapeti}" Style="{StaticResource TypeSelection}" IsChecked="{Binding MessageTypeTapeti}" />
</StackPanel>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">

View File

@ -110,22 +110,21 @@ namespace PettingZoo.UI.Tab.Publisher
MessageTypeControl = rawPublisherView;
messageTypePublishCommand = rawPublisherViewModel.PublishCommand;
publishCommand.RaiseCanExecuteChanged();
break;
case MessageType.Tapeti:
// TODO
var tapetiPublisherViewModel = new RawPublisherViewModel(connection);
tapetiPublisherView ??= new RawPublisherView(tapetiPublisherViewModel);
var tapetiPublisherViewModel = new TapetiPublisherViewModel(connection);
tapetiPublisherView ??= new TapetiPublisherView(tapetiPublisherViewModel);
MessageTypeControl = tapetiPublisherView;
messageTypePublishCommand = tapetiPublisherViewModel.PublishCommand;
publishCommand.RaiseCanExecuteChanged();
break;
default:
throw new ArgumentException();
throw new ArgumentException($@"Unknown message type: {value}", nameof(value));
}
publishCommand.RaiseCanExecuteChanged();
}
}

View File

@ -19,7 +19,7 @@ namespace PettingZoo.UI.Tab.Publisher {
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class PublisherViewStrings {
@ -79,7 +79,7 @@ namespace PettingZoo.UI.Tab.Publisher {
}
/// <summary>
/// Looks up a localized string similar to Raw.
/// Looks up a localized string similar to Raw message.
/// </summary>
public static string OptionMessageTypeRaw {
get {
@ -88,7 +88,7 @@ namespace PettingZoo.UI.Tab.Publisher {
}
/// <summary>
/// Looks up a localized string similar to Tapeti.
/// Looks up a localized string similar to Tapeti message.
/// </summary>
public static string OptionMessageTypeTapeti {
get {

View File

@ -124,9 +124,9 @@
<value>Message type: </value>
</data>
<data name="OptionMessageTypeRaw" xml:space="preserve">
<value>Raw</value>
<value>Raw message</value>
</data>
<data name="OptionMessageTypeTapeti" xml:space="preserve">
<value>Tapeti</value>
<value>Tapeti message</value>
</data>
</root>

View File

@ -0,0 +1,78 @@
<UserControl x:Class="PettingZoo.UI.Tab.Publisher.TapetiPublisherView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:publisher="clr-namespace:PettingZoo.UI.Tab.Publisher"
xmlns:ui="clr-namespace:PettingZoo.UI"
mc:Ignorable="d"
d:DesignHeight="800" d:DesignWidth="800"
d:DataContext="{d:DesignInstance publisher:DesignTimeTapetiPublisherViewModel, IsDesignTimeCreatable=True}"
Background="White">
<ui:GridLayout Style="{StaticResource Form}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ui:GridLayout.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="16"/>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="16"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" />
</ui:GridLayout.RowDefinitions>
<Label Grid.Row="0" Grid.Column="1">
<StackPanel Orientation="Horizontal">
<RadioButton Content="{x:Static publisher:TapetiPublisherViewStrings.LabelSendToExchange}" IsChecked="{Binding SendToExchange}" Style="{StaticResource TypeSelection}" />
<RadioButton Content="{x:Static publisher:TapetiPublisherViewStrings.LabelSendToQueue}" IsChecked="{Binding SendToQueue}" Style="{StaticResource TypeSelection}" />
</StackPanel>
</Label>
<Label Grid.Row="1" Grid.Column="0" Content="{x:Static publisher:TapetiPublisherViewStrings.LabelExchange}" Visibility="{Binding ExchangeVisibility}" />
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Exchange}" Visibility="{Binding ExchangeVisibility}" />
<Label Grid.Row="2" Grid.Column="0" Content="{x:Static publisher:TapetiPublisherViewStrings.LabelRoutingKey}" Visibility="{Binding ExchangeVisibility}" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding RoutingKey}" Visibility="{Binding ExchangeVisibility}" />
<Label Grid.Row="3" Grid.Column="0" Content="{x:Static publisher:TapetiPublisherViewStrings.LabelQueue}" Visibility="{Binding QueueVisibility}" />
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding Queue}" Visibility="{Binding QueueVisibility}" />
<Label Grid.Row="5" Grid.Column="0" Content="{x:Static publisher:TapetiPublisherViewStrings.LabelProperties}" Style="{StaticResource SectionLabel}"/>
<Label Grid.Row="6" Grid.Column="0" Content="{x:Static publisher:TapetiPublisherViewStrings.LabelCorrelationId}" />
<TextBox Grid.Row="6" Grid.Column="1" Text="{Binding CorrelationId}" />
<Label Grid.Row="7" Grid.Column="0" Content="{x:Static publisher:TapetiPublisherViewStrings.LabelReplyTo}" />
<TextBox Grid.Row="7" Grid.Column="1" Text="{Binding ReplyTo}" />
<Label Grid.Row="10" Grid.Column="0" Content="{x:Static publisher:TapetiPublisherViewStrings.LabelAssemblyName}" />
<TextBox Grid.Row="10" Grid.Column="1" Text="{Binding AssemblyName, UpdateSourceTrigger=PropertyChanged}" />
<Label Grid.Row="11" Grid.Column="0" Content="{x:Static publisher:TapetiPublisherViewStrings.LabelClassName}" />
<Grid Grid.Row="11" Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{Binding ClassName}" GotFocus="CaretToEnd" />
<!--
<Button Grid.Column="1" Content="{x:Static publisher:TapetiPublisherViewStrings.ButtonBrowseClass}" />
-->
</Grid>
<Label Grid.Row="12" Grid.Column="0" Content="{x:Static publisher:TapetiPublisherViewStrings.LabelPayload}" />
<TextBox Grid.Row="12" Grid.Column="1" Text="{Binding Payload}" AcceptsReturn="True" VerticalScrollBarVisibility="Visible" Height="150" />
</ui:GridLayout>
</UserControl>

View File

@ -0,0 +1,26 @@
using System.Windows;
using System.Windows.Controls;
namespace PettingZoo.UI.Tab.Publisher
{
/// <summary>
/// Interaction logic for TapetiPublisherView.xaml
/// </summary>
public partial class TapetiPublisherView
{
public TapetiPublisherView(TapetiPublisherViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
private void CaretToEnd(object sender, RoutedEventArgs e)
{
if (sender is not TextBox textBox)
return;
textBox.CaretIndex = textBox.Text?.Length ?? 0;
}
}
}

View File

@ -0,0 +1,170 @@
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Input;
using PettingZoo.Core.Connection;
using IConnection = PettingZoo.Core.Connection.IConnection;
namespace PettingZoo.UI.Tab.Publisher
{
public class TapetiPublisherViewModel : BaseViewModel
{
private readonly IConnection connection;
private readonly DelegateCommand publishCommand;
private bool sendToExchange = true;
private string exchange = "";
private string routingKey = "";
private string queue = "";
private MessageDeliveryMode deliveryMode;
private string correlationId = "";
private string replyTo = "";
private string payload = "";
private string className = "";
private string assemblyName = "";
public bool SendToExchange
{
get => sendToExchange;
set => SetField(ref sendToExchange, value, otherPropertiesChanged: new[] { nameof(SendToQueue), nameof(ExchangeVisibility), nameof(QueueVisibility) });
}
public bool SendToQueue
{
get => !SendToExchange;
set => SendToExchange = !value;
}
public string Exchange
{
get => exchange;
set => SetField(ref exchange, value);
}
public string RoutingKey
{
get => routingKey;
set => SetField(ref routingKey, value);
}
public string Queue
{
get => queue;
set => SetField(ref queue, value);
}
public virtual Visibility ExchangeVisibility => SendToExchange ? Visibility.Visible : Visibility.Collapsed;
public virtual Visibility QueueVisibility => SendToQueue ? Visibility.Visible : Visibility.Collapsed;
public int DeliveryModeIndex
{
get => deliveryMode == MessageDeliveryMode.Persistent ? 1 : 0;
set => SetField(ref deliveryMode, value == 1 ? MessageDeliveryMode.Persistent : MessageDeliveryMode.NonPersistent);
}
public string CorrelationId
{
get => correlationId;
set => SetField(ref correlationId, value);
}
public string ReplyTo
{
get => replyTo;
set => SetField(ref replyTo, value);
}
public string ClassName
{
get => string.IsNullOrEmpty(className) ? AssemblyName + "." : className;
set => SetField(ref className, value);
}
public string AssemblyName
{
get => assemblyName;
set => SetField(ref assemblyName, value, otherPropertiesChanged:
string.IsNullOrEmpty(value) || string.IsNullOrEmpty(className)
? new [] { nameof(ClassName) }
: null
);
}
public string Payload
{
get => payload;
set => SetField(ref payload, value);
}
public ICommand PublishCommand => publishCommand;
public TapetiPublisherViewModel(IConnection connection)
{
this.connection = connection;
publishCommand = new DelegateCommand(PublishExecute, PublishCanExecute);
}
private void PublishExecute()
{
static string? NullIfEmpty(string? value)
{
return string.IsNullOrEmpty(value) ? null : value;
}
// TODO support for Reply To to dynamic queue which waits for a message (or opens a new subscriber tab?)
// TODO background worker / async
connection.Publish(new PublishMessageInfo(
SendToExchange ? Exchange : "",
SendToExchange ? RoutingKey : Queue,
Encoding.UTF8.GetBytes(Payload),
new MessageProperties(new Dictionary<string, string>
{
{ @"classType", $"{ClassName}:{AssemblyName}" }
})
{
ContentType = @"application/json",
CorrelationId = NullIfEmpty(CorrelationId),
DeliveryMode = deliveryMode,
ReplyTo = NullIfEmpty(ReplyTo)
}));
}
private bool PublishCanExecute()
{
// TODO validate input
return true;
}
}
public class DesignTimeTapetiPublisherViewModel : TapetiPublisherViewModel
{
public DesignTimeTapetiPublisherViewModel() : base(null!)
{
}
public override Visibility ExchangeVisibility => Visibility.Visible;
public override Visibility QueueVisibility => Visibility.Visible;
}
}

View File

@ -0,0 +1,198 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace PettingZoo.UI.Tab.Publisher {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class TapetiPublisherViewStrings {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal TapetiPublisherViewStrings() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PettingZoo.UI.Tab.Publisher.TapetiPublisherViewStrings", typeof(TapetiPublisherViewStrings).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to ....
/// </summary>
public static string ButtonBrowseClass {
get {
return ResourceManager.GetString("ButtonBrowseClass", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Transient (non-persistent).
/// </summary>
public static string DeliveryModeNonPersistent {
get {
return ResourceManager.GetString("DeliveryModeNonPersistent", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Persistent.
/// </summary>
public static string DeliveryModePersistent {
get {
return ResourceManager.GetString("DeliveryModePersistent", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Assembly name.
/// </summary>
public static string LabelAssemblyName {
get {
return ResourceManager.GetString("LabelAssemblyName", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Class name (full).
/// </summary>
public static string LabelClassName {
get {
return ResourceManager.GetString("LabelClassName", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Correlation ID.
/// </summary>
public static string LabelCorrelationId {
get {
return ResourceManager.GetString("LabelCorrelationId", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Delivery mode.
/// </summary>
public static string LabelDeliveryMode {
get {
return ResourceManager.GetString("LabelDeliveryMode", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Exchange.
/// </summary>
public static string LabelExchange {
get {
return ResourceManager.GetString("LabelExchange", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Payload.
/// </summary>
public static string LabelPayload {
get {
return ResourceManager.GetString("LabelPayload", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Properties.
/// </summary>
public static string LabelProperties {
get {
return ResourceManager.GetString("LabelProperties", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Queue.
/// </summary>
public static string LabelQueue {
get {
return ResourceManager.GetString("LabelQueue", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Reply To.
/// </summary>
public static string LabelReplyTo {
get {
return ResourceManager.GetString("LabelReplyTo", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Routing key.
/// </summary>
public static string LabelRoutingKey {
get {
return ResourceManager.GetString("LabelRoutingKey", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Publish to exchange (topic).
/// </summary>
public static string LabelSendToExchange {
get {
return ResourceManager.GetString("LabelSendToExchange", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Publish to queue (direct).
/// </summary>
public static string LabelSendToQueue {
get {
return ResourceManager.GetString("LabelSendToQueue", resourceCulture);
}
}
}
}

View File

@ -0,0 +1,165 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ButtonBrowseClass" xml:space="preserve">
<value>...</value>
</data>
<data name="DeliveryModeNonPersistent" xml:space="preserve">
<value>Transient (non-persistent)</value>
</data>
<data name="DeliveryModePersistent" xml:space="preserve">
<value>Persistent</value>
</data>
<data name="LabelAssemblyName" xml:space="preserve">
<value>Assembly name</value>
</data>
<data name="LabelClassName" xml:space="preserve">
<value>Class name (full)</value>
</data>
<data name="LabelCorrelationId" xml:space="preserve">
<value>Correlation ID</value>
</data>
<data name="LabelDeliveryMode" xml:space="preserve">
<value>Delivery mode</value>
</data>
<data name="LabelExchange" xml:space="preserve">
<value>Exchange</value>
</data>
<data name="LabelPayload" xml:space="preserve">
<value>Payload</value>
</data>
<data name="LabelProperties" xml:space="preserve">
<value>Properties</value>
</data>
<data name="LabelQueue" xml:space="preserve">
<value>Queue</value>
</data>
<data name="LabelReplyTo" xml:space="preserve">
<value>Reply To</value>
</data>
<data name="LabelRoutingKey" xml:space="preserve">
<value>Routing key</value>
</data>
<data name="LabelSendToExchange" xml:space="preserve">
<value>Publish to exchange (topic)</value>
</data>
<data name="LabelSendToQueue" xml:space="preserve">
<value>Publish to queue (direct)</value>
</data>
</root>

View File

@ -8,6 +8,8 @@ using PettingZoo.Core.Connection;
using PettingZoo.Core.Rendering;
// TODO update title with unread message count if tab is not active
// TODO export option (to Tapeti.Cmd compatible format / command-line of course)
// TODO send message to (new) publisher tab
namespace PettingZoo.UI.Tab.Subscriber
{