1
0
mirror of synced 2024-11-14 09:29:17 +00:00

Live RabbitMQ connection, Json pretty-printing and further tweaks

This commit is contained in:
PsychoMark 2016-06-20 23:22:20 +02:00
parent ceb2e386f4
commit 05a6e380e4
19 changed files with 5203 additions and 50 deletions

View File

@ -1,6 +1,8 @@
using System.Linq;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Markup;
using PettingZoo.Model;
using PettingZoo.View;
using SimpleInjector;
@ -11,6 +13,10 @@ namespace PettingZoo
{
public void ApplicationStartup(object sender, StartupEventArgs e)
{
// WPF defaults to US for date formatting in bindings, this fixes it
FrameworkElement.LanguageProperty.OverrideMetadata(typeof(FrameworkElement), new FrameworkPropertyMetadata(
XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag)));
var container = Bootstrap();
RunApplication(container);
}
@ -38,6 +44,8 @@ namespace PettingZoo
private static void RunApplication(Container container)
{
var mainWindow = container.GetInstance<MainWindow>();
mainWindow.Closed += (sender, args) => container.Dispose();
mainWindow.Show();
}
}

BIN
Images/Connect.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
Images/Disconnect.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

4900
Images/PettingZoo.ai Normal file

File diff suppressed because one or more lines are too long

