├── .gitignore ├── ColorChat.Domain ├── ColorChat.Domain.csproj └── Models │ └── ColorChatColor.cs ├── ColorChat.SignalR ├── Authentication │ └── NameTokenValidator.cs ├── ColorChat.SignalR.csproj ├── Hubs │ └── ColorChatHub.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── Startup.cs └── appsettings.Development.json ├── ColorChat.WPF ├── App.xaml ├── App.xaml.cs ├── AssemblyInfo.cs ├── ColorChat.WPF.csproj ├── Commands │ └── SendColorChatColorMessageCommand.cs ├── Components │ ├── ColorPicker.xaml │ └── ColorPicker.xaml.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── Services │ └── SignalRChatService.cs ├── ViewModels │ ├── ColorChatColorViewModel.cs │ ├── ColorChatViewModel.cs │ ├── MainViewModel.cs │ └── ViewModelBase.cs └── Views │ ├── ColorChatView.xaml │ └── ColorChatView.xaml.cs ├── ColorChat.sln └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | obj/ 2 | bin/ 3 | .vs/ 4 | *.user 5 | appsettings.json 6 | -------------------------------------------------------------------------------- /ColorChat.Domain/ColorChat.Domain.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ColorChat.Domain/Models/ColorChatColor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace ColorChat.Domain.Models 6 | { 7 | public class ColorChatColor 8 | { 9 | public byte Red { get; set; } 10 | public byte Green { get; set; } 11 | public byte Blue { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ColorChat.SignalR/Authentication/NameTokenValidator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authentication.JwtBearer; 2 | using Microsoft.IdentityModel.Tokens; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Security.Claims; 7 | using System.Threading.Tasks; 8 | 9 | namespace ColorChat.SignalR.Authentication 10 | { 11 | public class NameTokenValidator : ISecurityTokenValidator 12 | { 13 | public bool CanValidateToken { get; } = true; 14 | public int MaximumTokenSizeInBytes { get; set; } 15 | 16 | public bool CanReadToken(string securityToken) 17 | { 18 | return true; 19 | } 20 | 21 | public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken) 22 | { 23 | validatedToken = null; 24 | 25 | string name = securityToken; 26 | 27 | return new ClaimsPrincipal(new List 28 | { 29 | new ClaimsIdentity(new List 30 | { 31 | new Claim(ClaimTypes.Name, name) 32 | }) 33 | }); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ColorChat.SignalR/ColorChat.SignalR.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /ColorChat.SignalR/Hubs/ColorChatHub.cs: -------------------------------------------------------------------------------- 1 | using ColorChat.Domain.Models; 2 | using Microsoft.AspNetCore.SignalR; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace ColorChat.SignalR.Hubs 9 | { 10 | public class ColorChatHub : Hub 11 | { 12 | public async Task SendColorMessage(ColorChatColor color) 13 | { 14 | await Clients.All.SendAsync("ReceiveColorMessage", color); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ColorChat.SignalR/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.Hosting; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace ColorChat.SignalR 11 | { 12 | public class Program 13 | { 14 | public static void Main(string[] args) 15 | { 16 | CreateHostBuilder(args).Build().Run(); 17 | } 18 | 19 | public static IHostBuilder CreateHostBuilder(string[] args) => 20 | Host.CreateDefaultBuilder(args) 21 | .ConfigureWebHostDefaults(webBuilder => 22 | { 23 | webBuilder.UseStartup(); 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ColorChat.SignalR/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:52865", 7 | "sslPort": 44377 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "ColorChat.SignalR": { 19 | "commandName": "Project", 20 | "environmentVariables": { 21 | "ASPNETCORE_ENVIRONMENT": "Development" 22 | }, 23 | "applicationUrl": "https://localhost:5001;http://localhost:5000" 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /ColorChat.SignalR/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Security.Claims; 5 | using System.Threading.Tasks; 6 | using ColorChat.SignalR.Authentication; 7 | using ColorChat.SignalR.Hubs; 8 | using Microsoft.AspNetCore.Authentication.JwtBearer; 9 | using Microsoft.AspNetCore.Authorization; 10 | using Microsoft.AspNetCore.Builder; 11 | using Microsoft.AspNetCore.Hosting; 12 | using Microsoft.AspNetCore.Http; 13 | using Microsoft.Extensions.DependencyInjection; 14 | using Microsoft.Extensions.Hosting; 15 | 16 | namespace ColorChat.SignalR 17 | { 18 | public class Startup 19 | { 20 | public void ConfigureServices(IServiceCollection services) 21 | { 22 | services.AddAuthentication(o => 23 | { 24 | o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; 25 | o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; 26 | }) 27 | .AddJwtBearer(o => 28 | { 29 | o.SecurityTokenValidators.Clear(); 30 | o.SecurityTokenValidators.Add(new NameTokenValidator()); 31 | }); 32 | 33 | services.AddAuthorization(o => 34 | { 35 | o.DefaultPolicy = new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme) 36 | .RequireClaim(ClaimTypes.Name) 37 | .Build(); 38 | }); 39 | 40 | services.AddSignalR(); 41 | } 42 | 43 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 44 | { 45 | if (env.IsDevelopment()) 46 | { 47 | app.UseDeveloperExceptionPage(); 48 | } 49 | 50 | app.UseRouting(); 51 | 52 | app.UseAuthentication(); 53 | app.UseAuthorization(); 54 | 55 | app.UseEndpoints(endpoints => 56 | { 57 | endpoints.MapHub("/colorchat"); 58 | }); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /ColorChat.SignalR/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ColorChat.WPF/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | -------------------------------------------------------------------------------- /ColorChat.WPF/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using ColorChat.WPF.Services; 2 | using ColorChat.WPF.ViewModels; 3 | using Microsoft.AspNetCore.SignalR.Client; 4 | using System.Windows; 5 | 6 | namespace ColorChat.WPF 7 | { 8 | public partial class App : Application 9 | { 10 | protected override void OnStartup(StartupEventArgs e) 11 | { 12 | HubConnection connection = new HubConnectionBuilder() 13 | .WithUrl("http://localhost:5000/colorchat") 14 | .Build(); 15 | 16 | ColorChatViewModel chatViewModel = ColorChatViewModel.CreatedConnectedViewModel(new SignalRChatService(connection)); 17 | 18 | MainWindow window = new MainWindow 19 | { 20 | DataContext = new MainViewModel(chatViewModel) 21 | }; 22 | 23 | window.Show(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ColorChat.WPF/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly: ThemeInfo( 4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 5 | //(used if a resource is not found in the page, 6 | // or application resource dictionaries) 7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 8 | //(used if a resource is not found in the page, 9 | // app, or any theme specific resource dictionaries) 10 | )] 11 | -------------------------------------------------------------------------------- /ColorChat.WPF/ColorChat.WPF.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | netcoreapp3.1 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /ColorChat.WPF/Commands/SendColorChatColorMessageCommand.cs: -------------------------------------------------------------------------------- 1 | using ColorChat.Domain.Models; 2 | using ColorChat.WPF.Services; 3 | using ColorChat.WPF.ViewModels; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Runtime.InteropServices.WindowsRuntime; 7 | using System.Text; 8 | using System.Windows.Input; 9 | 10 | namespace ColorChat.WPF.Commands 11 | { 12 | public class SendColorChatColorMessageCommand : ICommand 13 | { 14 | private readonly ColorChatViewModel _viewModel; 15 | private readonly SignalRChatService _chatService; 16 | 17 | public SendColorChatColorMessageCommand(ColorChatViewModel viewModel, SignalRChatService chatService) 18 | { 19 | _viewModel = viewModel; 20 | _chatService = chatService; 21 | } 22 | 23 | public event EventHandler CanExecuteChanged; 24 | 25 | public bool CanExecute(object parameter) 26 | { 27 | return true; 28 | } 29 | 30 | public async void Execute(object parameter) 31 | { 32 | try 33 | { 34 | await _chatService.SendColorMessage(new ColorChatColor() 35 | { 36 | Red = _viewModel.Red, 37 | Green = _viewModel.Green, 38 | Blue = _viewModel.Blue, 39 | }); 40 | 41 | _viewModel.ErrorMessage = string.Empty; 42 | } 43 | catch (Exception) 44 | { 45 | _viewModel.ErrorMessage = "Unable to send color message."; 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ColorChat.WPF/Components/ColorPicker.xaml: -------------------------------------------------------------------------------- 1 |  10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 37 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 56 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 75 | 78 | 79 | 80 | 81 | 82 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /ColorChat.WPF/Components/ColorPicker.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Windows; 5 | using System.Windows.Controls; 6 | using System.Windows.Data; 7 | using System.Windows.Documents; 8 | using System.Windows.Input; 9 | using System.Windows.Media; 10 | using System.Windows.Media.Imaging; 11 | using System.Windows.Navigation; 12 | using System.Windows.Shapes; 13 | 14 | namespace ColorChat.WPF.Components 15 | { 16 | /// 17 | /// Interaction logic for ColorPicker.xaml 18 | /// 19 | public partial class ColorPicker : UserControl 20 | { 21 | public static readonly DependencyProperty RedProperty = 22 | DependencyProperty.Register(nameof(Red), typeof(byte), typeof(ColorPicker), 23 | new FrameworkPropertyMetadata((byte)0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, RGBPropertyChanged)); 24 | 25 | public static readonly DependencyProperty GreenProperty = 26 | DependencyProperty.Register(nameof(Green), typeof(byte), typeof(ColorPicker), 27 | new FrameworkPropertyMetadata((byte)0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, RGBPropertyChanged)); 28 | 29 | public static readonly DependencyProperty BlueProperty = 30 | DependencyProperty.Register(nameof(Blue), typeof(byte), typeof(ColorPicker), 31 | new FrameworkPropertyMetadata((byte)0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, RGBPropertyChanged)); 32 | 33 | public static readonly DependencyProperty ColorBrushProperty = 34 | DependencyProperty.Register(nameof(ColorBrush), typeof(Brush), typeof(ColorPicker), 35 | new FrameworkPropertyMetadata(new SolidColorBrush(Colors.Black))); 36 | 37 | public byte Red 38 | { 39 | get { return (byte)GetValue(RedProperty); } 40 | set { SetValue(RedProperty, value); } 41 | } 42 | public byte Green 43 | { 44 | get { return (byte)GetValue(GreenProperty); } 45 | set { SetValue(GreenProperty, value); } 46 | } 47 | 48 | public byte Blue 49 | { 50 | get { return (byte)GetValue(BlueProperty); } 51 | set { SetValue(BlueProperty, value); } 52 | } 53 | 54 | public Brush ColorBrush 55 | { 56 | get { return (Brush)GetValue(ColorBrushProperty); } 57 | set { SetValue(ColorBrushProperty, value); } 58 | } 59 | 60 | public ColorPicker() 61 | { 62 | InitializeComponent(); 63 | } 64 | 65 | private static void RGBPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 66 | { 67 | if(d is ColorPicker colorPicker) 68 | { 69 | colorPicker.UpdateColorBrush(); 70 | } 71 | } 72 | 73 | private void UpdateColorBrush() 74 | { 75 | ColorBrush = new SolidColorBrush(Color.FromRgb(Red, Green, Blue)); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /ColorChat.WPF/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ColorChat.WPF/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Data; 9 | using System.Windows.Documents; 10 | using System.Windows.Input; 11 | using System.Windows.Media; 12 | using System.Windows.Media.Imaging; 13 | using System.Windows.Navigation; 14 | using System.Windows.Shapes; 15 | 16 | namespace ColorChat.WPF 17 | { 18 | /// 19 | /// Interaction logic for MainWindow.xaml 20 | /// 21 | public partial class MainWindow : Window 22 | { 23 | public MainWindow() 24 | { 25 | InitializeComponent(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ColorChat.WPF/Services/SignalRChatService.cs: -------------------------------------------------------------------------------- 1 | using ColorChat.Domain.Models; 2 | using Microsoft.AspNetCore.SignalR.Client; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace ColorChat.WPF.Services 9 | { 10 | public class SignalRChatService 11 | { 12 | private readonly HubConnection _connection; 13 | 14 | public event Action ColorMessageReceived; 15 | 16 | public SignalRChatService(HubConnection connection) 17 | { 18 | _connection = connection; 19 | 20 | _connection.On("ReceiveColorMessage", (color) => ColorMessageReceived?.Invoke(color)); 21 | } 22 | 23 | public async Task Connect() 24 | { 25 | await _connection.StartAsync(); 26 | } 27 | 28 | public async Task SendColorMessage(ColorChatColor color) 29 | { 30 | await _connection.SendAsync("SendColorMessage", color); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ColorChat.WPF/ViewModels/ColorChatColorViewModel.cs: -------------------------------------------------------------------------------- 1 | using ColorChat.Domain.Models; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Windows.Media; 6 | 7 | namespace ColorChat.WPF.ViewModels 8 | { 9 | public class ColorChatColorViewModel : ViewModelBase 10 | { 11 | public ColorChatColor ColorChatColor { get; set; } 12 | 13 | public Brush ColorBrush 14 | { 15 | get 16 | { 17 | try 18 | { 19 | return new SolidColorBrush(Color.FromRgb( 20 | ColorChatColor.Red, 21 | ColorChatColor.Green, 22 | ColorChatColor.Blue)); 23 | } 24 | catch (FormatException) 25 | { 26 | return new SolidColorBrush(Colors.Black); 27 | } 28 | } 29 | } 30 | 31 | public ColorChatColorViewModel(ColorChatColor colorChatColor) 32 | { 33 | ColorChatColor = colorChatColor; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ColorChat.WPF/ViewModels/ColorChatViewModel.cs: -------------------------------------------------------------------------------- 1 | using ColorChat.Domain.Models; 2 | using ColorChat.WPF.Commands; 3 | using ColorChat.WPF.Services; 4 | using System.Collections.ObjectModel; 5 | using System.Drawing; 6 | using System.Windows.Input; 7 | 8 | namespace ColorChat.WPF.ViewModels 9 | { 10 | public class ColorChatViewModel : ViewModelBase 11 | { 12 | private byte _red; 13 | public byte Red 14 | { 15 | get 16 | { 17 | return _red; 18 | } 19 | set 20 | { 21 | _red = value; 22 | OnPropertyChanged(nameof(Red)); 23 | } 24 | } 25 | 26 | private byte _green; 27 | public byte Green 28 | { 29 | get 30 | { 31 | return _green; 32 | } 33 | set 34 | { 35 | _green = value; 36 | OnPropertyChanged(nameof(Green)); 37 | } 38 | } 39 | 40 | private byte _blue; 41 | public byte Blue 42 | { 43 | get 44 | { 45 | return _blue; 46 | } 47 | set 48 | { 49 | _blue = value; 50 | OnPropertyChanged(nameof(Blue)); 51 | } 52 | } 53 | 54 | private string _errorMessage = string.Empty; 55 | public string ErrorMessage 56 | { 57 | get 58 | { 59 | return _errorMessage; 60 | } 61 | set 62 | { 63 | _errorMessage = value; 64 | OnPropertyChanged(nameof(ErrorMessage)); 65 | OnPropertyChanged(nameof(HasErrorMessage)); 66 | } 67 | } 68 | 69 | public bool HasErrorMessage => !string.IsNullOrEmpty(ErrorMessage); 70 | 71 | private bool _isConnected; 72 | public bool IsConnected 73 | { 74 | get 75 | { 76 | return _isConnected; 77 | } 78 | set 79 | { 80 | _isConnected = value; 81 | OnPropertyChanged(nameof(IsConnected)); 82 | } 83 | } 84 | 85 | public ObservableCollection Messages { get; } 86 | 87 | public ICommand SendColorChatColorMessageCommand { get; } 88 | 89 | public ColorChatViewModel(SignalRChatService chatService) 90 | { 91 | SendColorChatColorMessageCommand = new SendColorChatColorMessageCommand(this, chatService); 92 | 93 | Messages = new ObservableCollection(); 94 | 95 | chatService.ColorMessageReceived += ChatService_ColorMessageReceived; 96 | } 97 | 98 | public static ColorChatViewModel CreatedConnectedViewModel(SignalRChatService chatService) 99 | { 100 | ColorChatViewModel viewModel = new ColorChatViewModel(chatService); 101 | 102 | chatService.Connect().ContinueWith(task => 103 | { 104 | if(task.Exception != null) 105 | { 106 | viewModel.ErrorMessage = "Unable to connect to color chat hub"; 107 | } 108 | }); 109 | 110 | return viewModel; 111 | } 112 | 113 | private void ChatService_ColorMessageReceived(ColorChatColor color) 114 | { 115 | Messages.Add(new ColorChatColorViewModel(color)); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /ColorChat.WPF/ViewModels/MainViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace ColorChat.WPF.ViewModels 2 | { 3 | public class MainViewModel 4 | { 5 | public ColorChatViewModel ColorChatViewModel { get; } 6 | 7 | public MainViewModel(ColorChatViewModel chatViewModel) 8 | { 9 | ColorChatViewModel = chatViewModel; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ColorChat.WPF/ViewModels/ViewModelBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Runtime.CompilerServices; 5 | using System.Text; 6 | 7 | namespace ColorChat.WPF.ViewModels 8 | { 9 | public class ViewModelBase : INotifyPropertyChanged 10 | { 11 | public event PropertyChangedEventHandler PropertyChanged; 12 | 13 | protected void OnPropertyChanged([CallerMemberName] string propertyName = null) 14 | { 15 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ColorChat.WPF/Views/ColorChatView.xaml: -------------------------------------------------------------------------------- 1 |  14 | 15 | 16 | 17 | 22 | 23 | 24 | 28 | 29 | 36 | 37 | 38 | 39 | 40 | 41 | 44 | 45 | 49 | 50 |