├── .editorconfig ├── .gitignore ├── LICENSE ├── NuGet.Config ├── README.md ├── azure-pipelines.yml ├── icon ├── 128.png ├── 200.png ├── 32.png ├── 48.png ├── 64.png ├── Merq.ico └── icon.png ├── lib └── Microsoft.ComponentModel.Composition.Diagnostics.dll ├── msbuild.rsp └── src ├── Core ├── Merq.Core.Tests │ ├── CommandBusSpec.cs │ ├── EventStreamSamples.cs │ ├── EventStreamSpec.cs │ └── Merq.Core.Tests.csproj ├── Merq.Core │ ├── CommandBus.cs │ ├── EventStream.cs │ ├── Merq.Core.csproj │ └── Properties │ │ ├── Resources.Designer.cs │ │ └── Resources.resx └── Merq │ ├── GitInfo.txt │ ├── IAsyncCommand.cs │ ├── IAsyncCommandHandler.cs │ ├── ICanExecute.cs │ ├── ICommand.cs │ ├── ICommandBus.cs │ ├── ICommandHandler.cs │ ├── IEventStream.cs │ ├── IEventStreamExtensions.cs │ ├── IExecutable.cs │ ├── IExecutableCommandHandler.cs │ ├── Merq.csproj │ └── Merq.targets ├── Directory.Build.props ├── Directory.Build.targets ├── GitInfo.txt ├── GlobalAssemblyInfo.cs ├── Installers ├── Merq.Installer.props ├── Merq.Installer.targets ├── Merq.Lib │ ├── Directories.wxs │ ├── Library.wxs │ ├── Merq.Lib.wixproj │ └── packages.config ├── Merq.Msi │ ├── Merq.Msi.targets │ ├── Merq.Msi.wixproj │ ├── Merq.wxi │ ├── Product.wxs │ └── packages.config ├── SampleBundle │ ├── Bundle.wxs │ ├── Resources │ │ ├── 1033 │ │ │ └── ClassicTheme.wxl │ │ ├── 3082 │ │ │ └── ClassicTheme.wxl │ │ ├── ClassicTheme.wxl │ │ ├── ClassicTheme.xml │ │ ├── banner.bmp │ │ └── banner.pdn │ ├── SampleBundle.wixproj │ └── packages.config ├── SampleExtension │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── SampleExtension.csproj │ ├── project.json │ └── source.extension.vsixmanifest └── build │ ├── Merq.VisualStudio.WiX.targets │ ├── Merq.VisualStudio.props │ ├── Merq.VisualStudio.targets │ └── Readme.txt ├── Merq.VisualStudio.proj ├── Merq.key ├── Merq.props ├── Merq.sln ├── Merq.snk ├── Merq.vssettings ├── StaFact.cs ├── Version.targets ├── Vsix ├── Merq.Vsix.IntegrationTests │ ├── ComponentsSpec.cs │ └── Merq.Vsix.IntegrationTests.csproj ├── Merq.Vsix.Tests │ ├── CommandBusComponentSpec.cs │ ├── EventStreamComponentSpec.cs │ ├── GlobalSuppressions.cs │ └── Merq.Vsix.Tests.csproj └── Merq.Vsix │ ├── Components │ ├── CommandBusComponent.cs │ ├── DefaultExportProvider.cs │ └── EventStreamComponent.cs │ ├── Merq.Vsix.csproj │ ├── Merq.Vsix.props │ ├── Merq.Vsix.targets │ ├── MerqPackage.cs │ ├── MerqPackage.resx │ ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ └── Resources.resx │ └── source.extension.vsixmanifest └── _._ /.editorconfig: -------------------------------------------------------------------------------- 1 | ; EditorConfig to support per-solution formatting. 2 | ; Use the EditorConfig VS add-in to make this work. 3 | ; http://editorconfig.org/ 4 | 5 | ; This is the default for the codeline. 6 | root = true 7 | 8 | [*] 9 | end_of_line = CRLF 10 | 11 | [*.{cs,txt,md}] 12 | indent_style = tab 13 | indent_size = 4 14 | 15 | [*.{sln,proj,props,targets,xml,config,nuspec}] 16 | indent_style = tab 17 | indent_size = 4 18 | 19 | [*.{csproj,resx}] 20 | indent_style = space 21 | indent_size = 2 22 | 23 | [*.yaml] 24 | indent_style = space 25 | indent_size = 2 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .nuget 2 | out 3 | bin 4 | obj 5 | packages 6 | *.nuget.targets 7 | *.nuget.props 8 | *.lock.json 9 | *.suo 10 | *.user 11 | *.cache 12 | .vs 13 | .* 14 | log.txt 15 | *.binlog 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Mobile Essentials 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /NuGet.Config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Icon](https://raw.github.com/MobileEssentials/Merq/master/icon/32.png) Merq 2 | ================ 3 | 4 | > **Mercury:** messenger of the Roman gods 5 | 6 | > *Mercury* > *Merq-ry* > **Merq** 7 | 8 | Internal application architecture based on commands and events represented as 9 | messages in a command bus and an event stream respectively, with support for 10 | asynchronously executing commands in a main thread deadlock-free way. 11 | 12 | [![Build status](https://devdiv.visualstudio.com/_apis/public/build/definitions/0bdbc590-a062-4c3f-b0f6-9383f67865ee/8887/badge)](http://build.devdiv.io/8887) 13 | [![Coverage Status](https://coveralls.io/repos/github/MobileEssentials/Merq/badge.svg?branch=master)](https://coveralls.io/github/MobileEssentials/Merq?branch=master) 14 | [![Latest version](https://img.shields.io/nuget/v/Merq.svg)](https://www.nuget.org/packages/Merq) 15 | [![Join the chat at https://gitter.im/MobileEssentials](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/MobileEssentials?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 16 | [![License](https://img.shields.io/github/license/MobileEssentials/Merq.svg)](https://github.com/MobileEssentials/Merq/blob/master/LICENSE) 17 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | name: merq 2 | 3 | trigger: 4 | batch: false 5 | branches: 6 | include: 7 | - master 8 | - dev/* 9 | - feature/* 10 | - rel/* 11 | paths: 12 | exclude: 13 | - docs 14 | - icon 15 | 16 | variables: 17 | - group: Xamarin Release 18 | - group: Xamarin-Secrets 19 | - name: Configuration 20 | value: Release 21 | - name: DotNetVersion 22 | value: 3.1.x 23 | - name: PackageOutputPath 24 | value: $(Build.ArtifactStagingDirectory)/package 25 | - name: System.Debug 26 | value: true 27 | 28 | resources: 29 | repositories: 30 | - repository: templates 31 | type: github 32 | name: xamarin/yaml-templates 33 | ref: refs/heads/master 34 | endpoint: xamarin 35 | 36 | stages: 37 | - stage: Windows 38 | jobs: 39 | - job: Build 40 | pool: VSEng-MicroBuildVS2019 41 | steps: 42 | - checkout: self 43 | clean: true 44 | submodules: recursive 45 | - task: UseDotNet@2 46 | inputs: 47 | version: $(DotNetVersion) 48 | performMultiLevelLookup: true 49 | - script: 'dotnet tool update -g dotnet-format && dotnet format -f $(Build.SourcesDirectory)\src --dry-run --check -v:diag' 50 | displayName: Check .editorconfig compliance 51 | - template: dump-environment.yml@templates 52 | 53 | - task: MSBuild@1 54 | displayName: Build 55 | inputs: 56 | solution: src\Merq.sln 57 | msbuildArguments: '-r -bl:$(Build.ArtifactStagingDirectory)/logs/build.binlog' 58 | 59 | - task: VSTest@2 60 | displayName: 'Test' 61 | timeoutInMinutes: 5 62 | inputs: 63 | testAssemblyVer2: | 64 | **\*Tests.dll 65 | !**\*IntegrationTests.dll 66 | !**\*TestAdapter.dll 67 | !**\obj\** 68 | codeCoverageEnabled: true 69 | runInParallel: false 70 | rerunFailedTests: true 71 | rerunMaxAttempts: 5 72 | 73 | - task: PublishBuildArtifacts@1 74 | displayName: 'Logs' 75 | condition: always() 76 | inputs: 77 | PathtoPublish: '$(Build.ArtifactStagingDirectory)/logs' 78 | ArtifactName: logs 79 | 80 | - task: PublishBuildArtifacts@1 81 | displayName: 'Artifacts' 82 | inputs: 83 | PathtoPublish: '$(Build.ArtifactStagingDirectory)/package' 84 | ArtifactName: package 85 | 86 | - task: PublishBuildArtifacts@1 87 | displayName: 'Symbols' 88 | inputs: 89 | PathtoPublish: '$(Build.ArtifactStagingDirectory)/artifacts' 90 | ArtifactName: symbols 91 | 92 | - stage: Upload 93 | jobs: 94 | - job: Upload 95 | pool: VSEng-MicroBuildVS2019 96 | steps: 97 | - checkout: self 98 | 99 | - task: UseDotNet@2 100 | inputs: 101 | packageType: runtime 102 | version: $(DotNetVersion) 103 | performMultiLevelLookup: true 104 | - script: 'dotnet tool update -g --version 7.0.0 PowerShell >nul || dotnet tool list -g' 105 | displayName: UsePowerShell 106 | 107 | - template: fix-source-version/v2.yml@templates 108 | # This is only needed while we teach the build-tools tasks how to receive overriden variables. 109 | - script: git reset --hard $(GitHub.Commit) 110 | displayName: Align checkout with GitHub.Commit 111 | condition: ne(variables['GitHub.Commit'], variables['Build.SourceVersion']) 112 | 113 | - task: DownloadBuildArtifacts@0 114 | inputs: 115 | artifactName: package 116 | 117 | - template: dump-environment.yml@templates 118 | - template: upload-to-storage/win/v1.yml@templates 119 | parameters: 120 | ArtifactsDirectory: '$(Build.ArtifactStagingDirectory)/package' 121 | Azure.ContainerName: 'xvs-merq' 122 | GitHub.Context: 'artifacts' 123 | 124 | - task: NuGetCommand@2 125 | displayName: Push Packages 126 | continueOnError: true 127 | condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(variables['PushPackages'], 'true'))) 128 | inputs: 129 | command: push 130 | packagesToPush: $(Build.ArtifactStagingDirectory)/package/*.nupkg 131 | nuGetFeedType: external 132 | publishFeedCredentials: 'xamarin-impl public feed' 133 | -------------------------------------------------------------------------------- /icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileEssentials/Merq/34d6af216bf5c72f0bc3fa796607c83ae9a42fae/icon/128.png -------------------------------------------------------------------------------- /icon/200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileEssentials/Merq/34d6af216bf5c72f0bc3fa796607c83ae9a42fae/icon/200.png -------------------------------------------------------------------------------- /icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileEssentials/Merq/34d6af216bf5c72f0bc3fa796607c83ae9a42fae/icon/32.png -------------------------------------------------------------------------------- /icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileEssentials/Merq/34d6af216bf5c72f0bc3fa796607c83ae9a42fae/icon/48.png -------------------------------------------------------------------------------- /icon/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileEssentials/Merq/34d6af216bf5c72f0bc3fa796607c83ae9a42fae/icon/64.png -------------------------------------------------------------------------------- /icon/Merq.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileEssentials/Merq/34d6af216bf5c72f0bc3fa796607c83ae9a42fae/icon/Merq.ico -------------------------------------------------------------------------------- /icon/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileEssentials/Merq/34d6af216bf5c72f0bc3fa796607c83ae9a42fae/icon/icon.png -------------------------------------------------------------------------------- /lib/Microsoft.ComponentModel.Composition.Diagnostics.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileEssentials/Merq/34d6af216bf5c72f0bc3fa796607c83ae9a42fae/lib/Microsoft.ComponentModel.Composition.Diagnostics.dll -------------------------------------------------------------------------------- /msbuild.rsp: -------------------------------------------------------------------------------- 1 | /consoleloggerparameters:Verbosity=minimal 2 | /bl 3 | /v:m 4 | /nr:false 5 | /m -------------------------------------------------------------------------------- /src/Core/Merq.Core.Tests/CommandBusSpec.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using System.Linq; 6 | using Moq; 7 | using Xunit; 8 | 9 | namespace Merq 10 | { 11 | public class CommandBusSpec 12 | { 13 | [Fact] 14 | public void when_registering_non_generic_handler_then_throws() 15 | { 16 | Assert.Throws(() => new CommandBus(Mock.Of())); 17 | } 18 | 19 | [Fact] 20 | public void when_registering_duplicate_handlers_then_throws() 21 | { 22 | Assert.Throws(() => new CommandBus( 23 | Mock.Of>(), 24 | Mock.Of>())); 25 | } 26 | 27 | [Fact] 28 | public void when_executing_command_without_handler_then_throws() 29 | { 30 | var bus = new CommandBus(); 31 | 32 | Assert.Throws(() => bus.Execute(new Command())); 33 | } 34 | 35 | [Fact] 36 | public void when_executing_command_with_result_without_handler_then_throws() 37 | { 38 | var bus = new CommandBus(); 39 | 40 | Assert.Throws(() => bus.Execute(new CommandWithResult())); 41 | } 42 | 43 | [Fact] 44 | public async void when_executing_async_command_without_handler_then_throws() 45 | { 46 | var bus = new CommandBus(); 47 | 48 | await Assert.ThrowsAsync(() => bus.ExecuteAsync(new AsyncCommand(), CancellationToken.None)); 49 | } 50 | 51 | [Fact] 52 | public async void when_executing_async_command_with_result_without_handler_then_throws() 53 | { 54 | var bus = new CommandBus(); 55 | 56 | await Assert.ThrowsAsync(() => bus.ExecuteAsync(new AsyncCommandWithResult(), CancellationToken.None)); 57 | } 58 | 59 | [Fact] 60 | public void when_can_handle_requested_for_non_registered_handler_then_returns_false() 61 | { 62 | var bus = new CommandBus(); 63 | 64 | Assert.False(bus.CanHandle()); 65 | } 66 | 67 | [Fact] 68 | public void when_can_handle_requested_for_registered_handler_type_then_returns_true() 69 | { 70 | var bus = new CommandBus(Mock.Of>()); 71 | 72 | Assert.True(bus.CanHandle()); 73 | } 74 | 75 | [Fact] 76 | public void when_can_handle_requested_for_registered_handler_instance_then_returns_true() 77 | { 78 | var bus = new CommandBus(Mock.Of>()); 79 | 80 | Assert.True(bus.CanHandle(new Command())); 81 | } 82 | 83 | [Fact] 84 | public void when_can_handle_requested_for_null_command_then_returns_false() 85 | { 86 | var bus = new CommandBus(Mock.Of>()); 87 | 88 | Assert.False(bus.CanHandle((Command)null)); 89 | } 90 | 91 | [Fact] 92 | public void when_can_execute_requested_and_no_handler_registered_then_returns_false() 93 | { 94 | var bus = new CommandBus(); 95 | 96 | Assert.False(bus.CanExecute(new Command())); 97 | } 98 | 99 | [Fact] 100 | public void when_can_execute_requested_then_invokes_sync_handler() 101 | { 102 | var command = new Command(); 103 | var bus = new CommandBus(Mock.Of>(c => c.CanExecute(command) == true)); 104 | 105 | Assert.True(bus.CanExecute(command)); 106 | } 107 | 108 | [Fact] 109 | public void when_can_execute_requested_then_invokes_async_handler() 110 | { 111 | var command = new AsyncCommand(); 112 | var bus = new CommandBus(Mock.Of>(c => c.CanExecute(command) == true)); 113 | 114 | Assert.True(bus.CanExecute(command)); 115 | } 116 | 117 | [Fact] 118 | public void when_executing_sync_command_then_invokes_sync_handler() 119 | { 120 | var handler = new Mock>(); 121 | var command = new Command(); 122 | var bus = new CommandBus(handler.Object); 123 | 124 | bus.Execute(command); 125 | 126 | handler.Verify(x => x.Execute(command)); 127 | } 128 | 129 | [Fact] 130 | public void when_executing_sync_command_then_invokes_sync_handler_with_result() 131 | { 132 | var handler = new Mock>(); 133 | var command = new CommandWithResult(); 134 | var bus = new CommandBus(handler.Object); 135 | 136 | bus.Execute(command); 137 | 138 | handler.Verify(x => x.Execute(command)); 139 | } 140 | 141 | [Fact] 142 | public void when_executing_sync_command_with_result_then_invokes_sync_handler_with_result() 143 | { 144 | var handler = new Mock>(); 145 | var command = new CommandWithResult(); 146 | var expected = new Result(); 147 | 148 | handler.Setup(x => x.Execute(command)).Returns(expected); 149 | var bus = new CommandBus(handler.Object); 150 | 151 | var result = bus.Execute(command); 152 | 153 | Assert.Same(expected, result); 154 | } 155 | 156 | [Fact] 157 | public async void when_executing_async_command_then_invokes_async_handler() 158 | { 159 | var handler = new Mock>(); 160 | var command = new AsyncCommand(); 161 | 162 | handler.Setup(x => x.ExecuteAsync(command, CancellationToken.None)).Returns(Task.FromResult(true)); 163 | var bus = new CommandBus(handler.Object); 164 | 165 | await bus.ExecuteAsync(command, CancellationToken.None); 166 | 167 | handler.Verify(x => x.ExecuteAsync(command, CancellationToken.None)); 168 | } 169 | 170 | [Fact] 171 | public async void when_executing_async_command_then_invokes_async_handler_with_result() 172 | { 173 | var handler = new Mock>(); 174 | var command = new AsyncCommandWithResult(); 175 | var result = new Result(); 176 | 177 | handler.Setup(x => x.ExecuteAsync(command, CancellationToken.None)).Returns(Task.FromResult(result)); 178 | var bus = new CommandBus(handler.Object); 179 | 180 | await bus.ExecuteAsync(command, CancellationToken.None); 181 | 182 | handler.Verify(x => x.ExecuteAsync(command, CancellationToken.None)); 183 | } 184 | 185 | [Fact] 186 | public void when_constructing_with_null_handlers_then_throws() 187 | { 188 | Assert.Throws(() => new CommandBus(default(IEnumerable))); 189 | } 190 | 191 | [Fact] 192 | public void when_can_handle_with_null_command_then_returns_false() 193 | { 194 | Assert.False(new CommandBus().CanHandle(null)); 195 | } 196 | 197 | [Fact] 198 | public void when_can_execute_with_null_command_then_returns_false() 199 | { 200 | Assert.False(new CommandBus().CanExecute(null)); 201 | } 202 | 203 | [Fact] 204 | public void when_execute_with_null_command_then_throws() 205 | { 206 | Assert.Throws(() => new CommandBus().Execute(default(Command))); 207 | } 208 | 209 | [Fact] 210 | public void when_execute_result_with_null_command_then_throws() 211 | { 212 | Assert.Throws(() => new CommandBus().Execute(default(CommandWithResult))); 213 | } 214 | 215 | [Fact] 216 | public async Task when_executeasync_with_null_command_then_throws() 217 | { 218 | await Assert.ThrowsAsync(() => new CommandBus().ExecuteAsync(default(AsyncCommand), CancellationToken.None)); 219 | } 220 | 221 | [Fact] 222 | public async Task when_executeasync_result_with_null_command_then_throws() 223 | { 224 | await Assert.ThrowsAsync(() => new CommandBus().ExecuteAsync(default(AsyncCommandWithResult), CancellationToken.None)); 225 | } 226 | 227 | [Fact] 228 | public void when_executing_non_public_command_handler_then_invokes_handler_with_result() 229 | { 230 | var handler = new NonPublicCommandHandlerWithResults(new Result()); 231 | var bus = new CommandBus(handler); 232 | 233 | var results = bus.Execute(new CommandWithResults()); 234 | 235 | Assert.Single(results); 236 | } 237 | 238 | [Fact] 239 | public void when_executing_command_as_explicit_ICommand_then_invokes_handler() 240 | { 241 | var handler = new Mock>(); 242 | var command = new Command(); 243 | var bus = new CommandBus(handler.Object); 244 | 245 | bus.Execute((ICommand)command); 246 | 247 | handler.Verify(x => x.Execute(command)); 248 | } 249 | 250 | [Fact] 251 | public void when_command_execution_throws_then_throws_original_exception() 252 | { 253 | var handler = new Mock>(); 254 | var command = new Command(); 255 | var exception = new InvalidOperationException(); 256 | handler.Setup(x => x.Execute(command)).Throws(exception); 257 | var bus = new CommandBus(handler.Object); 258 | 259 | var actual = Assert.Throws(() => bus.Execute((ICommand)command)); 260 | 261 | Assert.Same(exception, actual); 262 | } 263 | 264 | public class AsyncCommand : IAsyncCommand { } 265 | 266 | public class AsyncCommandWithResult : IAsyncCommand { } 267 | 268 | public class Command : ICommand { } 269 | 270 | public class CommandWithResult : ICommand { } 271 | 272 | public class CommandWithResults : ICommand> { } 273 | 274 | public class Result { } 275 | 276 | class NonPublicCommandHandlerWithResults : ICommandHandler> 277 | { 278 | Result result; 279 | 280 | public NonPublicCommandHandlerWithResults(Result result) 281 | { 282 | this.result = result; 283 | } 284 | 285 | bool ICanExecute.CanExecute(CommandWithResults command) 286 | { 287 | return true; 288 | } 289 | 290 | IEnumerable ICommandHandler>.Execute(CommandWithResults command) 291 | { 292 | yield return result; 293 | } 294 | } 295 | 296 | // Ensure all test to be run using a derived command bus class 297 | class CommandBus : Merq.CommandBus 298 | { 299 | public CommandBus(IEnumerable handlers) : base(handlers) { } 300 | 301 | public CommandBus(params ICommandHandler[] handlers) : base(handlers) { } 302 | } 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /src/Core/Merq.Core.Tests/EventStreamSamples.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reactive; 5 | using System.Reactive.Linq; 6 | using Microsoft.Reactive.Testing; 7 | using Xunit; 8 | 9 | namespace Merq 10 | { 11 | public class EventStreamSamples 12 | { 13 | [Fact] 14 | public void when_patient_readmitted_then_raises_alert() 15 | { 16 | var events = new EventStream(); 17 | var query = 18 | from discharged in events.Of() 19 | from admitted in events.Of() 20 | where 21 | admitted.PatientId == discharged.PatientId && 22 | (admitted.When - discharged.When).Days < 5 23 | select admitted; 24 | 25 | 26 | var readmitted = new List(); 27 | 28 | using (var subscription = query.Subscribe(e => readmitted.Add(e.PatientId))) 29 | { 30 | // Two patients come in. 31 | events.Push(new PatientEnteredHospital { PatientId = 1, When = new DateTime(2011, 1, 1) }); 32 | events.Push(new PatientEnteredHospital { PatientId = 2, When = new DateTime(2011, 1, 1) }); 33 | 34 | // Both leave same day. 35 | events.Push(new PatientLeftHospital { PatientId = 1, When = new DateTime(2011, 1, 15) }); 36 | events.Push(new PatientLeftHospital { PatientId = 2, When = new DateTime(2011, 1, 15) }); 37 | 38 | // One comes back before 5 days passed. 39 | events.Push(new PatientEnteredHospital { PatientId = 1, When = new DateTime(2011, 1, 18) }); 40 | 41 | // The other comes back after 10 days passed. 42 | events.Push(new PatientEnteredHospital { PatientId = 1, When = new DateTime(2011, 1, 25) }); 43 | } 44 | 45 | // We should have an alert for patient 1 who came back before 5 days passed. 46 | Assert.Single(readmitted); 47 | Assert.Equal(1, readmitted[0]); 48 | } 49 | 50 | [Fact] 51 | public void when_user_login_fails_too_fast_then_locks_account() 52 | { 53 | var seconds = TimeSpan.FromSeconds(1).Ticks; 54 | var events = new EventStream(); 55 | 56 | // Here we use the test scheduler to simulate time passing by 57 | // because we have a dependency on time because of the Buffer 58 | // method. 59 | var scheduler = new TestScheduler(); 60 | var observable = scheduler.CreateColdObservable( 61 | // Two users attempt to log in, 4 times in a row 62 | new Recorded>(10 * seconds, Notification.CreateOnNext(new LoginFailure { UserId = 1 })), 63 | new Recorded>(10 * seconds, Notification.CreateOnNext(new LoginFailure { UserId = 2 })), 64 | new Recorded>(20 * seconds, Notification.CreateOnNext(new LoginFailure { UserId = 1 })), 65 | new Recorded>(20 * seconds, Notification.CreateOnNext(new LoginFailure { UserId = 2 })), 66 | new Recorded>(30 * seconds, Notification.CreateOnNext(new LoginFailure { UserId = 1 })), 67 | new Recorded>(30 * seconds, Notification.CreateOnNext(new LoginFailure { UserId = 2 })), 68 | new Recorded>(40 * seconds, Notification.CreateOnNext(new LoginFailure { UserId = 1 })), 69 | new Recorded>(40 * seconds, Notification.CreateOnNext(new LoginFailure { UserId = 2 })), 70 | 71 | // User 2 attempts one more time within the 1' window 72 | new Recorded>(45 * seconds, Notification.CreateOnNext(new LoginFailure { UserId = 2 })), 73 | 74 | // User 1 pulls out the paper where he wrote his pwd ;), so he takes longer 75 | new Recorded>(75 * seconds, Notification.CreateOnNext(new LoginFailure { UserId = 1 })) 76 | ); 77 | 78 | // This subscription bridges the scheduler-driven 79 | // observable with our event stream, causing us 80 | // to publish events as they are "raised" by the 81 | // test scheduler. 82 | observable.Subscribe(failure => events.Push(failure)); 83 | 84 | var query = events.Of() 85 | // Sliding windows 1' long, every 10'' 86 | .Buffer(TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(10), scheduler) 87 | // From all failure values 88 | .SelectMany(failures => failures 89 | // Group the failures by user 90 | .GroupBy(failure => failure.UserId) 91 | // Only grab those failures with more than 5 in the 1' window 92 | .Where(group => group.Count() >= 5) 93 | // Return the user id that failed to log in 94 | .Select(group => group.Key)); 95 | 96 | var blocked = new List(); 97 | 98 | using (var subscription = query.Subscribe(userId => blocked.Add(userId))) 99 | { 100 | // Here we could advance the scheduler half way and test intermediate 101 | // state if needed. We go all the way past the end of our login failures. 102 | scheduler.AdvanceTo(100 * seconds); 103 | } 104 | 105 | // We should have only user # 2 in the list. 106 | Assert.DoesNotContain(1, blocked); 107 | Assert.Contains(2, blocked); 108 | } 109 | 110 | public interface IBaseEvent { } 111 | 112 | public class BaseEvent : EventArgs, IBaseEvent 113 | { 114 | public override string ToString() 115 | { 116 | return "Base event"; 117 | } 118 | } 119 | 120 | public class PatientEnteredHospital : BaseEvent 121 | { 122 | public int PatientId { get; set; } 123 | public DateTimeOffset When { get; set; } 124 | 125 | public override string ToString() 126 | { 127 | return string.Format("Patient {0} entered on {1}.", PatientId, When); 128 | } 129 | } 130 | 131 | public class PatientLeftHospital : BaseEvent 132 | { 133 | public int PatientId { get; set; } 134 | public DateTimeOffset When { get; set; } 135 | 136 | public override string ToString() 137 | { 138 | return string.Format("Patient {0} left on {1}.", PatientId, When); 139 | } 140 | } 141 | 142 | public class LoginFailure : BaseEvent 143 | { 144 | public int UserId { get; set; } 145 | public DateTimeOffset When { get; set; } 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/Core/Merq.Core.Tests/EventStreamSpec.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive.Linq; 3 | using System.Reactive.Subjects; 4 | using Xunit; 5 | 6 | namespace Merq 7 | { 8 | public class EventStreamSpec 9 | { 10 | [Fact] 11 | public void when_pushing_null_event_then_throws() 12 | { 13 | var stream = new EventStream(); 14 | 15 | Assert.Throws(() => stream.Push(null)); 16 | } 17 | 18 | [Fact] 19 | public void when_pushing_non_public_event_type_then_throws() 20 | { 21 | var stream = new EventStream(); 22 | 23 | Assert.Throws(() => stream.Push(new NonPublicEvent())); 24 | } 25 | 26 | [Fact] 27 | public void when_subscribing_non_public_event_type_then_throws() 28 | { 29 | var stream = new EventStream(); 30 | 31 | Assert.Throws(() => stream.Of()); 32 | } 33 | 34 | [Fact] 35 | public void when_pushing_non_subscribed_event_then_does_not_call_subscriber() 36 | { 37 | var stream = new EventStream(); 38 | var called = false; 39 | 40 | using (var subscription = stream.Of().Subscribe(c => called = true)) 41 | { 42 | stream.Push(new AnotherEvent()); 43 | } 44 | 45 | Assert.False(called); 46 | } 47 | 48 | [Fact] 49 | public void when_pushing_subscribed_nested_public_event_then_calls_subscriber() 50 | { 51 | var stream = new EventStream(); 52 | var called = false; 53 | 54 | using (var subscription = stream.Of().Subscribe(c => called = true)) 55 | { 56 | stream.Push(new NestedPublicEvent()); 57 | } 58 | 59 | Assert.True(called); 60 | } 61 | 62 | [Fact] 63 | public void when_pushing_subscribed_event_then_calls_subscriber() 64 | { 65 | var stream = new EventStream(); 66 | var called = false; 67 | 68 | using (var subscription = stream.Of().Subscribe(c => called = true)) 69 | { 70 | stream.Push(new ConcreteEvent()); 71 | } 72 | 73 | Assert.True(called); 74 | } 75 | 76 | [Fact] 77 | public void when_pushing_subscribed_event_using_base_type_then_calls_subscriber() 78 | { 79 | var stream = new EventStream(); 80 | var called = false; 81 | 82 | using (var subscription = stream.Of().Subscribe(c => called = true)) 83 | { 84 | BaseEvent @event = new ConcreteEvent(); 85 | stream.Push(@event); 86 | } 87 | 88 | Assert.True(called); 89 | } 90 | 91 | [Fact] 92 | public void when_subscribing_as_event_interface_then_calls_subscriber() 93 | { 94 | var stream = new EventStream(); 95 | var called = false; 96 | 97 | using (var subscription = stream.Of().Subscribe(c => called = true)) 98 | { 99 | stream.Push(new ConcreteEvent()); 100 | } 101 | 102 | Assert.True(called); 103 | } 104 | 105 | [Fact] 106 | public void given_an_observable_when_subscribing_event_then_subscribes_to_observable() 107 | { 108 | var subject = new Subject(); 109 | var stream = new EventStream(subject); 110 | var called = false; 111 | 112 | using (var subscription = stream.Of().Subscribe(c => called = true)) 113 | { 114 | subject.OnNext(new ConcreteEvent()); 115 | } 116 | 117 | Assert.True(called); 118 | } 119 | 120 | [Fact] 121 | public void given_two_observables_when_subscribing_base_event_then_receives_both() 122 | { 123 | var subject1 = new Subject(); 124 | var subject2 = new Subject(); 125 | var stream = new EventStream(subject1, subject2); 126 | var called = 0; 127 | 128 | using (var subscription = stream.Of().Subscribe(c => called++)) 129 | { 130 | subject1.OnNext(new ConcreteEvent()); 131 | subject2.OnNext(new AnotherEvent()); 132 | } 133 | 134 | Assert.Equal(2, called); 135 | } 136 | 137 | [Fact] 138 | public void given_an_observable_when_pushing_compatible_event_then_subscriber_base_type_receives_both() 139 | { 140 | var subject = new Subject(); 141 | var stream = new EventStream(subject); 142 | var called = 0; 143 | 144 | using (var subscription = stream.Of().Subscribe(c => called++)) 145 | { 146 | subject.OnNext(new ConcreteEvent()); 147 | stream.Push(new AnotherEvent()); 148 | } 149 | 150 | Assert.Equal(2, called); 151 | } 152 | 153 | 154 | public class NestedPublicEvent { } 155 | } 156 | 157 | public interface IBaseEvent { } 158 | 159 | public class BaseEvent : IBaseEvent 160 | { 161 | } 162 | 163 | public class ConcreteEvent : BaseEvent { } 164 | 165 | public class AnotherEvent : BaseEvent { } 166 | 167 | internal class NonPublicEvent { } 168 | } -------------------------------------------------------------------------------- /src/Core/Merq.Core.Tests/Merq.Core.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | net472 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Core/Merq.Core/CommandBus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace Merq 10 | { 11 | /// 12 | /// Default implementation of the , optimized 13 | /// for high performance and no dynamic or reflection invocation. 14 | /// 15 | /// 16 | /// There are basically four types of command/handler pairs: 17 | /// * void synchronous 18 | /// * non-void synchronous 19 | /// * void async 20 | /// * non-void async 21 | /// 22 | /// Each of these have different handler interfaces that are constrained 23 | /// by the command type, so that callers can know what invocation style 24 | /// to use depending on the command alone, not the implementation. The 25 | /// implementers are constrained by what's declared in the command type 26 | /// so that there is no mismatch between the invocation style and the 27 | /// implementation style. This avoids implementing anti-patterns like 28 | /// faking async on a non-async implementation and vice-versa. 29 | /// 30 | /// 31 | /// In order for the caller to avoid having to specify both the command 32 | /// type and the result type when executing, we resort to inferring the 33 | /// latter from the former. But in order for this to work, we need to 34 | /// introduce an intermediary "executor" which is typed to the actual 35 | /// command instance received in the various Execute* overloads. 36 | /// 37 | /// This might seem like unnecessary complexity, but it actually produces 38 | /// a very performant invocation pattern (no reflection, lazy instantiation 39 | /// of the executors per command-type) while keeping the calling pattern on 40 | /// the command bus simple like: 41 | /// 42 | /// FooResult result = commandBus.Execute(new FooCommand()); 43 | /// 44 | /// Without the executors, inference wouldn't "just work" and both types 45 | /// (command and result) would need to be specified, as in: 46 | /// 47 | /// FooResult result = commandBus.Execute<FooCommand, FooResult>(new FooCommand()); 48 | /// 49 | /// which is quite awful. 50 | /// 51 | /// Each of the four invocation styles gets its own non-generic executor, so that the 52 | /// Execute overloads can invoke them with the generic command argument, as well as the 53 | /// generic implementations that contain the actual invocation and downcast. 54 | /// 55 | public class CommandBus : ICommandBus 56 | { 57 | ConcurrentDictionary> handlerMap = new ConcurrentDictionary>(); 58 | ConcurrentDictionary voidExecutors = new ConcurrentDictionary(); 59 | ConcurrentDictionary voidAsyncExecutors = new ConcurrentDictionary(); 60 | ConcurrentDictionary resultExecutors = new ConcurrentDictionary(); 61 | ConcurrentDictionary resultAsyncExecutors = new ConcurrentDictionary(); 62 | 63 | /// 64 | /// Creates a new with the given set of command handlers. 65 | /// 66 | /// A map from command types to lazy command handlers. 67 | public CommandBus(ConcurrentDictionary> handlerMap) 68 | => this.handlerMap = handlerMap; 69 | 70 | /// 71 | /// Initializes the command bus with the given list of handlers. 72 | /// 73 | public CommandBus(IEnumerable handlers) 74 | : this(new ConcurrentDictionary>( 75 | (handlers ?? throw new ArgumentNullException(nameof(handlers))) 76 | .Select(x => new KeyValuePair>(GetCommandType(x?.GetType() ?? throw new ArgumentNullException()), new Lazy(() => x))))) 77 | { 78 | } 79 | 80 | /// 81 | /// Initializes the command bus with the given list of handlers. 82 | /// 83 | public CommandBus(params ICommandHandler[] handlers) 84 | : this((IEnumerable)handlers) 85 | { 86 | } 87 | 88 | /// 89 | /// Registers a command handler type for use with the command bus. 90 | /// 91 | public virtual void Register() 92 | where TCommandHandler : ICommandHandler, new() 93 | => handlerMap.AddOrUpdate( 94 | GetCommandType(typeof(TCommandHandler)), 95 | _ => new Lazy(() => new TCommandHandler()), 96 | (_, __) => new Lazy(() => new TCommandHandler())); 97 | 98 | /// 99 | /// Registers a command handler instance for use with the command bus. 100 | /// 101 | public virtual void Register(TCommandHandler handler) 102 | where TCommandHandler : ICommandHandler 103 | => handlerMap.AddOrUpdate( 104 | GetCommandType(typeof(TCommandHandler)), 105 | _ => new Lazy(() => handler), 106 | (_, __) => new Lazy(() => handler)); 107 | 108 | /// 109 | /// Registers a lazy command handler instance for use with the command bus. 110 | /// 111 | public virtual void Register(Lazy handler) 112 | where TCommandHandler : ICommandHandler 113 | => handlerMap.AddOrUpdate( 114 | GetCommandType(typeof(TCommandHandler)), 115 | _ => new Lazy(() => handler.Value), 116 | (_, __) => new Lazy(() => handler.Value)); 117 | 118 | static Type GetCommandType(Type type) 119 | => type.GetTypeInfo().ImplementedInterfaces 120 | .Where(t => t.GetTypeInfo().IsGenericType && t.GetGenericTypeDefinition() == typeof(IExecutableCommandHandler<>)) 121 | .Select(t => t.GetTypeInfo().GenericTypeArguments[0]) 122 | .FirstOrDefault() ?? throw new ArgumentException(); 123 | 124 | /// 125 | /// Determines whether the given command type has a registered handler. 126 | /// 127 | /// The type of command to query. 128 | /// if the command has a registered handler. otherwise. 129 | public virtual bool CanHandle() where TCommand : IExecutable 130 | => handlerMap.ContainsKey(typeof(TCommand)); 131 | 132 | /// 133 | /// Determines whether the given command has a registered handler. 134 | /// 135 | /// The command to query. 136 | /// if the command has a registered handler. otherwise. 137 | public virtual bool CanHandle(IExecutable command) 138 | => command != null && handlerMap.ContainsKey(command.GetType()); 139 | 140 | /// 141 | /// Determines whether the given command can be executed by a registered 142 | /// handler with the provided command instance values. 143 | /// 144 | /// The command parameters for the query. 145 | /// if the command can be executed. otherwise. 146 | public virtual bool CanExecute(TCommand command) where TCommand : IExecutable 147 | => command != null && 148 | handlerMap.TryGetValue(command.GetType(), out var handler) && 149 | ((ICanExecute)handler.Value).CanExecute(command); 150 | 151 | /// 152 | /// Executes the given command synchronously. 153 | /// 154 | /// The command parameters for the execution. 155 | public virtual void Execute(ICommand command) 156 | => voidExecutors.GetOrAdd( 157 | GetCommandType(command), 158 | type => (VoidExecutor)Activator.CreateInstance( 159 | typeof(VoidExecutor<>).MakeGenericType(type), 160 | GetCommandHandler(type))) 161 | .Execute(command); 162 | 163 | /// 164 | /// Executes the given command synchronously. 165 | /// 166 | /// The return type of the command execution. 167 | /// The command parameters for the execution. 168 | /// The result of executing the command. 169 | public virtual TResult Execute(ICommand command) 170 | => (TResult)resultExecutors.GetOrAdd( 171 | GetCommandType(command), 172 | type => (ResultExecutor)Activator.CreateInstance( 173 | typeof(ResultExecutor<,>).MakeGenericType(type, typeof(TResult)), 174 | GetCommandHandler(type))) 175 | .Execute(command); 176 | 177 | /// 178 | /// Executes the given asynchronous command. 179 | /// 180 | /// The command parameters for the execution. 181 | /// Cancellation token to cancel command execution. 182 | public virtual Task ExecuteAsync(IAsyncCommand command, CancellationToken cancellation) 183 | => voidAsyncExecutors.GetOrAdd( 184 | GetCommandType(command), 185 | type => (VoidAsyncExecutor)Activator.CreateInstance( 186 | typeof(VoidAsyncExecutor<>).MakeGenericType(type), 187 | GetCommandHandler(type))) 188 | .ExecuteAsync(command, cancellation); 189 | 190 | /// 191 | /// Executes the given command asynchronously. 192 | /// 193 | /// The return type of the command execution. 194 | /// The command parameters for the execution. 195 | /// Cancellation token to cancel command execution. 196 | /// The result of executing the command. 197 | public virtual Task ExecuteAsync(IAsyncCommand command, CancellationToken cancellation) 198 | => (Task)resultAsyncExecutors.GetOrAdd( 199 | GetCommandType(command), 200 | type => (ResultAsyncExecutor)Activator.CreateInstance( 201 | typeof(ResultAsyncExecutor<,>).MakeGenericType(type, typeof(TResult)), 202 | GetCommandHandler(type))) 203 | .ExecuteAsync(command, cancellation); 204 | 205 | /// 206 | /// Command cannot be null for execution 207 | /// 208 | static Type GetCommandType(IExecutable command) 209 | => command?.GetType() ?? throw new ArgumentNullException(nameof(command)); 210 | 211 | /// 212 | /// A handler must be registered for the given command. 213 | /// 214 | ICommandHandler GetCommandHandler(Type commandType) 215 | => handlerMap.TryGetValue(commandType, out var value) ? value.Value : throw new NotSupportedException(commandType.Name); 216 | 217 | abstract class VoidExecutor 218 | { 219 | public abstract void Execute(IExecutable command); 220 | } 221 | 222 | class VoidExecutor : VoidExecutor where TCommand : ICommand 223 | { 224 | ICommandHandler handler; 225 | 226 | public VoidExecutor(ICommandHandler handler) 227 | => this.handler = (ICommandHandler)handler; 228 | 229 | public override void Execute(IExecutable command) 230 | => handler.Execute((TCommand)command); 231 | } 232 | 233 | abstract class ResultExecutor 234 | { 235 | public abstract object Execute(IExecutable command); 236 | } 237 | 238 | class ResultExecutor : ResultExecutor where TCommand : ICommand 239 | { 240 | ICommandHandler handler; 241 | 242 | public ResultExecutor(ICommandHandler handler) 243 | => this.handler = (ICommandHandler)handler; 244 | 245 | public override object Execute(IExecutable command) 246 | => handler.Execute((TCommand)command); 247 | } 248 | 249 | abstract class VoidAsyncExecutor 250 | { 251 | public abstract Task ExecuteAsync(IExecutable command, CancellationToken cancellation); 252 | } 253 | 254 | class VoidAsyncExecutor : VoidAsyncExecutor where TCommand : IAsyncCommand 255 | { 256 | IAsyncCommandHandler handler; 257 | 258 | public VoidAsyncExecutor(ICommandHandler handler) 259 | => this.handler = (IAsyncCommandHandler)handler; 260 | 261 | public override Task ExecuteAsync(IExecutable command, CancellationToken cancellation) 262 | => handler.ExecuteAsync((TCommand)command, cancellation); 263 | } 264 | 265 | abstract class ResultAsyncExecutor 266 | { 267 | public abstract object ExecuteAsync(IExecutable command, CancellationToken cancellation); 268 | } 269 | 270 | class ResultAsyncExecutor : ResultAsyncExecutor where TCommand : IAsyncCommand 271 | { 272 | IAsyncCommandHandler handler; 273 | 274 | public ResultAsyncExecutor(ICommandHandler handler) 275 | => this.handler = (IAsyncCommandHandler)handler; 276 | 277 | public override object ExecuteAsync(IExecutable command, CancellationToken cancellation) 278 | => handler.ExecuteAsync((TCommand)command, cancellation); 279 | } 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /src/Core/Merq.Core/EventStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | 7 | namespace Merq 8 | { 9 | /// 10 | /// Default implementation of . 11 | /// 12 | public class EventStream : IEventStream 13 | { 14 | // All subjects active in the event stream. 15 | readonly ConcurrentDictionary subjects = new ConcurrentDictionary(); 16 | // An cache of subjects indexed by the compatible event types, used to quickly lookup the subjects to 17 | // invoke in a Push. Refreshed whenever a new Of subscription is added. 18 | readonly ConcurrentDictionary compatibleSubjects = new ConcurrentDictionary(); 19 | // Externally-produced events by IObservable implementations. 20 | readonly HashSet observables; 21 | 22 | /// 23 | /// Initializes the event stream. 24 | /// 25 | public EventStream() 26 | : this(Enumerable.Empty()) 27 | { 28 | } 29 | 30 | /// 31 | /// Initializes the event stream with an optional list of 32 | /// externally managed event producers which implement 33 | /// . 34 | /// 35 | public EventStream(params object[] observables) 36 | : this((IEnumerable)observables) 37 | { 38 | } 39 | 40 | /// 41 | /// Initializes the event stream with an optional list of 42 | /// externally managed event producers. 43 | /// 44 | public EventStream(IEnumerable observables) 45 | => this.observables = new HashSet(observables); 46 | 47 | /// 48 | /// Pushes an event to the stream, causing any subscriber to be invoked if appropriate. 49 | /// 50 | public virtual void Push(TEvent @event) 51 | { 52 | if (@event == null) throw new ArgumentNullException(nameof(@event)); 53 | if (!IsValid()) 54 | throw new NotSupportedException(Strings.EventStream.PublishedEventNotPublic); 55 | 56 | var eventType = @event.GetType().GetTypeInfo(); 57 | 58 | InvokeCompatibleSubjects(@eventType, @event); 59 | } 60 | 61 | /// 62 | /// Observes the events of a given type . 63 | /// 64 | public virtual IObservable Of() 65 | { 66 | if (!IsValid()) 67 | throw new NotSupportedException(Strings.EventStream.SubscribedEventNotPublic); 68 | 69 | var subject = (IObservable)subjects.GetOrAdd(typeof(TEvent).GetTypeInfo(), info => 70 | { 71 | // If we're creating a new subject, we need to clear the cache of compatible subjects 72 | compatibleSubjects.Clear(); 73 | return new Subject(); 74 | }); 75 | 76 | // Merge with any externally-produced observables that are compatible 77 | var compatibleObservables = new[] { subject }.Concat(GetObservables()).ToArray(); 78 | if (compatibleObservables.Length == 1) 79 | return compatibleObservables[0]; 80 | 81 | return new CompositeObservable(compatibleObservables); 82 | } 83 | 84 | /// 85 | /// Derived classes can augment the available external event producers (observables) 86 | /// by overriding this method and concatenating other observables to the ones provided 87 | /// in the constructor (if any). 88 | /// 89 | /// Type of event being looked up. 90 | protected virtual IEnumerable> GetObservables() 91 | => observables.OfType>(); 92 | 93 | void InvokeCompatibleSubjects(TypeInfo info, object @event) 94 | { 95 | // We will call all subjects that are compatible with 96 | // the event type, not just concrete event type subscribers. 97 | var compatible = compatibleSubjects.GetOrAdd(info, eventType => subjects.Keys 98 | .Where(subjectEventType => subjectEventType.IsAssignableFrom(eventType)) 99 | .Select(subjectEventType => subjects[subjectEventType]) 100 | .ToArray()); 101 | 102 | foreach (var subject in compatible) 103 | { 104 | subject.OnNext(@event); 105 | } 106 | } 107 | 108 | static bool IsValid() 109 | => typeof(TEvent).GetTypeInfo().IsPublic || typeof(TEvent).GetTypeInfo().IsNestedPublic; 110 | 111 | abstract class Subject 112 | { 113 | public abstract void OnNext(object value); 114 | } 115 | 116 | class Subject : Subject, IObservable 117 | { 118 | readonly ConcurrentDictionary, object> observers = new ConcurrentDictionary, object>(); 119 | 120 | public override void OnNext(object value) 121 | { 122 | var next = (T)value; 123 | 124 | foreach (var item in observers.Keys.ToArray()) 125 | { 126 | // Don't let misbehaving subscribers prevent calling the others 127 | try 128 | { 129 | item.OnNext(next); 130 | } 131 | catch (Exception ex) 132 | { 133 | // Flag them and remove them to avoid perf. issues from 134 | // constantly throwing subscribers. 135 | item.OnError(ex); 136 | observers.TryRemove(item, out _); 137 | } 138 | } 139 | } 140 | 141 | IDisposable IObservable.Subscribe(IObserver observer) 142 | => new Subscription(this, observer); 143 | 144 | class Subscription : IDisposable 145 | { 146 | readonly Subject subject; 147 | readonly IObserver observer; 148 | 149 | public Subscription(Subject subject, IObserver observer) 150 | { 151 | this.subject = subject; 152 | this.observer = observer; 153 | subject.observers.TryAdd(observer, null); 154 | } 155 | 156 | public void Dispose() => subject.observers.TryRemove(observer, out _); 157 | } 158 | } 159 | 160 | class CompositeObservable : IObservable 161 | { 162 | readonly IObservable[] observables; 163 | 164 | public CompositeObservable(IObservable[] observables) 165 | => this.observables = observables; 166 | 167 | public IDisposable Subscribe(IObserver observer) 168 | => new CompositeDisposable(observables 169 | .Select(observable => observable.Subscribe(observer)).ToArray()); 170 | } 171 | 172 | class CompositeDisposable : IDisposable 173 | { 174 | readonly IDisposable[] disposables; 175 | 176 | public CompositeDisposable(IDisposable[] disposables) 177 | => this.disposables = disposables; 178 | 179 | public void Dispose() 180 | { 181 | foreach (var disposable in disposables) 182 | { 183 | disposable.Dispose(); 184 | } 185 | } 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/Core/Merq.Core/Merq.Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard1.3 5 | false 6 | Merq.Core 7 | Merq: Command Bus + Event Stream (Implementations) 8 | Default implementation of core interfaces for ICommandBus and IEventStream 9 | Default implementation of core interfaces for ICommandBus and IEventStream 10 | false 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | <_Parameter1>Merq.Core.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001009d69078301b6c4881e95cd924d5e355a4d24ba3d28fb571e00124706538eef959eb371fbb9bd2776fbe7d228178df51fbd4a849aff37161190f3254c77167d16e42c2be32c817537b67b874b66be01a4b6d1c780ff052c8f7f52cad6751288911d450cf443ed4f40b266332f6f25204df23df9a23d38e5fe19f6372a636c7da1 29 | 30 | 31 | 32 | 33 | 34 | True 35 | True 36 | Resources.resx 37 | 38 | 39 | 40 | 41 | 42 | ResXFileCodeGenerator 43 | Resources.Designer.cs 44 | Merq 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/Core/Merq.Core/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Merq { 12 | using System; 13 | using System.Reflection; 14 | 15 | 16 | /// 17 | /// A strongly-typed resource class, for looking up localized strings, etc. 18 | /// 19 | // This class was auto-generated by the StronglyTypedResourceBuilder 20 | // class via a tool like ResGen or Visual Studio. 21 | // To add or remove a member, edit your .ResX file then rerun ResGen 22 | // with the /str option, or rebuild your VS project. 23 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] 24 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 25 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 26 | internal class Resources { 27 | 28 | private static global::System.Resources.ResourceManager resourceMan; 29 | 30 | private static global::System.Globalization.CultureInfo resourceCulture; 31 | 32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 33 | internal Resources() { 34 | } 35 | 36 | /// 37 | /// Returns the cached ResourceManager instance used by this class. 38 | /// 39 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 40 | internal static global::System.Resources.ResourceManager ResourceManager { 41 | get { 42 | if (object.ReferenceEquals(resourceMan, null)) { 43 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Merq.Core.Properties.Resources", typeof(Resources).GetTypeInfo().Assembly); 44 | resourceMan = temp; 45 | } 46 | return resourceMan; 47 | } 48 | } 49 | 50 | /// 51 | /// Overrides the current thread's CurrentUICulture property for all 52 | /// resource lookups using this strongly typed resource class. 53 | /// 54 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 55 | internal static global::System.Globalization.CultureInfo Culture { 56 | get { 57 | return resourceCulture; 58 | } 59 | set { 60 | resourceCulture = value; 61 | } 62 | } 63 | 64 | /// 65 | /// Looks up a localized string similar to The command handler '{0}' cannot be used because an existing one is already registered for the command type '{1}'.. 66 | /// 67 | internal static string CommandBus_DuplicateHandler { 68 | get { 69 | return ResourceManager.GetString("CommandBus_DuplicateHandler", resourceCulture); 70 | } 71 | } 72 | 73 | /// 74 | /// Looks up a localized string similar to Command handler '{0}' does not implement ICommandHandler or IAsyncCommandHandler generic interfaces.. 75 | /// 76 | internal static string CommandBus_InvalidHandler { 77 | get { 78 | return ResourceManager.GetString("CommandBus_InvalidHandler", resourceCulture); 79 | } 80 | } 81 | 82 | /// 83 | /// Looks up a localized string similar to No command handler is registered for command type {0}.. 84 | /// 85 | internal static string CommandBus_NoHandler { 86 | get { 87 | return ResourceManager.GetString("CommandBus_NoHandler", resourceCulture); 88 | } 89 | } 90 | 91 | /// 92 | /// Looks up a localized string similar to Published events must be public types.. 93 | /// 94 | internal static string EventStream_PublishedEventNotPublic { 95 | get { 96 | return ResourceManager.GetString("EventStream_PublishedEventNotPublic", resourceCulture); 97 | } 98 | } 99 | 100 | /// 101 | /// Looks up a localized string similar to Subscribed events must be public types.. 102 | /// 103 | internal static string EventStream_SubscribedEventNotPublic { 104 | get { 105 | return ResourceManager.GetString("EventStream_SubscribedEventNotPublic", resourceCulture); 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/Core/Merq.Core/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | The command handler '{0}' cannot be used because an existing one is already registered for the command type '{1}'. 122 | 123 | 124 | Command handler '{0}' does not implement ICommandHandler or IAsyncCommandHandler generic interfaces. 125 | 126 | 127 | No command handler is registered for command type {0}. 128 | 129 | 130 | Published events must be public types. 131 | 132 | 133 | Subscribed events must be public types. 134 | 135 | -------------------------------------------------------------------------------- /src/Core/Merq/GitInfo.txt: -------------------------------------------------------------------------------- 1 | 1.1.1 -------------------------------------------------------------------------------- /src/Core/Merq/IAsyncCommand.cs: -------------------------------------------------------------------------------- 1 | namespace Merq 2 | { 3 | /// 4 | /// Marker interface for asynchronous commands. 5 | /// 6 | public interface IAsyncCommand : IExecutable { } 7 | 8 | /// 9 | /// Marker interface for asynchronous commands whose execution results in a value 10 | /// being returned by its command handler. 11 | /// 12 | public interface IAsyncCommand : IExecutable { } 13 | } -------------------------------------------------------------------------------- /src/Core/Merq/IAsyncCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace Merq 6 | { 7 | /// 8 | /// Marker interface for asynchronous command handlers. 9 | /// 10 | [EditorBrowsable(EditorBrowsableState.Never)] 11 | public interface IAsyncCommandHandler : ICommandHandler { } 12 | 13 | /// 14 | /// Interface implemented by command handlers that 15 | /// handle commands asynchronously and don't return 16 | /// a result. 17 | /// 18 | /// Type of command supported by the handler. 19 | public interface IAsyncCommandHandler : IAsyncCommandHandler, IExecutableCommandHandler, ICanExecute where TCommand : IAsyncCommand 20 | { 21 | /// 22 | /// Executes the command asynchronously. 23 | /// 24 | /// The command parameters for the execution. 25 | /// Cancellation token to cancel command execution. 26 | Task ExecuteAsync(TCommand command, CancellationToken cancellation); 27 | } 28 | 29 | /// 30 | /// Interface implemented by command handlers that 31 | /// handle commands asynchronously and return a result. 32 | /// 33 | /// Type of command supported by the handler. 34 | /// The type of the returned value from the execution. 35 | /// 36 | /// NOTE: we can't make TResult covariant since the result is Task{T} which is a class and 37 | /// isn't covariant on the {T} either. 38 | /// 39 | public interface IAsyncCommandHandler : IAsyncCommandHandler, IExecutableCommandHandler, IExecuteResult, ICanExecute where TCommand : IAsyncCommand 40 | { 41 | /// 42 | /// Executes the command asynchronously. 43 | /// 44 | /// The command parameters for the execution. 45 | /// Cancellation token to cancel command execution. 46 | /// The result of the execution. 47 | Task ExecuteAsync(TCommand command, CancellationToken cancellation); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Core/Merq/ICanExecute.cs: -------------------------------------------------------------------------------- 1 | namespace Merq 2 | { 3 | /// 4 | /// Interface implemented by all generic command handlers 5 | /// to determine if they can execute a given command. 6 | /// 7 | public interface ICanExecute where TCommand : IExecutable 8 | { 9 | /// 10 | /// Determines whether the given command can be executed given the 11 | /// current state of the environment or the command itself. 12 | /// 13 | /// The command being queried for execution. 14 | bool CanExecute(TCommand command); 15 | } 16 | } -------------------------------------------------------------------------------- /src/Core/Merq/ICommand.cs: -------------------------------------------------------------------------------- 1 | namespace Merq 2 | { 3 | /// 4 | /// Marker interface for synchronous commands. 5 | /// 6 | public interface ICommand : IExecutable { } 7 | 8 | /// 9 | /// Marker interface for synchronous commands whose execution results in a value 10 | /// being returned by its command handler. 11 | /// 12 | public interface ICommand : IExecutable { } 13 | } 14 | -------------------------------------------------------------------------------- /src/Core/Merq/ICommandBus.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace Merq 5 | { 6 | /// 7 | /// Provides a uniform way of executing commands (synchronous or 8 | /// asynchronous) regardless of their handler implementations. 9 | /// 10 | public interface ICommandBus : IFluentInterface 11 | { 12 | /// 13 | /// Determines whether the given command type has a registered handler. 14 | /// 15 | /// The type of command to query. 16 | /// if the command has a registered handler. otherwise. 17 | bool CanHandle() where TCommand : IExecutable; 18 | 19 | /// 20 | /// Determines whether the given command has a registered handler. 21 | /// 22 | /// The command to query. 23 | /// if the command has a registered handler. otherwise. 24 | bool CanHandle(IExecutable command); 25 | 26 | /// 27 | /// Determines whether the given command can be executed by a registered 28 | /// handler with the provided command instance values. If no registered 29 | /// handler exists, returns . 30 | /// 31 | /// The command parameters for the query. 32 | /// if a command handler is registered and 33 | /// the command can be executed. otherwise. 34 | bool CanExecute(TCommand command) where TCommand : IExecutable; 35 | 36 | /// 37 | /// Executes the given synchronous command. 38 | /// 39 | /// The command parameters for the execution. 40 | void Execute(ICommand command); 41 | 42 | /// 43 | /// Executes the given synchronous command and returns a result from it. 44 | /// 45 | /// The return type of the command execution. 46 | /// The command parameters for the execution. 47 | /// The result of executing the command. 48 | TResult Execute(ICommand command); 49 | 50 | /// 51 | /// Executes the given asynchronous command. 52 | /// 53 | /// The command parameters for the execution. 54 | /// Cancellation token to cancel command execution. 55 | Task ExecuteAsync(IAsyncCommand command, CancellationToken cancellation); 56 | 57 | /// 58 | /// Executes the given asynchronous command and returns a result from it. 59 | /// 60 | /// The return type of the command execution. 61 | /// The command parameters for the execution. 62 | /// Cancellation token to cancel command execution. 63 | /// The result of executing the command. 64 | Task ExecuteAsync(IAsyncCommand command, CancellationToken cancellation); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Core/Merq/ICommandHandler.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace Merq 4 | { 5 | /// 6 | /// Marker interface for all command handlers, whether synchronous or asynchronous, 7 | /// allowing the to receive both. 8 | /// 9 | [EditorBrowsable(EditorBrowsableState.Never)] 10 | public interface ICommandHandler { } 11 | 12 | /// 13 | /// Interface implemented by synchronous void commands. 14 | /// 15 | /// Type of command supported by the handler. 16 | public interface ICommandHandler : IExecutableCommandHandler, ICanExecute where TCommand : ICommand 17 | { 18 | /// 21 | /// The command parameters for the execution. 22 | void Execute(TCommand command); 23 | } 24 | 25 | /// 26 | /// Interface implemented by synchronous command handlers that return a result. 27 | /// 28 | /// Type of command supported by the handler. 29 | /// The type of the returned value from the execution. 30 | public interface ICommandHandler : IExecutableCommandHandler, IExecuteResult, ICanExecute where TCommand : ICommand 31 | { 32 | /// 33 | /// Executes the command synchronously. 34 | /// 35 | /// The command parameters for the execution. 36 | /// The result of the execution. 37 | TResult Execute(TCommand command); 38 | } 39 | 40 | /// 41 | /// Marker interface for all command handlers that return a result. 42 | /// 43 | [EditorBrowsable(EditorBrowsableState.Never)] 44 | public interface IExecuteResult { } 45 | } 46 | -------------------------------------------------------------------------------- /src/Core/Merq/IEventStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Merq 4 | { 5 | /// 6 | /// Provides an observable stream of events that can be used for analysis 7 | /// or real-time handling. 8 | /// 9 | /// 10 | /// Leveraging the Reactive Extensions (Rx), it's possible to build fairly 11 | /// complicated event reaction chains by simply issuing Linq-style queries 12 | /// over the event stream. 13 | /// 14 | public interface IEventStream : IFluentInterface 15 | { 16 | /// 17 | /// Pushes an event to the stream, causing any subscriber to be invoked if 18 | /// appropriate. 19 | /// 20 | void Push(TEvent args); 21 | 22 | /// 23 | /// Observes the events of a given type . 24 | /// 25 | IObservable Of(); 26 | } 27 | } -------------------------------------------------------------------------------- /src/Core/Merq/IEventStreamExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace Merq 4 | { 5 | /// 6 | /// Usability overloads for . 7 | /// 8 | [EditorBrowsable(EditorBrowsableState.Never)] 9 | public static class IEventStreamExtensions 10 | { 11 | /// 12 | /// Pushes a new instance of through the stream. 13 | /// 14 | public static void Push(this IEventStream events) where TEvent : new() 15 | => events.Push(new TEvent()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Core/Merq/IExecutable.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace Merq 4 | { 5 | /// 6 | /// Marker interface for both synchronous and asynchronous commands, which 7 | /// allows and 8 | /// to reference either. 9 | /// 10 | [EditorBrowsable(EditorBrowsableState.Never)] 11 | public interface IExecutable 12 | { 13 | } 14 | 15 | /// 16 | /// Marker interface for both synchronous and asynchronous non-void commands. 17 | /// 18 | [EditorBrowsable(EditorBrowsableState.Never)] 19 | public interface IExecutable : IExecutable 20 | { 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Core/Merq/IExecutableCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace Merq 4 | { 5 | /// 6 | /// Marker interface for all generic command handlers, whether synchronous or asynchronous, 7 | /// allowing easy introspection of the generic if necessary. 8 | /// 9 | [EditorBrowsable(EditorBrowsableState.Never)] 10 | public interface IExecutableCommandHandler : ICommandHandler where TCommand : IExecutable 11 | { 12 | } 13 | 14 | /// 15 | /// Marker interface for all generic command handlers that return values, whether synchronous or asynchronous, 16 | /// allowing easy introspection of the generic and 17 | /// if necessary. 18 | /// 19 | /// The type of the command execution result. 20 | [EditorBrowsable(EditorBrowsableState.Never)] 21 | public interface IExecutableCommandHandler : IExecutableCommandHandler where TCommand : IExecutable 22 | { 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Core/Merq/Merq.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard1.3 5 | 6 | 7 | 8 | Merq 9 | Merq: Command Bus + Event Stream (Interfaces) 10 | Command Bus and Event Stream patterns core interfaces 11 | 12 | false 13 | false 14 | false 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | <_Parameter1>Merq.Core.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001009d69078301b6c4881e95cd924d5e355a4d24ba3d28fb571e00124706538eef959eb371fbb9bd2776fbe7d228178df51fbd4a849aff37161190f3254c77167d16e42c2be32c817537b67b874b66be01a4b6d1c780ff052c8f7f52cad6751288911d450cf443ed4f40b266332f6f25204df23df9a23d38e5fe19f6372a636c7da1 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/Core/Merq/Merq.targets: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Mobile Essentials 5 | mobessen 6 | Copyright 2016 © Mobile Essentials 7 | 8 | https://github.com/MobileEssentials/Merq 9 | https://github.com/MobileEssentials/Merq/blob/master/LICENSE 10 | https://raw.github.com/MobileEssentials/Merq/master/icon/48.png 11 | false 12 | 13 | $(DefaultItemExcludes);**/*.binlog 14 | 15 | false 16 | true 17 | $(MSBuildThisFileDirectory)../pack 18 | $(CI) 19 | $(CI) 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | $(SYSTEM_PULLREQUEST_TARGETBRANCH) 40 | $(BUILD_SOURCEBRANCHNAME) 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/Directory.Build.targets: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/GitInfo.txt: -------------------------------------------------------------------------------- 1 | 1.2.0 2 | -------------------------------------------------------------------------------- /src/GlobalAssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | #if DEBUG 4 | [assembly: AssemblyConfiguration ("DEBUG")] 5 | #endif 6 | #if RELEASE 7 | [assembly: AssemblyConfiguration ("RELEASE")] 8 | #endif 9 | 10 | #pragma warning disable 0436 11 | [assembly: AssemblyTitle(ThisAssembly.Project.AssemblyName)] 12 | [assembly: AssemblyDescription(ThisAssembly.Project.AssemblyName)] 13 | [assembly: AssemblyCompany("Mobile Essentials")] 14 | [assembly: AssemblyProduct("Merq")] 15 | [assembly: AssemblyCopyright("Copyright © Mobile Essentials 2016")] 16 | 17 | [assembly: AssemblyVersion(ThisAssembly.Git.BaseVersion.Major + "." + ThisAssembly.Git.BaseVersion.Minor + ".0")] 18 | [assembly: AssemblyFileVersion(ThisAssembly.Git.SemVer.Major + "." + ThisAssembly.Git.SemVer.Minor + "." + ThisAssembly.Git.SemVer.Patch)] 19 | [assembly: AssemblyInformationalVersion(ThisAssembly.Git.SemVer.Major + "." + ThisAssembly.Git.SemVer.Minor + "." + ThisAssembly.Git.SemVer.Patch + "-" + ThisAssembly.Git.Branch + "+" + ThisAssembly.Git.Commit)] 20 | #pragma warning restore 0436 -------------------------------------------------------------------------------- /src/Installers/Merq.Installer.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Mobile Essentials 6 | 7 | 8 | ICE61 9 | 5151 10 | 1076;1103;1102 11 | true 12 | 13 | 14 | false 15 | 16 | 17 | high 18 | $(TempDir.TrimEnd('\')) 19 | true 20 | 4 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Installers/Merq.Installer.targets: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | Manufacturer=$(Manufacturer); 9 | ProductVersion=$(Version); 10 | IntermediateOutputPath=$(IntermediateOutputPath); 11 | ProductIcon=..\..\..\icon\Merq.ico; 12 | $(DefineConstants) 13 | 14 | 15 | 16 | 17 | 18 | 19 | $(GitSemVerMajor).$(GitSemVerMinor).$(GitSemVerPatch).0 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Installers/Merq.Lib/Directories.wxs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/Installers/Merq.Lib/Library.wxs: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 49 | 50 | VS2012_ROOT_FOLDER 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | VS2013_ROOT_FOLDER 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | VS2015_ROOT_FOLDER 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | VS15_ROOT_FOLDER 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /src/Installers/Merq.Lib/Merq.Lib.wixproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 2.0 6 | 3.10 7 | Debug 8 | x86 9 | 859ed305-ee37-48d6-a1b6-f5d7b30317f1 10 | Merq 11 | Library 12 | obj\$(Configuration)\ 13 | bin\$(Configuration)\ 14 | True 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | $(WixExtDir)WixVSExtension.dll 28 | WixVSExtension 29 | 30 | 31 | $(WixExtDir)WixUIExtension.dll 32 | WixUIExtension 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | Merq.Vsix 41 | {c9a505ca-f826-473a-8290-b36af6be56fb} 42 | True 43 | True 44 | Binaries;Content;Satellites 45 | INSTALLFOLDER 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/Installers/Merq.Lib/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/Installers/Merq.Msi/Merq.Msi.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Merq.Msi=$(MSBuildThisFileDirectory)..\tools\Merq.msi; 7 | Merq.Vsix=$(MSBuildThisFileDirectory)..\..\Vsix\Merq.Vsix\bin\$(Configuration)\Merq.vsix; 8 | Merq.Include=$(MSBuildThisFileDirectory)Merq.wxi; 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Installers/Merq.Msi/Merq.Msi.wixproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 2.0 6 | 3.10 7 | f4fca9e5-f9ab-4fb5-b239-f4274c45407b 8 | Debug 9 | x86 10 | 3.10 11 | Merq 12 | Package 13 | bin\$(Configuration)\ 14 | obj\$(Configuration)\ 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | $(WixExtDir)WixUIExtension.dll 27 | WixUIExtension 28 | 29 | 30 | $(WixExtDir)WixVSExtension.dll 31 | WixVSExtension 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/Installers/Merq.Msi/Merq.wxi: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 54 | 55 | VS2012_ROOT_FOLDER 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | VS2013_ROOT_FOLDER 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | VS2015_ROOT_FOLDER 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | VS2017_ROOT_FOLDER 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/Installers/Merq.Msi/Product.wxs: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | NOT VS2012_ROOT_FOLDER 15 | 16 | 17 | 18 | NOT VS2013_ROOT_FOLDER 19 | 20 | 21 | 22 | NOT VS2015_ROOT_FOLDER 23 | 24 | 25 | 26 | NOT VS15_ROOT_FOLDER 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 1 58 | 59 | NOT Installed 60 | Installed AND PATCH 61 | 62 | Installed 63 | NOT Installed 64 | 1 65 | 66 | NOT Installed OR WixUI_InstallMode = "Change" 67 | Installed AND NOT PATCH 68 | Installed AND PATCH 69 | 70 | 1 71 | 72 | 1 73 | 1 74 | 1 75 | 1 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /src/Installers/Merq.Msi/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/Installers/SampleBundle/Bundle.wxs: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/Installers/SampleBundle/Resources/1033/ClassicTheme.wxl: -------------------------------------------------------------------------------- 1 | 2 | 3 | [WixBundleName] Setup 4 | [WixBundleName] 5 | Are you sure you want to cancel? 6 | Setup Help 7 | /install | /repair | /uninstall | /layout [directory] - installs, repairs, uninstalls or 8 | creates a complete local copy of the bundle in directory. Install is the default. 9 | 10 | /passive | /quiet - displays minimal UI with no prompts or displays no UI and 11 | no prompts. By default UI and all prompts are displayed. 12 | 13 | /norestart - suppress any attempts to restart. By default UI will prompt before restart. 14 | /log log.txt - logs to a specific file. By default a log file is created in %TEMP%. 15 | &Close 16 | I &agree to the license terms and conditions 17 | &Options 18 | &Install 19 | &Close 20 | Version [WixBundleVersion] 21 | Setup Options 22 | Install location: 23 | &Browse 24 | &OK 25 | &Cancel 26 | Setup Progress 27 | Processing: 28 | Initializing... 29 | &Cancel 30 | Modify Setup 31 | &Repair 32 | &Uninstall 33 | &Close 34 | Setup Successful 35 | &Launch 36 | You must restart your computer before you can use the software. 37 | &Restart 38 | &Close 39 | Setup Failed 40 | One or more issues caused the setup to fail. Please fix the issues and then retry setup. For more information see the <a href="#">log file</a>. 41 | You must restart your computer to complete the rollback of the software. 42 | &Restart 43 | &Close 44 | [WixBundleName] will be installed on your device 45 | By installing you accept these <a href="#">license terms</a> 46 | 47 | -------------------------------------------------------------------------------- /src/Installers/SampleBundle/Resources/3082/ClassicTheme.wxl: -------------------------------------------------------------------------------- 1 | 2 | 3 | Programa de instalación de [WixBundleName] 4 | [WixBundleName] 5 | ¿Esta seguro que desea cerrar el programa de instalación? 6 | Ayuda 7 | 8 | /install | /repair | /uninstall | /layout [directory] - instala, repara, desinstala or 9 | crea una copia local completa del paquete en el directorio. Instalar es el valor predeterminado. 10 | 11 | /passive | /quiet - muestra la interfaz de usuario mínima sin mensajes o no 12 | muestra la interfaz de usuario ni indicaciones. Por defecto la interfaz de usuario y todos los avisos se muestran. 13 | 14 | /norestart - suprimir cualquier intento de reiniciar el equipo. Por defecto la interfaz de usuario se pregunta antes de reiniciar. 15 | /log log.txt - guarda el registro de eventos en un archivo especifico. Por defecto se crea un archivo de registro en %TEMP%. 16 | &Cerrar 17 | Acep&to los terminos y condiciones 18 | &Opciones 19 | &Instalar 20 | &Cerrar 21 | Version [WixBundleVersion] 22 | Opciones de instalación 23 | Carpeta de instalación: 24 | &Examinar 25 | &Aceptar 26 | &Cancelar 27 | Progreso de la instalación 28 | Procesando: 29 | Preparando los componentes... 30 | &Cancelar 31 | Reparar ó desinstalar 32 | &Reparar 33 | &Desinstalar 34 | &Cerrar 35 | Finalizado correctamente 36 | &Iniciar 37 | Debe reiniciar su equipo antes de iniciar [WixBundleName]. 38 | &Reiniciar 39 | &Cerrar 40 | Instalación fallida 41 | Uno o más problemas causaron la que instalación fallara. Para obtener más información, consulte el <a href="#">registro de sucesos</a>. 42 | Debe reiniciar el equipo para completar la reversión de [WixBundleName]. 43 | &Reiniciar 44 | &Cerrar 45 | [WixBundleName] sera instalado en su equipo 46 | Al instalar acepta <a href="#">terminos y condiciones</a> 47 | 48 | -------------------------------------------------------------------------------- /src/Installers/SampleBundle/Resources/ClassicTheme.wxl: -------------------------------------------------------------------------------- 1 | 2 | 3 | [WixBundleName] Setup 4 | [WixBundleName] 5 | Are you sure you want to cancel? 6 | Setup Help 7 | /install | /repair | /uninstall | /layout [directory] - installs, repairs, uninstalls or 8 | creates a complete local copy of the bundle in directory. Install is the default. 9 | 10 | /passive | /quiet - displays minimal UI with no prompts or displays no UI and 11 | no prompts. By default UI and all prompts are displayed. 12 | 13 | /norestart - suppress any attempts to restart. By default UI will prompt before restart. 14 | /log log.txt - logs to a specific file. By default a log file is created in %TEMP%. 15 | &Close 16 | I &agree to the license terms and conditions 17 | &Options 18 | &Install 19 | &Close 20 | Version [WixBundleVersion] 21 | Setup Options 22 | Install location: 23 | &Browse 24 | &OK 25 | &Cancel 26 | Setup Progress 27 | Processing: 28 | Initializing... 29 | &Cancel 30 | Modify Setup 31 | &Repair 32 | &Uninstall 33 | &Close 34 | Setup Successful 35 | &Launch 36 | You must restart your computer before you can use the software. 37 | &Restart 38 | &Close 39 | Setup Failed 40 | One or more issues caused the setup to fail. Please fix the issues and then retry setup. For more information see the <a href="#">log file</a>. 41 | You must restart your computer to complete the rollback of the software. 42 | &Restart 43 | &Close 44 | [WixBundleName] will be installed on your device 45 | By installing you accept these <a href="#">license terms</a> 46 | 47 | -------------------------------------------------------------------------------- /src/Installers/SampleBundle/Resources/ClassicTheme.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #(loc.Caption) 4 | Segoe UI 5 | Segoe UI 6 | Segoe UI 7 | Segoe UI 8 | Segoe UI 9 | Segoe UI 10 | Segoe UI 11 | 12 | 13 | 14 | #(loc.WillInstall) 15 | #(loc.InstallLicenseLinkText) 16 | #(loc.InstallVersion) 17 | 18 | 19 | 20 | 21 | #(loc.ProgressHeader) 22 | #(loc.ProgressLabel) 23 | #(loc.OverallProgressPackageText) 24 | 25 | 26 | 27 | 28 | #(loc.ModifyHeader) 29 | 30 | 31 | 32 | 33 | 34 | #(loc.SuccessHeader) 35 | 36 | #(loc.SuccessRestartText) 37 | 38 | 39 | 40 | 41 | #(loc.FailureHeader) 42 | #(loc.FailureHyperlinkLogText) 43 | 44 | #(loc.FailureRestartText) 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/Installers/SampleBundle/Resources/banner.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileEssentials/Merq/34d6af216bf5c72f0bc3fa796607c83ae9a42fae/src/Installers/SampleBundle/Resources/banner.bmp -------------------------------------------------------------------------------- /src/Installers/SampleBundle/Resources/banner.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileEssentials/Merq/34d6af216bf5c72f0bc3fa796607c83ae9a42fae/src/Installers/SampleBundle/Resources/banner.pdn -------------------------------------------------------------------------------- /src/Installers/SampleBundle/SampleBundle.wixproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 2.0 6 | 3.10 7 | {E932A2E0-2594-4B6A-9C65-97718C1C1B42} 8 | Debug 9 | x86 10 | SampleBundle 11 | Bundle 12 | bin\$(Configuration)\ 13 | obj\$(Configuration)\ 14 | WiX 15 | $(ProductVersion) 16 | $(TargetFrameworkIdentifier),Version=v$(TargetFrameworkVersion) 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | $(WixExtDir)WixBalExtension.dll 29 | WixBalExtension 30 | 31 | 32 | $(WixExtDir)\WixUtilExtension.dll 33 | WixUtilExtension 34 | 35 | 36 | $(WixExtDir)\WixDependencyExtension.dll 37 | WixDependencyExtension 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | Merq.Msi 48 | {f4fca9e5-f9ab-4fb5-b239-f4274c45407b} 49 | True 50 | True 51 | Binaries;Content;Satellites 52 | INSTALLFOLDER 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/Installers/SampleBundle/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/Installers/SampleExtension/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("SampleExtension")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("SampleExtension")] 12 | [assembly: AssemblyCopyright("")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // Version information for an assembly consists of the following four values: 22 | // 23 | // Major Version 24 | // Minor Version 25 | // Build Number 26 | // Revision 27 | // 28 | // You can specify all the values or you can default the Build and Revision Numbers 29 | // by using the '*' as shown below: 30 | // [assembly: AssemblyVersion("1.0.*")] 31 | [assembly: AssemblyVersion("1.0.0.0")] 32 | [assembly: AssemblyFileVersion("1.0.0.0")] 33 | -------------------------------------------------------------------------------- /src/Installers/SampleExtension/SampleExtension.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 2.0 8 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 9 | {690FA191-9900-4505-AFB2-A7CC1A9BE12B} 10 | Library 11 | Properties 12 | SampleExtension 13 | SampleExtension 14 | v4.5 15 | false 16 | true 17 | full 18 | false 19 | bin\$(Configuration)\ 20 | TRACE 21 | prompt 22 | 4 23 | 14.0 24 | 12.0 25 | 26 | 27 | DEBUG;$(DefineConstants) 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | Designer 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/Installers/SampleExtension/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "Microsoft.VisualStudio.SDK.VsixSuppression": "14.1.25", 4 | "Microsoft.VSSDK.BuildTools": "14.3.25420", 5 | "MSBuilder.CodeTaskAssembly": "0.2.4", 6 | "MSBuilder.DumpItems": "0.2.1", 7 | "MSBuilder.ThisAssembly.Project": "0.3.1", 8 | "MSBuilder.VsixDependency": "0.2.0" 9 | }, 10 | "frameworks": { 11 | "net45": {} 12 | }, 13 | "runtimes": { 14 | "win": {} 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Installers/SampleExtension/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Merq.SampleExtension 6 | Sample extension embedding Merq VSIX installer. 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Installers/build/Merq.VisualStudio.WiX.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Merq.Msi=$(MSBuildThisFileDirectory)..\tools\Merq.msi; 7 | Merq.Vsix=$(MSBuildThisFileDirectory)..\tools\Merq.vsix; 8 | Merq.Include=$(MSBuildThisFileDirectory)..\tools\Merq.wxi; 9 | $(DefineConstants) 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | $(WixExtDir)WixVSExtension.dll 26 | WixVSExtension 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/Installers/build/Merq.VisualStudio.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/Installers/build/Merq.VisualStudio.targets: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | @(MerqVsix -> '%(FullPath)') 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Installers/build/Readme.txt: -------------------------------------------------------------------------------- 1 | This NuGet package targets VSIX projects that embed the Merq VSIX or WiX Toolset 2 | projects (.wixproj) that build MSIs or chained installers (with Burn). 3 | 4 | It provides the installer for the Merq VSIX that provides the MEF components 5 | to consume ICommandBus or IEventStream from Visual Studio 2012 or later. 6 | 7 | == VSIX Projects == 8 | 9 | If your project's output is a VSIX, installing this NuGet package will automatically 10 | add the Merq.vsix file as a Content item with CopyToOutputDirectory=PreserveNewest and 11 | IncludeInVsix=true, as well as add it as an embedded VSIX dependency, like so: 12 | 13 | 15 | 16 | == MSI Projects == 17 | 18 | If your project's output is an MSI (OutputType=Package), installing this NuGet 19 | package will add the necessary WixVSExtension reference and WixLibrary reference. 20 | 21 | However, you must define the following minimal requirements in order to get it 22 | properly bundled and installed by your MSI: 23 | 24 | 1 - Your MSI must define these directories to satisfy symbol references in the .wixlib: 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | Alternatively, you can reference an already declared TARGETDIR: 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | You must also reference this component as part of one of your features: 47 | 48 | 49 | 50 | 51 | == Burn/Chained installer Projects == 52 | 53 | If your project's output is an chained installer bundle (OutputType=Bundle), you 54 | just need to reference the included MSI as follows from your Chain: 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/Merq.VisualStudio.proj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | netcore50 10 | 11 | PackageReference 12 | 13 | $(MSBuildThisFileDirectory).nuget 14 | Release 15 | 16 | 17 | 18 | 19 | 20 | true 21 | false 22 | false 23 | 24 | Merq.VisualStudio 25 | Merq.VisualStudio: Command Bus + Event Stream 26 | Command Bus and Event Stream implementation installers for Visual Studio Extensibility. 27 | Command Bus and Event Stream implementation installers for Visual Studio Extensibility. 28 | Copyright 2016 © Mobile Essentials 29 | 30 | $(Out) 31 | ..\out 32 | 33 | 34 | 35 | 36 | lib\_._ 37 | 38 | 39 | build 40 | 41 | 42 | tools 43 | 44 | 45 | tools 46 | 47 | 48 | tools 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | $(Version) 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | $(MSBuildExtensionsPath)\..\Common7\IDE\CommonExtensions\Microsoft\NuGet\NuGet.targets 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /src/Merq.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileEssentials/Merq/34d6af216bf5c72f0bc3fa796607c83ae9a42fae/src/Merq.key -------------------------------------------------------------------------------- /src/Merq.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | bin\$(Configuration)\ 8 | prompt 9 | 4 10 | true 11 | Properties 12 | true 13 | full 14 | TRACE 15 | 512 16 | CS1570 17 | $(OutputPath)$(AssemblyName).xml 18 | true 19 | false 20 | $(MSBuildThisFileDirectory)Merq.snk 21 | true 22 | false 23 | $(OutputPath) 24 | $([System.IO.Path]::ChangeExtension($(MSBuildProjectFullPath), '.nuspec')) 25 | 26 | low 27 | true 28 | true 29 | true 30 | true 31 | 32 | 0.0.0 33 | 34 | 35 | 36 | false 37 | DEBUG;$(DefineConstants) 38 | 39 | 40 | 41 | true 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/Merq.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30001.183 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{235AA06D-5767-49D1-88F3-F25383FE1CAA}" 7 | ProjectSection(SolutionItems) = preProject 8 | .editorconfig = .editorconfig 9 | ..\azure-pipelines.yml = ..\azure-pipelines.yml 10 | Directory.Build.props = Directory.Build.props 11 | Directory.Build.targets = Directory.Build.targets 12 | GlobalAssemblyInfo.cs = GlobalAssemblyInfo.cs 13 | Merq.props = Merq.props 14 | Merq.targets = Merq.targets 15 | Merq.VisualStudio.proj = Merq.VisualStudio.proj 16 | Merq.vssettings = Merq.vssettings 17 | NuGet.Config = NuGet.Config 18 | ..\README.md = ..\README.md 19 | Version.targets = Version.targets 20 | EndProjectSection 21 | EndProject 22 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{D01702F6-0CCA-4107-883D-57092F592644}" 23 | EndProject 24 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Vsix", "Vsix", "{514F4A5F-3C2E-4137-BD6E-4B3A30A68879}" 25 | EndProject 26 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Merq", "Core\Merq\Merq.csproj", "{C6B16D17-4F6A-4457-8497-92068E53DE39}" 27 | EndProject 28 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Merq.Core.Tests", "Core\Merq.Core.Tests\Merq.Core.Tests.csproj", "{3A395046-3F7B-4907-9D63-48F92174D2E5}" 29 | EndProject 30 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Merq.Core", "Core\Merq.Core\Merq.Core.csproj", "{33BDD9D1-3E01-49C1-AB46-50941DFFD5D4}" 31 | EndProject 32 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Merq.Vsix", "Vsix\Merq.Vsix\Merq.Vsix.csproj", "{C9A505CA-F826-473A-8290-B36AF6BE56FB}" 33 | EndProject 34 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Merq.Vsix.Tests", "Vsix\Merq.Vsix.Tests\Merq.Vsix.Tests.csproj", "{882DCF88-ADA6-4425-8E57-8EA4B3201DD5}" 35 | EndProject 36 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Merq.Vsix.IntegrationTests", "Vsix\Merq.Vsix.IntegrationTests\Merq.Vsix.IntegrationTests.csproj", "{571C5326-8221-4BB0-A5B3-DE0BBEC15F74}" 37 | EndProject 38 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Installers", "Installers", "{EA718D24-7B8F-4AF0-9C81-9378954CEE65}" 39 | EndProject 40 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{2763056B-E6F5-4AC7-B2F2-B3AF97359EA1}" 41 | ProjectSection(SolutionItems) = preProject 42 | Installers\build\Merq.VisualStudio.props = Installers\build\Merq.VisualStudio.props 43 | Installers\build\Merq.VisualStudio.targets = Installers\build\Merq.VisualStudio.targets 44 | Installers\build\Merq.VisualStudio.WiX.targets = Installers\build\Merq.VisualStudio.WiX.targets 45 | Installers\build\Readme.txt = Installers\build\Readme.txt 46 | EndProjectSection 47 | EndProject 48 | Global 49 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 50 | Debug|Any CPU = Debug|Any CPU 51 | Release|Any CPU = Release|Any CPU 52 | EndGlobalSection 53 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 54 | {C6B16D17-4F6A-4457-8497-92068E53DE39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {C6B16D17-4F6A-4457-8497-92068E53DE39}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {C6B16D17-4F6A-4457-8497-92068E53DE39}.Release|Any CPU.ActiveCfg = Release|Any CPU 57 | {C6B16D17-4F6A-4457-8497-92068E53DE39}.Release|Any CPU.Build.0 = Release|Any CPU 58 | {3A395046-3F7B-4907-9D63-48F92174D2E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 59 | {3A395046-3F7B-4907-9D63-48F92174D2E5}.Debug|Any CPU.Build.0 = Debug|Any CPU 60 | {3A395046-3F7B-4907-9D63-48F92174D2E5}.Release|Any CPU.ActiveCfg = Release|Any CPU 61 | {3A395046-3F7B-4907-9D63-48F92174D2E5}.Release|Any CPU.Build.0 = Release|Any CPU 62 | {33BDD9D1-3E01-49C1-AB46-50941DFFD5D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 63 | {33BDD9D1-3E01-49C1-AB46-50941DFFD5D4}.Debug|Any CPU.Build.0 = Debug|Any CPU 64 | {33BDD9D1-3E01-49C1-AB46-50941DFFD5D4}.Release|Any CPU.ActiveCfg = Release|Any CPU 65 | {33BDD9D1-3E01-49C1-AB46-50941DFFD5D4}.Release|Any CPU.Build.0 = Release|Any CPU 66 | {C9A505CA-F826-473A-8290-B36AF6BE56FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 67 | {C9A505CA-F826-473A-8290-B36AF6BE56FB}.Debug|Any CPU.Build.0 = Debug|Any CPU 68 | {C9A505CA-F826-473A-8290-B36AF6BE56FB}.Release|Any CPU.ActiveCfg = Release|Any CPU 69 | {C9A505CA-F826-473A-8290-B36AF6BE56FB}.Release|Any CPU.Build.0 = Release|Any CPU 70 | {882DCF88-ADA6-4425-8E57-8EA4B3201DD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 71 | {882DCF88-ADA6-4425-8E57-8EA4B3201DD5}.Debug|Any CPU.Build.0 = Debug|Any CPU 72 | {882DCF88-ADA6-4425-8E57-8EA4B3201DD5}.Release|Any CPU.ActiveCfg = Release|Any CPU 73 | {882DCF88-ADA6-4425-8E57-8EA4B3201DD5}.Release|Any CPU.Build.0 = Release|Any CPU 74 | {571C5326-8221-4BB0-A5B3-DE0BBEC15F74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 75 | {571C5326-8221-4BB0-A5B3-DE0BBEC15F74}.Debug|Any CPU.Build.0 = Debug|Any CPU 76 | {571C5326-8221-4BB0-A5B3-DE0BBEC15F74}.Release|Any CPU.ActiveCfg = Release|Any CPU 77 | {571C5326-8221-4BB0-A5B3-DE0BBEC15F74}.Release|Any CPU.Build.0 = Release|Any CPU 78 | EndGlobalSection 79 | GlobalSection(SolutionProperties) = preSolution 80 | HideSolutionNode = FALSE 81 | EndGlobalSection 82 | GlobalSection(NestedProjects) = preSolution 83 | {C6B16D17-4F6A-4457-8497-92068E53DE39} = {D01702F6-0CCA-4107-883D-57092F592644} 84 | {3A395046-3F7B-4907-9D63-48F92174D2E5} = {D01702F6-0CCA-4107-883D-57092F592644} 85 | {33BDD9D1-3E01-49C1-AB46-50941DFFD5D4} = {D01702F6-0CCA-4107-883D-57092F592644} 86 | {C9A505CA-F826-473A-8290-B36AF6BE56FB} = {514F4A5F-3C2E-4137-BD6E-4B3A30A68879} 87 | {882DCF88-ADA6-4425-8E57-8EA4B3201DD5} = {514F4A5F-3C2E-4137-BD6E-4B3A30A68879} 88 | {571C5326-8221-4BB0-A5B3-DE0BBEC15F74} = {514F4A5F-3C2E-4137-BD6E-4B3A30A68879} 89 | {2763056B-E6F5-4AC7-B2F2-B3AF97359EA1} = {EA718D24-7B8F-4AF0-9C81-9378954CEE65} 90 | EndGlobalSection 91 | GlobalSection(ExtensibilityGlobals) = postSolution 92 | SolutionGuid = {5F401F53-1497-4C3F-BA2B-915F7560AF17} 93 | EndGlobalSection 94 | EndGlobal 95 | -------------------------------------------------------------------------------- /src/Merq.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileEssentials/Merq/34d6af216bf5c72f0bc3fa796607c83ae9a42fae/src/Merq.snk -------------------------------------------------------------------------------- /src/Merq.vssettings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 2 8 | 4 9 | true 10 | 4 11 | 12 | 13 | 4 14 | 4 15 | true 16 | 17 | 18 | 1 19 | 1 20 | 1 21 | 0 22 | 1 23 | 0 24 | 1 25 | 1 26 | 0 27 | 1 28 | 1 29 | 0 30 | 0 31 | 0 32 | 0 33 | 0 34 | 0 35 | 1 36 | 0 37 | 1 38 | 0 39 | 0 40 | 0 41 | 1 42 | 1 43 | 1 44 | 1 45 | 1 46 | 1 47 | 0 48 | 1 49 | 0 50 | 1 51 | 1 52 | 1 53 | 1 54 | 1 55 | 1 56 | 0 57 | 0 58 | 1 59 | 0 60 | 0 61 | 0 62 | 0 63 | 0 64 | 1 65 | 0 66 | 0 67 | 0 68 | 0 69 | 0 70 | 0 71 | 0 72 | 0 73 | 1 74 | 1 75 | 1 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /src/StaFact.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using System.Windows.Threading; 9 | using Xunit; 10 | using Xunit.Abstractions; 11 | using Xunit.Sdk; 12 | 13 | namespace Merq 14 | { 15 | public class StaFactDiscoverer : IXunitTestCaseDiscoverer 16 | { 17 | readonly FactDiscoverer factDiscoverer; 18 | 19 | public StaFactDiscoverer(IMessageSink diagnosticMessageSink) 20 | { 21 | factDiscoverer = new FactDiscoverer(diagnosticMessageSink); 22 | } 23 | 24 | public IEnumerable Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) 25 | { 26 | return factDiscoverer.Discover(discoveryOptions, testMethod, factAttribute) 27 | .Select(testCase => new StaTestCase(testCase)); 28 | } 29 | } 30 | 31 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] 32 | #pragma warning disable 0436 33 | [XunitTestCaseDiscoverer("Merq.StaFactDiscoverer", ThisAssembly.Project.AssemblyName)] 34 | #pragma warning restore 0436 35 | public class StaFactAttribute : FactAttribute { } 36 | 37 | /// 38 | /// Wraps test cases for FactAttribute and TheoryAttribute so the test case runs on the WPF STA thread 39 | /// 40 | [DebuggerDisplay(@"\{ class = {TestMethod.TestClass.Class.Name}, method = {TestMethod.Method.Name}, display = {DisplayName}, skip = {SkipReason} \}")] 41 | public class StaTestCase : LongLivedMarshalByRefObject, IXunitTestCase 42 | { 43 | IXunitTestCase testCase; 44 | 45 | public StaTestCase(IXunitTestCase testCase) 46 | { 47 | this.testCase = testCase; 48 | } 49 | 50 | /// 51 | [EditorBrowsable(EditorBrowsableState.Never)] 52 | [Obsolete("Called by the de-serializer", error: true)] 53 | public StaTestCase() { } 54 | 55 | public IMethodInfo Method => testCase.Method; 56 | 57 | public Task RunAsync(IMessageSink diagnosticMessageSink, 58 | IMessageBus messageBus, 59 | object[] constructorArguments, 60 | ExceptionAggregator aggregator, 61 | CancellationTokenSource cancellationTokenSource) 62 | { 63 | var tcs = new TaskCompletionSource(); 64 | var thread = new Thread(() => 65 | { 66 | try 67 | { 68 | // Set up the SynchronizationContext so that any awaits 69 | // resume on the STA thread as they would in a GUI app. 70 | SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext()); 71 | 72 | // Start off the test method. 73 | var testCaseTask = this.testCase.RunAsync(diagnosticMessageSink, messageBus, constructorArguments, aggregator, cancellationTokenSource); 74 | 75 | // Arrange to pump messages to execute any async work associated with the test. 76 | var frame = new DispatcherFrame(); 77 | Task.Run(async delegate 78 | { 79 | try 80 | { 81 | await testCaseTask; 82 | } 83 | finally 84 | { 85 | // The test case's execution is done. Terminate the message pump. 86 | frame.Continue = false; 87 | } 88 | }); 89 | Dispatcher.PushFrame(frame); 90 | 91 | // Report the result back to the Task we returned earlier. 92 | CopyTaskResultFrom(tcs, testCaseTask); 93 | } 94 | catch (Exception e) 95 | { 96 | tcs.SetException(e); 97 | } 98 | }); 99 | 100 | thread.SetApartmentState(ApartmentState.STA); 101 | thread.Start(); 102 | return tcs.Task; 103 | } 104 | 105 | public string DisplayName => testCase.DisplayName; 106 | 107 | public string SkipReason => testCase.SkipReason; 108 | 109 | public ISourceInformation SourceInformation 110 | { 111 | get { return testCase.SourceInformation; } 112 | set { testCase.SourceInformation = value; } 113 | } 114 | 115 | public ITestMethod TestMethod => testCase.TestMethod; 116 | 117 | public object[] TestMethodArguments => testCase.TestMethodArguments; 118 | 119 | public Dictionary> Traits => testCase.Traits; 120 | 121 | public string UniqueID => testCase.UniqueID; 122 | 123 | public void Deserialize(IXunitSerializationInfo info) 124 | { 125 | testCase = info.GetValue("InnerTestCase"); 126 | } 127 | 128 | public void Serialize(IXunitSerializationInfo info) 129 | { 130 | info.AddValue("InnerTestCase", testCase); 131 | } 132 | 133 | static void CopyTaskResultFrom(TaskCompletionSource tcs, Task template) 134 | { 135 | if (tcs == null) 136 | throw new ArgumentNullException(nameof(tcs)); 137 | if (template == null) 138 | throw new ArgumentNullException(nameof(template)); 139 | if (!template.IsCompleted) 140 | throw new ArgumentException("Task must be completed first.", nameof(template)); 141 | 142 | if (template.IsFaulted) 143 | tcs.SetException(template.Exception); 144 | else if (template.IsCanceled) 145 | tcs.SetCanceled(); 146 | else 147 | tcs.SetResult(template.Result); 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/Version.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | SetVersion;$(GenerateNuspecDependsOn) 10 | SetVersion;$(GetPackageVersionDependsOn) 11 | 12 | 13 | 14 | 15 | 17 | 18 | 19 | 20 | 21 | 22 | $([MSBuild]::Add('$(GitCommits)', '1')) 23 | 24 | 25 | 26 | 27 | 31 | 32 | 33 | 34 | $(GitSemVerDashLabel)-pr$(BUILD_SOURCEBRANCH.Substring(10).TrimEnd('/merge')) 35 | $(GitSemVerDashLabel)-pr$(APPVEYOR_PULL_REQUEST_NUMBER) 36 | 37 | 38 | $(SYSTEM_PULLREQUEST_TARGETBRANCH) 39 | $(BUILD_SOURCEBRANCHNAME) 40 | $(APPVEYOR_REPO_BRANCH) 41 | 42 | 46 | <_IndexOfBranchSlash>$(GitBranch.LastIndexOf('/')) 47 | <_IndexOfBranchSubstring>$([MSBuild]::Add('$(_IndexOfBranchSlash)', '1')) 48 | <_GitBranch Condition="'$(_IndexOfBranchSlash)' != '0'">$(GitBranch.Substring($(_IndexOfBranchSubstring))) 49 | <_GitBranch Condition="'$(_IndexOfBranchSlash)' == '0'">$(GitBranch) 50 | 51 | 52 | -$(_GitBranch) 53 | $(_GitBranch). 54 | $(SemVerMetadata)sha.$(GitCommit) 55 | 56 | 57 | $(GitBaseVersionMajor).$(GitBaseVersionMinor).$(GitBaseVersionPatch)$(GitSemVerDashLabel).$(GitCommits) 58 | 59 | $(GitSemVerMajor).$(GitSemVerMinor).$(GitSemVerPatch) 60 | $(GitBaseVersionMajor).$(GitBaseVersionMinor).$(GitBaseVersionPatch) 61 | $(GitSemVerMajor).$(GitSemVerMinor).$(GitSemVerPatch) 62 | $(PackageVersion)+$(SemVerMetadata) 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/Vsix/Merq.Vsix.IntegrationTests/ComponentsSpec.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.Composition; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | using Microsoft.ComponentModel.Composition.Diagnostics; 9 | using Microsoft.VisualStudio.ComponentModelHost; 10 | using Xunit; 11 | using Xunit.Abstractions; 12 | 13 | namespace Merq 14 | { 15 | public class ComponentsSpec 16 | { 17 | ITestOutputHelper output; 18 | 19 | public ComponentsSpec(ITestOutputHelper output) 20 | { 21 | this.output = output; 22 | } 23 | 24 | [InlineData(typeof(ICommandBus))] 25 | [InlineData(typeof(IEventStream))] 26 | [VsixTheory] 27 | public void when_retrieving_exports_then_reports_duplicate_services(Type serviceType) 28 | { 29 | var componentModel = GlobalServices.GetService(); 30 | var contractName = AttributedModelServices.GetContractName(serviceType); 31 | var components = componentModel.DefaultExportProvider 32 | .GetExports>(contractName) 33 | .ToArray(); 34 | 35 | if (components.Length != 1) 36 | { 37 | var info = new CompositionInfo(componentModel.DefaultCatalog, componentModel.DefaultExportProvider); 38 | var log = Path.GetTempFileName(); 39 | using (var writer = new StreamWriter(log)) 40 | { 41 | CompositionInfoTextFormatter.Write(info, writer); 42 | writer.Flush(); 43 | } 44 | 45 | output.WriteLine(log); 46 | // Process.Start(new ProcessStartInfo("notepad", log) { UseShellExecute = true }); 47 | 48 | Assert.False(true, $"Expected only one component of {serviceType.Name}. Composition log at {log}"); 49 | } 50 | } 51 | 52 | [VsixFact] 53 | public void when_subscribing_and_pushing_events_then_succeeds() 54 | { 55 | var stream = GlobalServices.GetService().GetService(); 56 | var expected = new FooEvent(); 57 | 58 | FooEvent actual = null; 59 | 60 | var subscription = stream.Of().Subscribe(e => actual = e); 61 | 62 | stream.Push(expected); 63 | 64 | Assert.Same(expected, actual); 65 | } 66 | 67 | [VsixFact] 68 | public void when_querying_command_bus_for_handler_then_succeeds() 69 | { 70 | var commands = GlobalServices.GetService().GetService(); 71 | 72 | Assert.False(commands.CanHandle(new FooCommand())); 73 | Assert.False(commands.CanHandle()); 74 | } 75 | 76 | public class FooCommand : ICommand { } 77 | 78 | public class FooAsyncCommand : IAsyncCommand { } 79 | 80 | public class FooEvent { } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Vsix/Merq.Vsix.IntegrationTests/Merq.Vsix.IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | net472 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ..\..\..\lib\Microsoft.ComponentModel.Composition.Diagnostics.dll 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/Vsix/Merq.Vsix.Tests/CommandBusComponentSpec.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using System.Linq; 6 | using Moq; 7 | using Xunit; 8 | using Microsoft.VisualStudio.ComponentModelHost; 9 | 10 | namespace Merq 11 | { 12 | public class CommandBusComponentSpec 13 | { 14 | [Fact] 15 | public void when_instantiating_with_service_provider_then_uses_component_model_service() 16 | { 17 | new CommandBusComponent(Mock.Of(x => 18 | x.GetService(typeof(SComponentModel)) == Mock.Of())); 19 | } 20 | 21 | [Fact] 22 | public void when_executing_command_without_handler_then_throws() 23 | { 24 | var bus = new CommandBusComponent(Mock.Of()); 25 | 26 | Assert.Throws(() => bus.Execute(new Command())); 27 | } 28 | 29 | [Fact] 30 | public void when_executing_command_with_result_without_handler_then_throws() 31 | { 32 | var bus = new CommandBusComponent(Mock.Of()); 33 | 34 | Assert.Throws(() => bus.Execute(new CommandWithResult())); 35 | } 36 | 37 | [Fact] 38 | public async Task when_executing_async_command_without_handler_then_throws() 39 | { 40 | var bus = new CommandBusComponent(Mock.Of()); 41 | 42 | await Assert.ThrowsAsync(() => bus.ExecuteAsync(new AsyncCommand(), CancellationToken.None)); 43 | } 44 | 45 | [Fact] 46 | public async Task when_executing_async_command_with_result_without_handler_then_throws() 47 | { 48 | var bus = new CommandBusComponent(Mock.Of()); 49 | 50 | await Assert.ThrowsAsync(() => bus.ExecuteAsync(new AsyncCommandWithResult(), CancellationToken.None)); 51 | } 52 | 53 | [Fact] 54 | public void when_can_handle_requested_for_non_registered_handler_then_returns_false() 55 | { 56 | var bus = new CommandBusComponent(Mock.Of()); 57 | 58 | Assert.False(bus.CanHandle()); 59 | } 60 | 61 | [Fact] 62 | public void when_can_handle_requested_for_registered_handler_type_then_returns_true() 63 | { 64 | var bus = new CommandBusComponent(Mock.Of(x => 65 | x.GetExtensions>() == new[] { Mock.Of>() })); 66 | 67 | Assert.True(bus.CanHandle()); 68 | } 69 | 70 | [Fact] 71 | public void when_can_handle_requested_for_registered_handler_instance_then_returns_true() 72 | { 73 | var bus = new CommandBusComponent(Mock.Of(x => 74 | x.GetExtensions>() == new[] { Mock.Of>() })); 75 | 76 | Assert.True(bus.CanHandle(new Command())); 77 | } 78 | 79 | [Fact] 80 | public void when_can_handle_requested_for_null_command_then_throws() 81 | { 82 | var bus = new CommandBusComponent(Mock.Of()); 83 | 84 | Assert.Throws(() => bus.CanHandle((Command)null)); 85 | } 86 | 87 | [Fact] 88 | public void when_can_execute_requested_and_no_handler_registered_then_returns_false() 89 | { 90 | var bus = new CommandBusComponent(Mock.Of()); 91 | 92 | Assert.False(bus.CanExecute(new Command())); 93 | } 94 | 95 | [Fact] 96 | public void when_can_execute_requested_then_invokes_sync_handler() 97 | { 98 | var command = new Command(); 99 | var bus = new CommandBusComponent(Mock.Of(x => 100 | x.GetExtensions>() == new[] { Mock.Of>(c => c.CanExecute(command) == true) })); 101 | 102 | Assert.True(bus.CanExecute(command)); 103 | } 104 | 105 | [Fact] 106 | public void when_can_execute_requested_then_invokes_async_handler() 107 | { 108 | var command = new AsyncCommand(); 109 | var bus = new CommandBusComponent(Mock.Of(x => 110 | x.GetExtensions>() == new[] { Mock.Of>(c => c.CanExecute(command) == true) })); 111 | 112 | Assert.True(bus.CanExecute(command)); 113 | } 114 | 115 | [Fact] 116 | public void when_executing_sync_command_then_invokes_sync_handler() 117 | { 118 | var handler = new Mock>(); 119 | var command = new Command(); 120 | 121 | var bus = new CommandBusComponent(Mock.Of(x => 122 | x.GetExtensions>() == new[] { handler.Object })); 123 | 124 | bus.Execute(command); 125 | 126 | handler.Verify(x => x.Execute(command)); 127 | } 128 | 129 | [Fact] 130 | public void when_executing_sync_command_then_invokes_sync_handler_with_result() 131 | { 132 | var handler = new Mock>(); 133 | var command = new CommandWithResult(); 134 | var bus = new CommandBusComponent(Mock.Of(x => 135 | x.GetExtensions>() == new[] { handler.Object })); 136 | 137 | bus.Execute(command); 138 | 139 | handler.Verify(x => x.Execute(command)); 140 | } 141 | 142 | [Fact] 143 | public void when_executing_sync_command_with_result_then_invokes_sync_handler_with_result() 144 | { 145 | var handler = new Mock>(); 146 | var command = new CommandWithResult(); 147 | var expected = new Result(); 148 | 149 | handler.Setup(x => x.Execute(command)).Returns(expected); 150 | var bus = new CommandBusComponent(Mock.Of(x => 151 | x.GetExtensions>() == new[] { handler.Object })); 152 | 153 | var result = bus.Execute(command); 154 | 155 | Assert.Same(expected, result); 156 | } 157 | 158 | [Fact] 159 | public async Task when_executing_async_command_then_invokes_async_handler() 160 | { 161 | var handler = new Mock>(); 162 | var command = new AsyncCommand(); 163 | 164 | handler.Setup(x => x.ExecuteAsync(command, CancellationToken.None)).Returns(Task.FromResult(true)); 165 | var bus = new CommandBusComponent(Mock.Of(x => 166 | x.GetExtensions>() == new[] { handler.Object })); 167 | 168 | await bus.ExecuteAsync(command, CancellationToken.None); 169 | 170 | handler.Verify(x => x.ExecuteAsync(command, CancellationToken.None)); 171 | } 172 | 173 | [Fact] 174 | public async Task when_executing_async_command_then_invokes_async_handler_with_result() 175 | { 176 | var handler = new Mock>(); 177 | var command = new AsyncCommandWithResult(); 178 | var result = new Result(); 179 | 180 | handler.Setup(x => x.ExecuteAsync(command, CancellationToken.None)).Returns(Task.FromResult(result)); 181 | var bus = new CommandBusComponent(Mock.Of(x => 182 | x.GetExtensions>() == new[] { handler.Object })); 183 | 184 | await bus.ExecuteAsync(command, CancellationToken.None); 185 | 186 | handler.Verify(x => x.ExecuteAsync(command, CancellationToken.None)); 187 | } 188 | 189 | 190 | [Fact] 191 | public void when_can_handle_with_null_command_then_throws() 192 | { 193 | Assert.Throws(() => new CommandBusComponent(Mock.Of()).CanHandle(null)); 194 | } 195 | 196 | [Fact] 197 | public void when_can_execute_with_null_command_then_throws() 198 | { 199 | Assert.Throws(() => new CommandBusComponent(Mock.Of()).CanExecute(null)); 200 | } 201 | 202 | [Fact] 203 | public void when_execute_with_null_command_then_throws() 204 | { 205 | Assert.Throws(() => new CommandBusComponent(Mock.Of()).Execute(default(Command))); 206 | } 207 | 208 | [Fact] 209 | public void when_execute_result_with_null_command_then_throws() 210 | { 211 | Assert.Throws(() => new CommandBusComponent(Mock.Of()).Execute(default(CommandWithResult))); 212 | } 213 | 214 | [Fact] 215 | public async Task when_executeasync_with_null_command_then_throws() 216 | { 217 | await Assert.ThrowsAsync(() => new CommandBusComponent(Mock.Of()).ExecuteAsync(default(AsyncCommand), CancellationToken.None)); 218 | } 219 | 220 | [Fact] 221 | public async Task when_executeasync_result_with_null_command_then_throws() 222 | { 223 | await Assert.ThrowsAsync(() => new CommandBusComponent(Mock.Of()).ExecuteAsync(default(AsyncCommandWithResult), CancellationToken.None)); 224 | } 225 | 226 | [Fact] 227 | public void when_executing_non_public_command_handler_then_invokes_handler_with_result() 228 | { 229 | var handler = new NonPublicCommandHandlerWithResults(new Result()); 230 | var bus = new CommandBusComponent(Mock.Of(x => 231 | x.GetExtensions>>() == new[] { handler })); 232 | 233 | var results = bus.Execute(new CommandWithResults()); 234 | 235 | Assert.Single(results); 236 | } 237 | 238 | [Fact] 239 | public void when_executing_command_as_explicit_ICommand_then_invokes_handler() 240 | { 241 | var handler = new Mock>(); 242 | var command = new Command(); 243 | var bus = new CommandBusComponent(Mock.Of(x => 244 | x.GetExtensions>() == new[] { handler.Object })); 245 | 246 | bus.Execute((ICommand)command); 247 | 248 | handler.Verify(x => x.Execute(command)); 249 | } 250 | 251 | [Fact] 252 | public void when_execute_command_throws_then_rethrows_original_exception() 253 | { 254 | var command = new Command(); 255 | var bus = new CommandBusComponent(Mock.Of(x => 256 | x.GetExtensions>() == new[] { new ThrowingCommandHandler() })); 257 | 258 | var ex = Assert.Throws(() => bus.Execute((ICommand)command)); 259 | 260 | Assert.Equal("Invalid", ex.Message); 261 | } 262 | 263 | public class ThrowingCommandHandler : ICommandHandler 264 | { 265 | public bool CanExecute(Command command) => true; 266 | 267 | public void Execute(Command command) 268 | { 269 | throw new InvalidOperationException("Invalid"); 270 | } 271 | } 272 | 273 | public class AsyncCommand : IAsyncCommand { } 274 | 275 | public class AsyncCommandWithResult : IAsyncCommand { } 276 | 277 | public class Command : ICommand { } 278 | 279 | public class CommandWithResult : ICommand { } 280 | 281 | public class CommandWithResults : ICommand> { } 282 | 283 | public class Result { } 284 | 285 | class NonPublicCommandHandlerWithResults : ICommandHandler> 286 | { 287 | Result result; 288 | 289 | public NonPublicCommandHandlerWithResults(Result result) 290 | { 291 | this.result = result; 292 | } 293 | 294 | bool ICanExecute.CanExecute(CommandWithResults command) 295 | { 296 | return true; 297 | } 298 | 299 | IEnumerable ICommandHandler>.Execute(CommandWithResults command) 300 | { 301 | yield return result; 302 | } 303 | } 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /src/Vsix/Merq.Vsix.Tests/EventStreamComponentSpec.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using System.Linq; 6 | using Moq; 7 | using Xunit; 8 | using Microsoft.VisualStudio.ComponentModelHost; 9 | using System.Reactive.Linq; 10 | 11 | namespace Merq 12 | { 13 | public class EventStreamComponentSpec 14 | { 15 | [Fact] 16 | public async Task when_subscribing_to_externally_produced_event_then_fetches_observable_from_components() 17 | { 18 | var expected = new FooEvent(); 19 | var observable = new[] { expected }.ToObservable(); 20 | var stream = new EventStreamComponent(Mock.Of(s => 21 | s.GetService(typeof(SComponentModel)) == Mock.Of(c => 22 | c.GetExtensions>() == new[] { observable }))); 23 | 24 | var actual = await stream.Of().FirstAsync(); 25 | 26 | Assert.Same(expected, actual); 27 | } 28 | 29 | public class FooEvent { } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Vsix/Merq.Vsix.Tests/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 |  2 | // This file is used by Code Analysis to maintain SuppressMessage 3 | // attributes that are applied to this project. 4 | // Project-level suppressions either have no target or are given 5 | // a specific target and scoped to a namespace, type, member, etc. 6 | 7 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "Not needed on tests", Scope = "module")] 8 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("AsyncUsage.CSharp.Naming", "UseAsyncSuffix:Use Async suffix", Justification = "", Scope = "module")] -------------------------------------------------------------------------------- /src/Vsix/Merq.Vsix.Tests/Merq.Vsix.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | net472 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ..\..\..\lib\Microsoft.ComponentModel.Composition.Diagnostics.dll 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/Vsix/Merq.Vsix/Components/CommandBusComponent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.Composition; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Microsoft.VisualStudio.ComponentModelHost; 6 | using System.Linq; 7 | using System.Reflection; 8 | using Merq.Properties; 9 | 10 | namespace Merq 11 | { 12 | [Export("Merq.ICommandBus.Default", typeof(ICommandBus))] 13 | [PartCreationPolicy(CreationPolicy.Shared)] 14 | internal class CommandBusComponent : ICommandBus 15 | { 16 | MethodInfo canHandleMethod = typeof(CommandBusComponent) 17 | .GetTypeInfo() 18 | .GetDeclaredMethods("CanHandle") 19 | .First(m => m.IsGenericMethodDefinition); 20 | 21 | IComponentModel components; 22 | Runner forCommands; 23 | 24 | [ImportingConstructor] 25 | public CommandBusComponent([Import(typeof(Microsoft.VisualStudio.Shell.SVsServiceProvider))] IServiceProvider services) 26 | : this((IComponentModel)services.GetService(typeof(SComponentModel))) 27 | { 28 | } 29 | 30 | public CommandBusComponent(IComponentModel components) 31 | { 32 | this.components = components; 33 | forCommands = new Runner(components); 34 | } 35 | 36 | public bool CanExecute(TCommand command) where TCommand : IExecutable 37 | { 38 | if (command == null) throw new ArgumentNullException(nameof(command)); 39 | 40 | var handler = components.GetExtensions>().FirstOrDefault(); 41 | 42 | return handler == null ? false : handler.CanExecute(command); 43 | } 44 | 45 | public bool CanHandle(IExecutable command) 46 | { 47 | if (command == null) throw new ArgumentNullException(nameof(command)); 48 | 49 | try 50 | { 51 | return (bool)canHandleMethod.MakeGenericMethod(command.GetType()) 52 | .Invoke(this, new object[0]); 53 | } 54 | catch (TargetInvocationException ex) 55 | { 56 | // Rethrow the inner exception preserving stack trace. 57 | System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); 58 | // Will never get here. 59 | throw ex.InnerException; 60 | } 61 | } 62 | 63 | public bool CanHandle() where TCommand : IExecutable 64 | { 65 | return components.GetExtensions>().Any(); 66 | } 67 | 68 | public void Execute(ICommand command) 69 | { 70 | if (command == null) throw new ArgumentNullException(nameof(command)); 71 | 72 | ForCommand().Execute((dynamic)command); 73 | } 74 | 75 | public TResult Execute(ICommand command) 76 | { 77 | if (command == null) throw new ArgumentNullException(nameof(command)); 78 | 79 | return ForResult().Execute((dynamic)command); 80 | } 81 | 82 | public Task ExecuteAsync(IAsyncCommand command, CancellationToken cancellation) 83 | { 84 | if (command == null) throw new ArgumentNullException(nameof(command)); 85 | 86 | return ForCommand().ExecuteAsync((dynamic)command, cancellation); 87 | } 88 | 89 | public Task ExecuteAsync(IAsyncCommand command, CancellationToken cancellation) 90 | { 91 | if (command == null) throw new ArgumentNullException(nameof(command)); 92 | 93 | return ForResult().ExecuteAsync((dynamic)command, cancellation); 94 | } 95 | 96 | Runner ForCommand() => forCommands; 97 | 98 | Runner ForResult() => new Runner(components); 99 | 100 | class Runner 101 | { 102 | IComponentModel components; 103 | 104 | public Runner(IComponentModel components) 105 | { 106 | this.components = components; 107 | } 108 | 109 | public void Execute(TCommand command) where TCommand : ICommand 110 | { 111 | var handler = components.GetExtensions>().FirstOrDefault(); 112 | if (handler == null) 113 | throw new NotSupportedException(Strings.CommandBus.NoHandler(command.GetType())); 114 | 115 | handler.Execute(command); 116 | } 117 | 118 | public Task ExecuteAsync(TCommand command, CancellationToken cancellation) where TCommand : IAsyncCommand 119 | { 120 | var handler = components.GetExtensions>().FirstOrDefault(); 121 | if (handler == null) 122 | throw new NotSupportedException(Strings.CommandBus.NoHandler(command.GetType())); 123 | 124 | return handler.ExecuteAsync(command, cancellation); 125 | } 126 | } 127 | 128 | class Runner 129 | { 130 | IComponentModel components; 131 | 132 | public Runner(IComponentModel components) 133 | { 134 | this.components = components; 135 | } 136 | 137 | public TResult Execute(TCommand command) where TCommand : ICommand 138 | { 139 | var handler = components.GetExtensions>().FirstOrDefault(); 140 | if (handler == null) 141 | throw new NotSupportedException(Strings.CommandBus.NoHandler(command.GetType())); 142 | 143 | return handler.Execute(command); 144 | } 145 | 146 | public Task ExecuteAsync(TCommand command, CancellationToken cancellation) where TCommand : IAsyncCommand 147 | { 148 | var handler = components.GetExtensions>().FirstOrDefault(); 149 | if (handler == null) 150 | throw new NotSupportedException(Strings.CommandBus.NoHandler(command.GetType())); 151 | 152 | return handler.ExecuteAsync(command, cancellation); 153 | } 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/Vsix/Merq.Vsix/Components/DefaultExportProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel.Composition; 3 | using System.Linq; 4 | 5 | namespace Merq.Components 6 | { 7 | [PartCreationPolicy(CreationPolicy.Shared)] 8 | class DefaultExportProvider 9 | { 10 | [ImportingConstructor] 11 | public DefaultExportProvider( 12 | [Import("Merq.ICommandBus.Default")] ICommandBus defaultCommandBus, 13 | [Import("Merq.IEventStream.Default")] IEventStream defaultEventStream, 14 | [ImportMany("Merq.ICommandBus.Override")] IEnumerable customCommandBus, 15 | [ImportMany("Merq.IEventStream.Override")] IEnumerable customEventStream) 16 | { 17 | CommandBus = customCommandBus.FirstOrDefault() ?? defaultCommandBus; 18 | EventStream = customEventStream.FirstOrDefault() ?? defaultEventStream; 19 | } 20 | 21 | [Export] 22 | public ICommandBus CommandBus { get; } 23 | 24 | [Export] 25 | public IEventStream EventStream { get; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Vsix/Merq.Vsix/Components/EventStreamComponent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.Composition; 4 | using Microsoft.VisualStudio.ComponentModelHost; 5 | using Microsoft.VisualStudio.Shell; 6 | 7 | namespace Merq 8 | { 9 | [Export("Merq.IEventStream.Default", typeof(IEventStream))] 10 | [PartCreationPolicy(CreationPolicy.Shared)] 11 | internal class EventStreamComponent : EventStream 12 | { 13 | IComponentModel components; 14 | 15 | [ImportingConstructor] 16 | public EventStreamComponent([Import(typeof(SVsServiceProvider))] IServiceProvider services) 17 | { 18 | components = (IComponentModel)services.GetService(typeof(SComponentModel)); 19 | } 20 | 21 | protected override IEnumerable> GetObservables() 22 | { 23 | // Since each IObservable component is exported explicitly for each of 24 | // the base classes of the generated event, there will be, for example: 25 | // for IObservable, an export for that interface, as well as 26 | // a (say) IObservable and IObservable, so that 27 | // when someone subscribes to stream.Of, we just need to retrieve 28 | // the concrete IObservable here, which considerably simplifies 29 | // the implementation here and avoids caching/invalidation/traversal of 30 | // TEvent hierarchy, etc. 31 | return components.GetExtensions>(); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/Vsix/Merq.Vsix/Merq.Vsix.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | net472 6 | Merq 7 | Merq.vsix 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | True 16 | True 17 | Resources.resx 18 | 19 | 20 | 21 | 22 | 23 | 200.png 24 | true 25 | 26 | 27 | Merq.ico 28 | true 29 | 30 | 31 | LICENSE 32 | true 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | Merq.Core 55 | BuiltProjectOutputGroup;DebugSymbolsProjectOutputGroup;GetCopyToOutputDirectoryItems;SatelliteDllsProjectOutputGroup;BuiltProjectOutputGroupDependencies;DebugSymbolsProjectOutputGroupDependencies;SatelliteDllsProjectOutputGroupDependencies 56 | 57 | 58 | Merq 59 | BuiltProjectOutputGroup;DebugSymbolsProjectOutputGroup;GetCopyToOutputDirectoryItems;SatelliteDllsProjectOutputGroup;BuiltProjectOutputGroupDependencies;DebugSymbolsProjectOutputGroupDependencies;SatelliteDllsProjectOutputGroupDependencies 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | true 80 | VSPackage 81 | MerqPackage.cs 82 | Designer 83 | 84 | 85 | ResXFileCodeGenerator 86 | Resources.Designer.cs 87 | Designer 88 | Merq.Properties 89 | 90 | 91 | 92 | 93 | 94 | 95 | Program 96 | $(DevEnvDir)\devenv.exe 97 | /rootsuffix Exp 98 | 99 | 100 | -------------------------------------------------------------------------------- /src/Vsix/Merq.Vsix/Merq.Vsix.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | false 7 | true 8 | $(CI) 9 | 10 | true 11 | true 12 | true 13 | None 14 | 15 | Merq 16 | true 17 | 18 | 19 | 20 | $(BUILD_ARTIFACTSTAGINGDIRECTORY)/artifacts 21 | Merq*dll Merq*pdb 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Vsix/Merq.Vsix/Merq.Vsix.targets: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | false 6 | true 7 | 8 | true 9 | false 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 20 | 21 | 22 | 23 | 24 | 25 | $([System.IO.Path]::ChangeExtension('$(TargetVsixContainerName)', '$(FileVersion).vsix')) 26 | $([System.IO.Path]::Combine('$(PackageOutputPath)', '$(TargetVsixContainerName)')) 27 | $([System.IO.Path]::Combine('$(OutDir)', '$(TargetVsixContainerName)')) 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/Vsix/Merq.Vsix/MerqPackage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using Microsoft.VisualStudio.Shell; 4 | 5 | [assembly: ProvideCodeBase] 6 | [assembly: ProvideCodeBase(AssemblyName = "Merq")] 7 | [assembly: ProvideCodeBase(AssemblyName = "Merq.Core")] 8 | 9 | namespace Merq 10 | { 11 | /// 12 | /// Package providing Merq registration. 13 | /// 14 | [Guid("49A95AF4-CB3D-4770-BD67-B0BBB46C6463")] 15 | [InstalledProductRegistration("#100", "#110", 16 | ThisAssembly.Git.SemVer.Major + "." + 17 | ThisAssembly.Git.SemVer.Minor + "." + 18 | ThisAssembly.Git.SemVer.Patch + ThisAssembly.Git.SemVer.DashLabel + " (" + 19 | ThisAssembly.Git.Branch + "@" + 20 | ThisAssembly.Git.Commit + ")")] 21 | [PackageRegistration(RegisterUsing = RegistrationMethod.CodeBase, UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] 22 | [ProvideBindingPath] 23 | public class MerqPackage : AsyncPackage 24 | { 25 | } 26 | } -------------------------------------------------------------------------------- /src/Vsix/Merq.Vsix/MerqPackage.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | Extensibility Message Bus 122 | 123 | 124 | Provides common messaging-based MEF services for loosely coupled Visual Studio extension components communication and integration. 125 | 126 | 127 | 128 | ..\..\..\icon\merq.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 129 | 130 | -------------------------------------------------------------------------------- /src/Vsix/Merq.Vsix/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("Merq.Vsix.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001009d69078301b6c4881e95cd924d5e355a4d24ba3d28fb571e00124706538eef959eb371fbb9bd2776fbe7d228178df51fbd4a849aff37161190f3254c77167d16e42c2be32c817537b67b874b66be01a4b6d1c780ff052c8f7f52cad6751288911d450cf443ed4f40b266332f6f25204df23df9a23d38e5fe19f6372a636c7da1")] -------------------------------------------------------------------------------- /src/Vsix/Merq.Vsix/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Merq.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Merq.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized string similar to No command handler is registered for command type {0}.. 65 | /// 66 | internal static string CommandBus_NoHandler { 67 | get { 68 | return ResourceManager.GetString("CommandBus_NoHandler", resourceCulture); 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Vsix/Merq.Vsix/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | No command handler is registered for command type {0}. 122 | 123 | -------------------------------------------------------------------------------- /src/Vsix/Merq.Vsix/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | Extensibility Message Bus 8 | Provides common messaging-based MEF services for loosely coupled Visual Studio extension components communication and integration. 9 | https://github.com/MobileEssentials/Merq 10 | LICENSE 11 | Merq.ico 12 | 200.png 13 | vsix 14 | Microsoft.VisualStudio.Xamarin.Merq 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/_._: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MobileEssentials/Merq/34d6af216bf5c72f0bc3fa796607c83ae9a42fae/src/_._ --------------------------------------------------------------------------------