BIN
Images/PettingZoo.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -51,7 +51,7 @@ namespace PettingZoo.Infrastructure
ItemsSourcePropertyChanged));
private readonly System.Windows.Controls.ListBox target;
private ScrollViewer scrollViewer = null;
private ScrollViewer scrollViewer;
public AutoScrollHandler(System.Windows.Controls.ListBox target)
{

View File

@ -1,27 +1,42 @@
using System;
using System.Collections.Generic;
using System.Text;
using Newtonsoft.Json;
namespace PettingZoo.Model
{
public class MessageBodyRenderer
{
public static List<String> TextTypes = new List<string>
public static Dictionary<string, Func<byte[], string>> ContentTypeHandlers = new Dictionary<string, Func<byte[], string>>
{
"application/json",
"application/xml"
{ "application/json", RenderJson }
};
public static string Render(byte[] body, string contentType = "")
{
if (TextTypes.Contains(contentType))
{
return Encoding.UTF8.GetString(body);
}
Func<byte[], string> handler;
// ToDo hex output
return "";
if (ContentTypeHandlers.TryGetValue(contentType, out handler))
return handler(body);
// ToDo hex output if required
return Encoding.UTF8.GetString(body);
}
public static string RenderJson(byte[] body)
{
var bodyText = Encoding.UTF8.GetString(body);
try
{
var obj = JsonConvert.DeserializeObject(bodyText);
return JsonConvert.SerializeObject(obj, Formatting.Indented);
}
catch
{
return bodyText;
}
}
}
}

View File

@ -6,6 +6,7 @@ namespace PettingZoo.Model
public class MessageInfo
{
public DateTime Timestamp { get; set; }
public string Exchange { get; set; }
public string RoutingKey { get; set; }
public byte[] Body { get; set; }
@ -15,8 +16,8 @@ namespace PettingZoo.Model
{
get
{
return Properties != null && Properties.ContainsKey("content-type")
? Properties["content-type"]
return Properties != null && Properties.ContainsKey(RabbitMQProperties.ContentType)
? Properties[RabbitMQProperties.ContentType]
: "";
}
}

View File

@ -1,50 +1,161 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using PettingZoo.Properties;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
namespace PettingZoo.Model
{
public class RabbitMQClientConnection : IConnection
{
private readonly CancellationTokenSource timer;
private readonly CancellationTokenSource connectionTaskToken;
private RabbitMQ.Client.IConnection connection;
private IModel model;
public event EventHandler<MessageReceivedEventArgs> MessageReceived;
public RabbitMQClientConnection(ConnectionInfo connectionInfo)
{
timer = new CancellationTokenSource();
var token = timer.Token;
connectionTaskToken = new CancellationTokenSource();
var connectionToken = connectionTaskToken.Token;
Task.Run(() =>
{
while (true)
{
if (token.IsCancellationRequested)
break;
if (MessageReceived != null)
MessageReceived(null, new MessageReceivedEventArgs(new MessageInfo
{
RoutingKey = "test",
Body = Encoding.UTF8.GetBytes("{ \"hello\": \"world\" }"),
Properties = new Dictionary<string, string>
{
{ "content-type", "application/json" },
{ "classType", "LEF.Messaging.Internal.ActieNewMessage" }
}
}));
Thread.Sleep(200);
}
}, token);
Task.Factory.StartNew(() => TryConnection(connectionInfo, connectionToken), connectionToken);
}
public void Dispose()
{
timer.Cancel();
connectionTaskToken.Cancel();
if (model != null)
{
model.Dispose();
model = null;
}
if (connection != null)
{
connection.Dispose();
connection = null;
}
}
public event EventHandler<MessageReceivedEventArgs> MessageReceived;
private void TryConnection(ConnectionInfo connectionInfo, CancellationToken cancellationToken)
{
var factory = new ConnectionFactory
{
HostName = connectionInfo.Host,
Port = connectionInfo.Port,
VirtualHost = connectionInfo.VirtualHost,
UserName = connectionInfo.Username,
Password = connectionInfo.Password
};
// ToDo exception handling
connection = factory.CreateConnection();
model = connection.CreateModel();
var queueName = model.QueueDeclare().QueueName;
model.QueueBind(queueName, connectionInfo.Exchange, connectionInfo.RoutingKey);
var consumer = new EventingBasicConsumer(model);
consumer.Received += ClientReceived;
model.BasicConsume(queueName, true, consumer);
}
private void ClientReceived(object sender, BasicDeliverEventArgs args)
{
if (MessageReceived == null)
return;
MessageReceived(this, new MessageReceivedEventArgs(
new MessageInfo
{
Exchange = args.Exchange,
RoutingKey = args.RoutingKey,
Body = args.Body,
Properties = ConvertProperties(args.BasicProperties)
}
));
}
private static Dictionary<string, string> ConvertProperties(IBasicProperties basicProperties)
{
var properties = new Dictionary<string, string>();
if (basicProperties.IsDeliveryModePresent())
{
string deliveryMode;
switch (basicProperties.DeliveryMode)
{
case 1:
deliveryMode = Resources.DeliveryModeNonPersistent;
break;
case 2:
deliveryMode = Resources.DeliveryModePersistent;
break;
default:
deliveryMode = basicProperties.DeliveryMode.ToString(CultureInfo.InvariantCulture);
break;
}
properties.Add(RabbitMQProperties.DeliveryMode, deliveryMode);
}
if (basicProperties.IsContentTypePresent())
properties.Add(RabbitMQProperties.ContentType, basicProperties.ContentType);
if (basicProperties.IsContentEncodingPresent())
properties.Add(RabbitMQProperties.ContentEncoding, basicProperties.ContentEncoding);
if (basicProperties.IsPriorityPresent())
properties.Add(RabbitMQProperties.Priority, basicProperties.Priority.ToString(CultureInfo.InvariantCulture));
if (basicProperties.IsCorrelationIdPresent())
properties.Add(RabbitMQProperties.Priority, basicProperties.CorrelationId);
if (basicProperties.IsReplyToPresent())
properties.Add(RabbitMQProperties.ReplyTo, basicProperties.ReplyTo);
if (basicProperties.IsExpirationPresent())
properties.Add(RabbitMQProperties.Expiration, basicProperties.Expiration);
if (basicProperties.IsMessageIdPresent())
properties.Add(RabbitMQProperties.MessageId, basicProperties.MessageId);
if (basicProperties.IsTimestampPresent())
properties.Add(RabbitMQProperties.Timestamp, basicProperties.Timestamp.UnixTime.ToString(CultureInfo.InvariantCulture));
if (basicProperties.IsTypePresent())
properties.Add(RabbitMQProperties.Type, basicProperties.Type);
if (basicProperties.IsUserIdPresent())
properties.Add(RabbitMQProperties.UserId, basicProperties.UserId);
if (basicProperties.IsAppIdPresent())
properties.Add(RabbitMQProperties.UserId, basicProperties.AppId);
if (basicProperties.IsClusterIdPresent())
properties.Add(RabbitMQProperties.ClusterId, basicProperties.ClusterId);
foreach (var header in basicProperties.Headers)
properties.Add(header.Key, Encoding.UTF8.GetString((byte[])header.Value));
return properties;
}
}
}

View File

@ -0,0 +1,19 @@
namespace PettingZoo.Model
{
static class RabbitMQProperties
{
public const string ContentType = "content-type";
public const string ContentEncoding = "content-encoding";
public const string DeliveryMode = "delivery-mode";
public const string Priority = "priority";
public const string CorrelationId = "correlation-id";
public const string ReplyTo = "reply-to";
public const string Expiration = "expiration";
public const string MessageId = "message-id";
public const string Timestamp = "timestamp";
public const string Type = "type";
public const string UserId = "user-id";
public const string AppId = "app-id";
public const string ClusterId = "cluster-id";
}
}

View File

@ -53,11 +53,22 @@
<StartupObject>
</StartupObject>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>PettingZoo.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Reference Include="AutoMapper, Version=4.2.1.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005, processorArchitecture=MSIL">
<HintPath>packages\AutoMapper.4.2.1\lib\net45\AutoMapper.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="RabbitMQ.Client, Version=3.6.2.0, Culture=neutral, PublicKeyToken=89e7d7c5feba84ce, processorArchitecture=MSIL">
<HintPath>packages\RabbitMQ.Client.3.6.2\lib\net45\RabbitMQ.Client.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="SimpleInjector, Version=3.1.5.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
<HintPath>packages\SimpleInjector.3.1.5\lib\net45\SimpleInjector.dll</HintPath>
<Private>True</Private>
@ -110,6 +121,7 @@
<Compile Include="Model\MessageInfo.cs" />
<Compile Include="Model\RabbitMQClientConnection.cs" />
<Compile Include="Model\RabbitMQClientConnectionFactory.cs" />
<Compile Include="Model\RabbitMQProperties.cs" />
<Compile Include="ViewModel\ConnectionViewModel.cs" />
<Compile Include="ViewModel\MainViewModel.cs" />
<Compile Include="View\ConnectionWindow.xaml.cs">
@ -165,6 +177,13 @@
<Install>false</Install>
</BootstrapperPackage>
</ItemGroup>
<ItemGroup>
<Resource Include="Images\Connect.png" />
<Resource Include="Images\Disconnect.png" />
</ItemGroup>
<ItemGroup>
<Resource Include="PettingZoo.ico" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

BIN
PettingZoo.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -9,9 +9,6 @@
//------------------------------------------------------------------------------
namespace PettingZoo.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
@ -150,6 +147,24 @@ namespace PettingZoo.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to 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 Petting Zoo - a RabbitMQ Message Viewer.
/// </summary>

View File

@ -147,6 +147,12 @@
<data name="ConnectionWindowTitle" xml:space="preserve">
<value>Connection parameters</value>
</data>
<data name="DeliveryModeNonPersistent" xml:space="preserve">
<value>Non-persistent</value>
</data>
<data name="DeliveryModePersistent" xml:space="preserve">
<value>Persistent</value>
</data>
<data name="MainWindowTitle" xml:space="preserve">
<value>Petting Zoo - a RabbitMQ Message Viewer</value>
</data>

View File

@ -40,4 +40,25 @@
<Setter Property="HorizontalGridLinesBrush" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
<Setter Property="VerticalGridLinesBrush" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
</Style>
<Style x:Key="ToolbarIcon" TargetType="{x:Type Image}">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value="0.25"/>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="Timestamp" TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}, Path=IsSelected}" Value="False">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="Routingkey" TargetType="{x:Type TextBlock}">
</Style>
</ResourceDictionary>

View File

@ -15,10 +15,25 @@
Title="{x:Static res:Resources.MainWindowTitle}">
<DockPanel>
<ToolBar DockPanel.Dock="Top" ToolBarTray.IsLocked="True">
<Button Command="{Binding ConnectCommand}" Content="Connect"></Button>
<Button Command="{Binding DisconnectCommand}" Content="Disconnect"></Button>
<Button Command="{Binding ConnectCommand}">
<StackPanel Orientation="Horizontal">
<Image Source="/Images/Connect.png" Width="16" Height="16" Style="{StaticResource ToolbarIcon}"/>
<TextBlock Margin="3,0,0,0">Connect</TextBlock>
</StackPanel>
</Button>
<Button Command="{Binding DisconnectCommand}">
<StackPanel Orientation="Horizontal">
<Image Source="/Images/Disconnect.png" Width="16" Height="16" Style="{StaticResource ToolbarIcon}"/>
<TextBlock Margin="3,0,0,0">Disconnect</TextBlock>
</StackPanel>
</Button>
<Separator Style="{StaticResource {x:Static ToolBar.SeparatorStyleKey}}" />
<Button Command="{Binding ClearCommand}" Content="Clear messages"></Button>
<Button Command="{Binding ClearCommand}">
<StackPanel Orientation="Horizontal">
<Image Source="/Images/Disconnect.png" Width="16" Height="16" Style="{StaticResource ToolbarIcon}"/>
<TextBlock Margin="3,0,0,0">Clear messages</TextBlock>
</StackPanel>
</Button>
</ToolBar>
<StatusBar DockPanel.Dock="Bottom">
<StatusBarItem>
@ -43,8 +58,8 @@
<ColumnDefinition Width="Auto" MinWidth="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Text="{Binding Timestamp}"></TextBlock>
<TextBlock Grid.Column="1" Grid.Row="0" Text="{Binding RoutingKey}"></TextBlock>
<TextBlock Grid.Column="0" Grid.Row="0" Text="{Binding Timestamp, StringFormat=g}" Style="{StaticResource Timestamp}"></TextBlock>
<TextBlock Grid.Column="1" Grid.Row="0" Text="{Binding RoutingKey}" Style="{StaticResource Routingkey}"></TextBlock>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>

View File

@ -1,4 +1,5 @@
using System.Windows;
using System;
using System.Windows;
using PettingZoo.ViewModel;
namespace PettingZoo.View
@ -11,6 +12,16 @@ namespace PettingZoo.View
InitializeComponent();
DataContext = viewModel;
Dispatcher.ShutdownStarted += OnDispatcherShutDownStarted;
}
private void OnDispatcherShutDownStarted(object sender, EventArgs e)
{
var disposable = DataContext as IDisposable;
if (!ReferenceEquals(null, disposable))
disposable.Dispose();
}
}
}

View File

@ -9,7 +9,7 @@ using PettingZoo.Model;
namespace PettingZoo.ViewModel
{
public class MainViewModel : BaseViewModel
public class MainViewModel : BaseViewModel, IDisposable
{
private readonly TaskScheduler uiScheduler;
private readonly IConnectionInfoBuilder connectionInfoBuilder;
@ -89,6 +89,16 @@ namespace PettingZoo.ViewModel
}
public void Dispose()
{
if (connection != null)
{
connection.Dispose();
connection = null;
}
}
private void ConnectExecute()
{
var newInfo = connectionInfoBuilder.Build();

View File

@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="AutoMapper" version="4.2.1" targetFramework="net45" />
<package id="Newtonsoft.Json" version="8.0.3" targetFramework="net45" />
<package id="RabbitMQ.Client" version="3.6.2" targetFramework="net45" />
<package id="SimpleInjector" version="3.1.5" targetFramework="net45" />
</packages>