This commit is contained in:
Thomas Peterson 2024-11-04 20:45:34 +01:00
parent 653ceb0189
commit 4b29a88239
607 changed files with 17893 additions and 143 deletions

View File

@ -0,0 +1 @@
PSCHelpdesk

View File

@ -11,52 +11,18 @@
</component>
<component name="ChangeListManager">
<list default="true" id="95257dc5-08bd-4c50-8726-85956b3c2c92" name="Changes" comment="">
<change afterPath="$PROJECT_DIR$/.gitignore" afterDir="false" />
<change afterPath="$PROJECT_DIR$/HetznerServer/ViewModels/ServerViewModel.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/HetznerServer/Views/ServerView.axaml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/HetznerServer/Views/ServerView.axaml.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk.sln" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/.gitignore" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/Directory.Build.props" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk.Android/Icon.png" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk.Android/MainActivity.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk.Android/PSCHelpdesk.Android.csproj" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk.Android/Properties/AndroidManifest.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk.Android/Resources/AboutResources.txt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk.Android/Resources/drawable-night-v31/avalonia_anim.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk.Android/Resources/drawable-v31/avalonia_anim.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk.Android/Resources/drawable/splash_screen.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk.Android/Resources/values-night/colors.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk.Android/Resources/values-v31/styles.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk.Android/Resources/values/colors.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk.Android/Resources/values/styles.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk.Browser/PSCHelpdesk.Browser.csproj" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk.Browser/Program.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk.Browser/Properties/AssemblyInfo.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk.Browser/Properties/launchSettings.json" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk.Browser/runtimeconfig.template.json" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk.Browser/wwwroot/app.css" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk.Browser/wwwroot/favicon.ico" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk.Browser/wwwroot/index.html" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk.Browser/wwwroot/main.js" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk.Desktop/PSCHelpdesk.Desktop.csproj" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk.Desktop/Program.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk.Desktop/app.manifest" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk.iOS/AppDelegate.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk.iOS/Entitlements.plist" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk.iOS/Info.plist" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk.iOS/Main.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk.iOS/PSCHelpdesk.iOS.csproj" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk.iOS/Resources/LaunchScreen.xib" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk.sln" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk/App.axaml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk/App.axaml.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk/Assets/avalonia-logo.ico" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk/PSCHelpdesk.csproj" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk/ViewLocator.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk/Views/MainWindow.axaml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk/Views/MainWindow.axaml.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Shared/ViewModels/ViewModelBase.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/.idea.PSCHelpdesk/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.idea.PSCHelpdesk/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/HetznerServer/HetznerServer.cs" beforeDir="false" afterPath="$PROJECT_DIR$/HetznerServer/HetznerServer.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/HetznerServer/HetznerServer.csproj" beforeDir="false" afterPath="$PROJECT_DIR$/HetznerServer/HetznerServer.csproj" afterDir="false" />
<change beforePath="$PROJECT_DIR$/HetznerServer/HetznerServerBootstrap.cs" beforeDir="false" afterPath="$PROJECT_DIR$/HetznerServer/HetznerServerBootstrap.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/PSCHelpdesk.sln" beforeDir="false" afterPath="$PROJECT_DIR$/PSCHelpdesk.sln" afterDir="false" />
<change beforePath="$PROJECT_DIR$/PSCHelpdesk.sln.DotSettings.user" beforeDir="false" afterPath="$PROJECT_DIR$/PSCHelpdesk.sln.DotSettings.user" afterDir="false" />
<change beforePath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk/PSCHelpdesk.csproj" beforeDir="false" afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk/PSCHelpdesk.csproj" afterDir="false" />
<change beforePath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk/Services/MenuService.cs" beforeDir="false" afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk/Services/MenuService.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk/Services/PluginService.cs" beforeDir="false" afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk/Services/PluginService.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk/Startup.cs" beforeDir="false" afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk/Startup.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk/ViewModels/MainWindowViewModel.cs" beforeDir="false" afterPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk/ViewModels/MainWindowViewModel.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Shared/Shared.csproj" beforeDir="false" afterPath="$PROJECT_DIR$/Shared/Shared.csproj" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@ -70,57 +36,38 @@
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="HighlightingSettingsPerFile">
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/DecompilerCache/decompiler/667004c0a83e47158c9865f6d54f01d91ac00/34/243c7c7d/DefaultPluginLoader.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/DecompilerCache/decompiler/667004c0a83e47158c9865f6d54f01d91ac00/59/bf99e13a/IPluginLoader.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/DecompilerCache/decompiler/667004c0a83e47158c9865f6d54f01d91ac00/5a/bd6f74c9/DefaultDirectoryTraverser.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/DecompilerCache/decompiler/667004c0a83e47158c9865f6d54f01d91ac00/a5/00a41a89/DefaultAssemblyScanner.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/DecompilerCache/decompiler/667004c0a83e47158c9865f6d54f01d91ac00/bb/f5cef1fb/IDirectoryTraverser.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/DecompilerCache/decompiler/95e899f2b13d4189874af98ec00443321e400/0d/00fcbad0/PublicNet.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/DecompilerCache/decompiler/95e899f2b13d4189874af98ec00443321e400/f7/54c88032/Ipv6.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/DecompilerCache/decompiler/a924c6fef45b419cbbc71e86813a9b3a35000/f5/d14c12da/IServerActionsService.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/16a25d89779ca224b34f189688787a4b4f091751ee7706b8b396a5e88aa225/HeaderedSelectingItemsControl.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/27be5d106a789638721745a2a46f9cb0f7a39905117d644638b6e0d56261be/ReplaySubject.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/2c86fa7dbfba747d28348dc8a7bfd0e9d9fe887ed61499b2417e8ee3ec5888/ClrPropertyInfo.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="mock:///Dummy.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="mock:///Dummy.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="mock:///Dummy.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="mock:///Dummy.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="mock:///Dummy.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="mock:///Dummy.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="mock:///Dummy.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="mock:///Dummy.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="mock:///Dummy.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="mock:///Dummy.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/1b81cb3be224213a6a73519b6e340a628d9a1fb8629c351a186a26f6376669/List.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/1eb3d84af5e656ecdf75c8c4df55d5f931ced552db5e04c6fc85b4ecf45288/ConcurrentDictionary.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/2e6883f773fb7c69a15db509adac9a0c068e4ca54fa119e835fd2324311c3b/Ioc.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/33fa97197c7d5dc2e649bd1e13ca25ad6fd7928c626fa7796267b9434bd4ba/ServiceCollection.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/37bd8e7b5e226f349e547a5599819e82461d93768c749730253eeec329288a2b/Array.Enumerators.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/45823974e7a2167a2d01b44459552e2f13db5da9bcf7c58193328bc8a093ae/Process.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/45c62c1f9af1eb2248ec17220752868b3b6728095879e849b6ea733ef7f5ff/CastHelpers.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/4743f513c7ddc8411223a46f0ca426ed929391acebcff993721dff2f0c6b34/ThrowHelper.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/5094e71058416d72278f32a6913f67a9c664bd46c296c5cce3ee63993dde3f/Type.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/571361bb1a5845cf1e8a5dc5b730e5d9b36de9a6be6f33fbd43b4096aa24e7/JsonSettings.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/6cb17de97a8121f48e5408d49811afb1bc7ab58eb8836f1d2e30a5d79/MenuItem.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/711f7792fa6122388fd055dc53371e134cb0064e6918a15d025c8778db5b73/Char.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/7e333a9f3297ba553cccfd3b7c3f1f96125b23d09f883e4d6e66d531559a4c/Type.CoreCLR.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/8874fa6fbc50b05ba8332188d36141eb4bba81fb1f92189ba9d7a25f545/ThrowHelper.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/8f5178316d8f8271b8f333bd87cd9e9f7da6764081a31277e776c6cdcb3a25a8/SystemDialog.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/9d493cae194a42808b62a282d8d797b4ef18a9435baa6da7a73bee8e66ca1/ReactiveCommandBase.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/5cde391207de75962d7bacb899ca2bd3985c86911b152d185b58999a422bf0/Type.CoreCLR.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/87c584767b46b5fd42769be76547105558e6690f785614efddca134b2d682/Type.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/9d4f4ac7db6d2c5d183ab2d92602280ed4349fd6e6a1b6313546b3d01fdab5/ServiceProvider.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/a119bbffad50bc743b255772611ceba491d63fce14a64e86c71a607766c5b0/AvaloniaObject.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/ad21a37fb9c62642a9445c29321d3d136d19fa60425b26806692bbfee3523c/Path.Unix.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/b06c2ce0981bf6e5989cda2e3e737e53c2d54ee6ae7e7ce318d378f52e5f66/StyledElement.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/bf9021a960b74107a7e141aa06bc9d8a0a53c929178c2fb95b1597be8af8dc/ExceptionDispatchInfo.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/c9f1e78ef04f5214281bc448c6a251bb5f9a3135b662f76745bbfb39b14150/First.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/ceb1c14a2cad4d38a6c5bf90e72339c81cc000/_cb8e6/IStorageProvider.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/d0d96f40fdd9bc395fad59e4a8a6539e4eecce4865809ef4be1a473f911eceaa/ReadOnlySpan.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/d6b757e154dd7f8c23e0e785431c97a76e4b9c6bdae38b978238421dbab55d/MethodBaseInvoker.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/d7bc48c79f70bd7a187cf86258b2815e2deb22274953837f735bbfe18f6c69e2/MemoryExtensions.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/dad3f0ebff0dd1f8e1d244c3c44c649be8228d5e25fb37ef1de7f3c0e261c/String.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/de1dc94e26de2eb8cdb9b19e9451c6a0738025b875978dc863de7ba87ee78c4/ServiceCollectionServiceExtensions.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/e6b66c6b133847f9a2c8b591fb074f9d100400/_4277f/UserControl.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/e91a2750b979a5265711aaa020b980ebe7c5168bdf221ad2e32d16e3f74b8382/AsyncVoidMethodBuilder.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/f51bb85dff27841839d69724c7524901cc897dafafe61e611f7a7460e327c/ConcurrentDictionary.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/f53745e7f6e85d65317047eeae9af151fe9aca1cb284d27e5c83962a50be46/ServiceDescriptor.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/f93d3944f37777fd1f922d0d91e96fb77c2f83024101cb4b92f8d9b68080da/SelectMany.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/ad1d58b8dd22123dfae3f2cf5d8b199992465c9c344d032f93a826d8e5539ff/RuntimeType.CoreCLR.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/bd1d5c50194fea68ff3559c160230b0ab50f5acf4ce3061bffd6d62958e2182/ExceptionDispatchInfo.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/HetznerServer/ViewModels/ServerViewModel.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/HetznerServer/Views/ServerView.axaml" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/HetznerServer/Views/ServerView.axaml.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Vendor/Prise/AssemblyScanning/DefaultAssemblyResolver.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Vendor/Prise/DependencyInjection/ServiceCollectionExtensions.cs" root0="FORCE_HIGHLIGHTING" />
</component>
<component name="InvalidFacetManager">
<ignored-facets>
<facet id="PSCHelpdesk.Android/invalid/Android Facet - PSCHelpdesk.Android" />
</ignored-facets>
</component>
<component name="ProblemsViewState">
<option name="selectedTabId" value="Toolset" />
</component>
<component name="ProjectColorInfo">{
&quot;customColor&quot;: &quot;&quot;,
&quot;associatedIndex&quot;: 8
@ -227,6 +174,7 @@
<workItem from="1730307680217" duration="708000" />
<workItem from="1730315057448" duration="72343000" />
<workItem from="1730626276818" duration="15656000" />
<workItem from="1730716176795" duration="11704000" />
</task>
<servers />
</component>
@ -242,56 +190,43 @@
<breakpoints>
<line-breakpoint enabled="true" type="DotNet Breakpoints">
<url>file://$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk/ViewModels/MainWindowViewModel.cs</url>
<line>86</line>
<line>78</line>
<properties documentPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk/ViewModels/MainWindowViewModel.cs" containingFunctionPresentation="Method 'SelectMenu'">
<startOffsets>
<option value="2529" />
<option value="2343" />
</startOffsets>
<endOffsets>
<option value="2553" />
<option value="2367" />
</endOffsets>
</properties>
<option name="timeStamp" value="78" />
</line-breakpoint>
<line-breakpoint enabled="true" type="DotNet Breakpoints">
<url>file://$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk/ViewModels/MainWindowViewModel.cs</url>
<line>83</line>
<line>75</line>
<properties documentPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk/ViewModels/MainWindowViewModel.cs" containingFunctionPresentation="Method 'SelectMenu'">
<startOffsets>
<option value="2363" />
<option value="2177" />
</startOffsets>
<endOffsets>
<option value="2481" />
<option value="2295" />
</endOffsets>
</properties>
<option name="timeStamp" value="79" />
</line-breakpoint>
<line-breakpoint enabled="true" type="DotNet Breakpoints">
<url>file://$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk/ViewModels/MainWindowViewModel.cs</url>
<line>84</line>
<line>76</line>
<properties documentPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk/ViewModels/MainWindowViewModel.cs" containingFunctionPresentation="Method 'SelectMenu'">
<startOffsets>
<option value="2490" />
<option value="2304" />
</startOffsets>
<endOffsets>
<option value="2506" />
<option value="2320" />
</endOffsets>
</properties>
<option name="timeStamp" value="80" />
</line-breakpoint>
<line-breakpoint enabled="true" type="DotNet Breakpoints">
<url>file://$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk/Views/ContentDisplay.axaml.cs</url>
<line>71</line>
<properties documentPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk/Views/ContentDisplay.axaml.cs" containingFunctionPresentation="Property 'SelectedContent'">
<startOffsets>
<option value="2129" />
</startOffsets>
<endOffsets>
<option value="2213" />
</endOffsets>
</properties>
<option name="timeStamp" value="81" />
</line-breakpoint>
<line-breakpoint enabled="true" type="DotNet Breakpoints">
<url>file://$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk/Views/ContentDisplay.axaml.cs</url>
<line>37</line>
@ -306,17 +241,56 @@
<option name="timeStamp" value="82" />
</line-breakpoint>
<line-breakpoint enabled="true" type="DotNet Breakpoints">
<url>file://$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk/ViewModels/MainWindowViewModel.cs</url>
<line>48</line>
<properties documentPath="$PROJECT_DIR$/PSCHelpdesk/PSCHelpdesk/ViewModels/MainWindowViewModel.cs" containingFunctionPresentation="">
<url>file://$PROJECT_DIR$/HetznerServer/Menu/MainMenu.cs</url>
<line>13</line>
<properties documentPath="C:\Users\info\RiderProjects\pschelpdesk\HetznerServer\Menu\MainMenu.cs" containingFunctionPresentation="Method 'addMenu'">
<startOffsets>
<option value="1425" />
<option value="335" />
</startOffsets>
<endOffsets>
<option value="1470" />
<option value="428" />
</endOffsets>
</properties>
<option name="timeStamp" value="83" />
<option name="timeStamp" value="92" />
</line-breakpoint>
<line-breakpoint enabled="true" type="DotNet Breakpoints">
<url>file://$PROJECT_DIR$/HetznerServer/HetznerServer.cs</url>
<line>26</line>
<properties documentPath="C:\Users\info\RiderProjects\pschelpdesk\HetznerServer\HetznerServer.cs" containingFunctionPresentation="Method 'addMenu'">
<startOffsets>
<option value="648" />
</startOffsets>
<endOffsets>
<option value="705" />
</endOffsets>
</properties>
<option name="timeStamp" value="94" />
</line-breakpoint>
<line-breakpoint enabled="true" type="DotNet Breakpoints">
<url>file://$PROJECT_DIR$/HetznerServer/HetznerServer.cs</url>
<line>28</line>
<properties documentPath="C:\Users\info\RiderProjects\pschelpdesk\HetznerServer\HetznerServer.cs" containingFunctionPresentation="Method 'addMenu'">
<startOffsets>
<option value="780" />
</startOffsets>
<endOffsets>
<option value="808" />
</endOffsets>
</properties>
<option name="timeStamp" value="95" />
</line-breakpoint>
<line-breakpoint enabled="true" type="DotNet Breakpoints">
<url>file://$PROJECT_DIR$/HetznerServer/HetznerServer.cs</url>
<line>27</line>
<properties documentPath="C:\Users\info\RiderProjects\pschelpdesk\HetznerServer\HetznerServer.cs" containingFunctionPresentation="Method 'addMenu'">
<startOffsets>
<option value="714" />
</startOffsets>
<endOffsets>
<option value="771" />
</endOffsets>
</properties>
<option name="timeStamp" value="109" />
</line-breakpoint>
</breakpoints>
</breakpoint-manager>

View File

@ -1,8 +1,11 @@
using CommunityToolkit.Mvvm.DependencyInjection;
using Prise.Plugin;
using PSCHelpdesk.Plugins.HetznerServer.Menu;
using PSCHelpdesk.Plugins.HetznerServer.ViewModels;
using PSCHelpdesk.Plugins.HetznerServer.Views;
using PSCHelpdesk.Shared.Menu;
using PSCHelpdesk.Shared.Plugin;
using PSCHelpdesk.Shared.Service;
namespace PSCHelpdesk.Plugins.HetznerServer;
@ -16,11 +19,18 @@ public class HetznerServer : Contract
public async Task<List<Item>> addMenu()
{
var menu = new MainMenu();
//menu.addMenu();
var menuService = Ioc.Default.GetService<IMenuService>();
var testService = Ioc.Default.GetService<ITestService>();
var list = new List<Item>();
var serverTab = new Item()
{
Header = "Server",
CommandParameter = typeof(ServerViewModel),
// CommandParameter = typeof(ServerViewModel)
};
list.Add(serverTab);
return list;

View File

@ -20,6 +20,9 @@
<ItemGroup>
<ProjectReference Include="..\Shared\Shared.csproj" />
<ProjectReference Include="..\Vendor\Prise.Plugin\Prise.Plugin.csproj" />
<ProjectReference Include="..\Vendor\Prise.Proxy\Prise.Proxy.csproj" />
<ProjectReference Include="..\Vendor\Prise.ReverseProxy\Prise.ReverseProxy.csproj" />
</ItemGroup>
<ItemGroup>
@ -40,7 +43,6 @@
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="11.2.0" />
<PackageReference Include="Avalonia.Xaml.Interactions" Version="11.2.0" />
<PackageReference Include="HetznerCloud.API" Version="1.1.9" />
<PackageReference Include="Prise.Plugin" Version="6.0.0" />
</ItemGroup>
</Project>

View File

@ -1,4 +1,5 @@
using PSCHelpdesk.Plugins.HetznerServer.ViewModels;
using PSCHelpdesk.Shared.Service;
namespace PSCHelpdesk.Plugins.HetznerServer;
@ -9,8 +10,12 @@ using Prise.Plugin;
[PluginBootstrapper(PluginType = typeof(HetznerServer))]
public class HetznerServerBootstrap : IPluginBootstrapper
{
//[BootstrapperService(ServiceType = typeof(IMenuService), ProxyType = typeof(MenuService))]
//private readonly IMenuService menuService;
public IServiceCollection Bootstrap(IServiceCollection services)
{
//services.AddSingleton<IMenuService>(this.menuService);
services.AddTransient<ServerViewModel>();
return services;
}

View File

@ -0,0 +1,20 @@
using Prise.Plugin;
using PSCHelpdesk.Shared.Menu;
using PSCHelpdesk.Shared.Service;
namespace PSCHelpdesk.Plugins.HetznerServer.Menu;
public class MainMenu
{
[PluginService(ProvidedBy = ProvidedBy.Host, ServiceType = typeof(IMenuService))]
private readonly IMenuService menuService;
public void addMenu()
{
this.menuService.AddMenuItem(new Item()
{
Header = "Hetzner",
});
}
}

View File

@ -0,0 +1,15 @@
using Prise.Proxy;
using PSCHelpdesk.Shared.Menu;
using PSCHelpdesk.Shared.Service;
namespace PSCHelpdesk.Plugins.HetznerServer;
public class MenuService : ReverseProxy, IMenuService
{
public MenuService(object hostService) : base(hostService) { }
public void AddMenuItem(Item item)
{
this.InvokeOnHostService<Item>(new[] { item });
}
}

View File

@ -18,6 +18,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HetznerServer", "HetznerSer
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nextcloud", "Nextcloud\Nextcloud.csproj", "{4C34E3F4-718D-4E15-97A2-AE61A4ACEE02}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Vendor", "Vendor", "{D45462B2-C09A-4BC6-A8B1-F03C6533B084}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Prise", "Vendor\Prise\Prise.csproj", "{AD222F87-EAC5-4120-AA33-EB32DF2DDCD5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Prise.Plugin", "Vendor\Prise.Plugin\Prise.Plugin.csproj", "{F4EF7157-C5F0-4444-9E49-28F6BD56295F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Prise.ReverseProxy", "Vendor\Prise.ReverseProxy\Prise.ReverseProxy.csproj", "{52EFDD1B-B775-49A1-BD3D-E7B7F6A989EA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Prise.Proxy", "Vendor\Prise.Proxy\Prise.Proxy.csproj", "{79029F78-5113-46E4-8896-436707A7251F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -60,9 +70,29 @@ Global
{4C34E3F4-718D-4E15-97A2-AE61A4ACEE02}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4C34E3F4-718D-4E15-97A2-AE61A4ACEE02}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4C34E3F4-718D-4E15-97A2-AE61A4ACEE02}.Release|Any CPU.Build.0 = Release|Any CPU
{AD222F87-EAC5-4120-AA33-EB32DF2DDCD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AD222F87-EAC5-4120-AA33-EB32DF2DDCD5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AD222F87-EAC5-4120-AA33-EB32DF2DDCD5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AD222F87-EAC5-4120-AA33-EB32DF2DDCD5}.Release|Any CPU.Build.0 = Release|Any CPU
{F4EF7157-C5F0-4444-9E49-28F6BD56295F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F4EF7157-C5F0-4444-9E49-28F6BD56295F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F4EF7157-C5F0-4444-9E49-28F6BD56295F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F4EF7157-C5F0-4444-9E49-28F6BD56295F}.Release|Any CPU.Build.0 = Release|Any CPU
{52EFDD1B-B775-49A1-BD3D-E7B7F6A989EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{52EFDD1B-B775-49A1-BD3D-E7B7F6A989EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{52EFDD1B-B775-49A1-BD3D-E7B7F6A989EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{52EFDD1B-B775-49A1-BD3D-E7B7F6A989EA}.Release|Any CPU.Build.0 = Release|Any CPU
{79029F78-5113-46E4-8896-436707A7251F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{79029F78-5113-46E4-8896-436707A7251F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{79029F78-5113-46E4-8896-436707A7251F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{79029F78-5113-46E4-8896-436707A7251F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{552272D2-9506-4F9E-9D53-E908E53D9039} = {E7A87869-4D47-4292-BE39-77D36933088C}
{4C34E3F4-718D-4E15-97A2-AE61A4ACEE02} = {E7A87869-4D47-4292-BE39-77D36933088C}
{AD222F87-EAC5-4120-AA33-EB32DF2DDCD5} = {D45462B2-C09A-4BC6-A8B1-F03C6533B084}
{F4EF7157-C5F0-4444-9E49-28F6BD56295F} = {D45462B2-C09A-4BC6-A8B1-F03C6533B084}
{52EFDD1B-B775-49A1-BD3D-E7B7F6A989EA} = {D45462B2-C09A-4BC6-A8B1-F03C6533B084}
{79029F78-5113-46E4-8896-436707A7251F} = {D45462B2-C09A-4BC6-A8B1-F03C6533B084}
EndGlobalSection
EndGlobal

View File

@ -5,17 +5,21 @@
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACastHelpers_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F45c62c1f9af1eb2248ec17220752868b3b6728095879e849b6ea733ef7f5ff_003FCastHelpers_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AChar_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F711f7792fa6122388fd055dc53371e134cb0064e6918a15d025c8778db5b73_003FChar_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AClrPropertyInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F2c86fa7dbfba747d28348dc8a7bfd0e9d9fe887ed61499b2417e8ee3ec5888_003FClrPropertyInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AConcurrentDictionary_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F1eb3d84af5e656ecdf75c8c4df55d5f931ced552db5e04c6fc85b4ecf45288_003FConcurrentDictionary_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AConcurrentDictionary_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Ff51bb85dff27841839d69724c7524901cc897dafafe61e611f7a7460e327c_003FConcurrentDictionary_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADefaultAssemblyScanner_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F667004c0a83e47158c9865f6d54f01d91ac00_003Fa5_003F00a41a89_003FDefaultAssemblyScanner_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADefaultDirectoryTraverser_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F667004c0a83e47158c9865f6d54f01d91ac00_003F5a_003Fbd6f74c9_003FDefaultDirectoryTraverser_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADefaultPluginLoader_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F667004c0a83e47158c9865f6d54f01d91ac00_003F34_003F243c7c7d_003FDefaultPluginLoader_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExceptionDispatchInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fbd1d5c50194fea68ff3559c160230b0ab50f5acf4ce3061bffd6d62958e2182_003FExceptionDispatchInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExceptionDispatchInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fbf9021a960b74107a7e141aa06bc9d8a0a53c929178c2fb95b1597be8af8dc_003FExceptionDispatchInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AFirst_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fc9f1e78ef04f5214281bc448c6a251bb5f9a3135b662f76745bbfb39b14150_003FFirst_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AHeaderedSelectingItemsControl_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F16a25d89779ca224b34f189688787a4b4f091751ee7706b8b396a5e88aa225_003FHeaderedSelectingItemsControl_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIoc_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F2e6883f773fb7c69a15db509adac9a0c068e4ca54fa119e835fd2324311c3b_003FIoc_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIoc_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F2e6883f773fb7c69a15db509adac9a0c068e4ca54fa119e835fd2324311c3b_003FIoc_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIpv6_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F95e899f2b13d4189874af98ec00443321e400_003Ff7_003F54c88032_003FIpv6_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIServerActionsService_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa924c6fef45b419cbbc71e86813a9b3a35000_003Ff5_003Fd14c12da_003FIServerActionsService_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIStorageProvider_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fceb1c14a2cad4d38a6c5bf90e72339c81cc000_003F_005Fcb8e6_003FIStorageProvider_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AList_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F1b81cb3be224213a6a73519b6e340a628d9a1fb8629c351a186a26f6376669_003FList_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMemoryExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fd7bc48c79f70bd7a187cf86258b2815e2deb22274953837f735bbfe18f6c69e2_003FMemoryExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMethodBaseInvoker_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fd6b757e154dd7f8c23e0e785431c97a76e4b9c6bdae38b978238421dbab55d_003FMethodBaseInvoker_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APath_002EUnix_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fad21a37fb9c62642a9445c29321d3d136d19fa60425b26806692bbfee3523c_003FPath_002EUnix_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
@ -23,26 +27,33 @@
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AReactiveCommandBase_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F9d493cae194a42808b62a282d8d797b4ef18a9435baa6da7a73bee8e66ca1_003FReactiveCommandBase_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AReadOnlySpan_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fd0d96f40fdd9bc395fad59e4a8a6539e4eecce4865809ef4be1a473f911eceaa_003FReadOnlySpan_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AReplaySubject_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F27be5d106a789638721745a2a46f9cb0f7a39905117d644638b6e0d56261be_003FReplaySubject_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ARuntimeType_002ECoreCLR_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fad1d58b8dd22123dfae3f2cf5d8b199992465c9c344d032f93a826d8e5539ff_003FRuntimeType_002ECoreCLR_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASelectMany_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Ff93d3944f37777fd1f922d0d91e96fb77c2f83024101cb4b92f8d9b68080da_003FSelectMany_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceCollection_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F33fa97197c7d5dc2e649bd1e13ca25ad6fd7928c626fa7796267b9434bd4ba_003FServiceCollection_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceDescriptor_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Ff53745e7f6e85d65317047eeae9af151fe9aca1cb284d27e5c83962a50be46_003FServiceDescriptor_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceProvider_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F9d4f4ac7db6d2c5d183ab2d92602280ed4349fd6e6a1b6313546b3d01fdab5_003FServiceProvider_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceProvider_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F9d4f4ac7db6d2c5d183ab2d92602280ed4349fd6e6a1b6313546b3d01fdab5_003FServiceProvider_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AString_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fdad3f0ebff0dd1f8e1d244c3c44c649be8228d5e25fb37ef1de7f3c0e261c_003FString_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStyledElement_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fb06c2ce0981bf6e5989cda2e3e737e53c2d54ee6ae7e7ce318d378f52e5f66_003FStyledElement_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASystemDialog_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F8f5178316d8f8271b8f333bd87cd9e9f7da6764081a31277e776c6cdcb3a25a8_003FSystemDialog_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F4743f513c7ddc8411223a46f0ca426ed929391acebcff993721dff2f0c6b34_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F8874fa6fbc50b05ba8332188d36141eb4bba81fb1f92189ba9d7a25f545_003FThrowHelper_002Ecs_002Fz_003A2_002D1/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AType_002ECoreCLR_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F5cde391207de75962d7bacb899ca2bd3985c86911b152d185b58999a422bf0_003FType_002ECoreCLR_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AType_002ECoreCLR_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F7e333a9f3297ba553cccfd3b7c3f1f96125b23d09f883e4d6e66d531559a4c_003FType_002ECoreCLR_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AType_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F87c584767b46b5fd42769be76547105558e6690f785614efddca134b2d682_003FType_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AType_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F5094e71058416d72278f32a6913f67a9c664bd46c296c5cce3ee63993dde3f_003FType_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AUserControl_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fe6b66c6b133847f9a2c8b591fb074f9d100400_003F_005F4277f_003FUserControl_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=a0b4bc4d_002Dd13b_002D4a37_002Db37e_002Dc9c6864e4302/@EntryIndexedValue"></s:String>
<s:Boolean x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=a0b4bc4d_002Dd13b_002D4a37_002Db37e_002Dc9c6864e4302/@EntryIndexRemoved">True</s:Boolean>
<s:String x:Key="/Default/Environment/AssemblyExplorer/XmlDocument/@EntryValue">&lt;AssemblyExplorer&gt;
&lt;Assembly Path="/home/thomas/.nuget/packages/hcloud-api-net/1.0.0/lib/netstandard2.0/hcloud-api.dll" /&gt;
<s:String x:Key="/Default/Environment/AssemblyExplorer/XmlDocument/@EntryValue">&lt;AssemblyExplorer&gt;&#xD;
&lt;Assembly Path="\home\thomas\.nuget\packages\hcloud-api-net\1.0.0\lib\netstandard2.0\hcloud-api.dll" /&gt;&#xD;
&lt;Assembly Path="C:\Users\info\.nuget\packages\prise.pluginbridge\1.7.5\lib\netstandard2.0\Prise.PluginBridge.dll" /&gt;&#xD;
&lt;Assembly Path="C:\Users\info\.nuget\packages\microsoft.extensions.dependencymodel\6.0.0\lib\netstandard2.0\Microsoft.Extensions.DependencyModel.dll" /&gt;&#xD;
&lt;/AssemblyExplorer&gt;</s:String>
<s:Boolean x:Key="/Default/UnloadedProject/UnloadedProjects/=1868ceb9_002D4dab_002D4196_002D91ae_002D60765bdd9820_0023PSCHelpdesk_002EiOS/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UnloadedProject/UnloadedProjects/=1c4ca302_002Da031_002D439a_002Dbfcb_002D42c4bac467f2_0023PSCHelpdesk_002EAndroid/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UnloadedProject/UnloadedProjects/=a910fef2_002D046b_002D4956_002Dbc0a_002Daa57ec9a6b27_0023PSCHelpdesk_002EBrowser/@EntryIndexedValue">True</s:Boolean>
</wpf:ResourceDictionary>

View File

@ -18,17 +18,17 @@
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.2.0" />
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="11.2.0" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1"/>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.3.2" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Nucs.JsonSettings2" Version="2.0.3" />
<PackageReference Include="Prise" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\HetznerServer\HetznerServer.csproj" />
<ProjectReference Include="..\..\Nextcloud\Nextcloud.csproj" />
<ProjectReference Include="..\..\Shared\Shared.csproj" />
<ProjectReference Include="..\..\Vendor\Prise.Plugin\Prise.Plugin.csproj" />
<ProjectReference Include="..\..\Vendor\Prise\Prise.csproj" />
</ItemGroup>
<ItemGroup>

View File

@ -1,5 +1,5 @@
using System;
using Avalonia.Collections;
using System.Collections.Generic;
using CommunityToolkit.Mvvm.DependencyInjection;
using PSCHelpdesk.Shared.Menu;
using PSCHelpdesk.Shared.Service;
@ -10,12 +10,12 @@ namespace PSCHelpdesk.Services;
public class MenuService: ReactiveObject, IMenuService
{
private AvaloniaList<Item> menuItems = new AvaloniaList<Item>();
private AvaloniaList<Item> menuOptionItems = new AvaloniaList<Item>();
private List<Item> menuItems = new List<Item>();
private List<Item> menuOptionItems = new List<Item>();
public event EventHandler MenuChanged;
public AvaloniaList<Item> MenuItems
public List<Item> MenuItems
{
get => this.menuItems;
private set => this.RaiseAndSetIfChanged(ref this.menuItems, value);
@ -24,7 +24,7 @@ public class MenuService: ReactiveObject, IMenuService
/// <summary>
/// Gets the list of options items shown in the hamburger menu (at the bottom).
/// </summary>
public AvaloniaList<Item> MenuOptionItems
public List<Item> MenuOptionItems
{
get => this.menuOptionItems;
private set => this.RaiseAndSetIfChanged(ref this.menuOptionItems, value);

View File

@ -59,6 +59,7 @@ public class PluginService
var pluginLoader = Ioc.Default.GetService(typeof(IPluginLoader)) as IPluginLoader;
var menuService = Ioc.Default.GetRequiredService<IMenuService>();
var testService = Ioc.Default.GetRequiredService<ITestService>();
var appService = Ioc.Default.GetService(typeof(AppService)) as AppService;
//var settingsService = Ioc.Default.GetService<SettingsManager>();
@ -70,10 +71,11 @@ public class PluginService
var plugin = await pluginLoader.LoadPlugin<Contract>(pluginToEnable, configure: (context) =>
{
context
//.AddHostTypes(new[] {typeof(Application)})
.AddHostService<IMenuService>(menuService)
//.AddHostService(appService)
.AddHostService<ITestService>(testService)
//.AddHostService<SettingsManager>(settingsService)
;
});

View File

@ -14,11 +14,12 @@ class Startup
var menuService = new MenuService();
var te = new ServiceCollection()
.AddPrise()
.AddSingleton<AppService>()
.AddSingleton<IMenuService>(menuService)
.AddSingleton<ITestService>(new TestService())
.AddSingleton<PluginService>()
.AddTransient<PluginListViewModel>();
.AddTransient<PluginListViewModel>()
.AddPrise();
var ls = te.BuildServiceProvider();

View File

@ -29,14 +29,6 @@ public class MainWindowViewModel : ViewModelBase
get => _selectedItem;
set => SetAndRaisePropertyChanged(ref _selectedItem, value);
}
private Item _selectedContent;
public Item SelectedContent
{
get => _selectedContent;
set => SetAndRaisePropertyChanged(ref _selectedContent, value);
}
public MainWindowViewModel()
{
this.MenuItems = new List<Item>();

View File

@ -0,0 +1,6 @@
namespace PSCHelpdesk.Shared.Service;
public interface ITestService
{
public void addItem();
}

View File

@ -0,0 +1,9 @@
namespace PSCHelpdesk.Shared.Service;
public class TestService: ITestService
{
public void addItem()
{
throw new NotImplementedException();
}
}

View File

@ -19,8 +19,4 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Folder Include="Views\" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,14 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
namespace Prise.Mvc
{
public static class ApplicationBuilderExtensions
{
public static IApplicationBuilder EnsureStaticPluginCache(this IApplicationBuilder app)
{
app.ApplicationServices.GetRequiredService<IPluginCacheAccessorBootstrapper>();
return app;
}
}
}

View File

@ -0,0 +1,71 @@
using System;
using System.IO;
using System.Threading.Tasks;
using System.Collections.Generic;
using Prise.Caching;
using Prise.AssemblyScanning;
using Prise.AssemblyLoading;
using System.Linq;
using Prise.Utils;
namespace Prise.Mvc
{
public class DefaultMvcPluginLoader : IMvcPluginLoader
{
protected readonly IAssemblyScanner assemblyScanner;
protected readonly IPluginTypeSelector pluginTypeSelector;
protected readonly IAssemblyLoader assemblyLoader;
protected readonly IPluginCache pluginCache;
public DefaultMvcPluginLoader(IAssemblyScanner assemblyScanner,
IPluginTypeSelector pluginTypeSelector,
IAssemblyLoader assemblyLoader,
IPluginCache pluginCache)
{
this.assemblyScanner = assemblyScanner;
this.pluginTypeSelector = pluginTypeSelector;
this.assemblyLoader = assemblyLoader;
this.pluginCache = pluginCache;
}
public async virtual Task<IEnumerable<AssemblyScanResult>> FindPlugins<T>(string pathToPlugins)
{
return (await this.assemblyScanner.Scan(new AssemblyScannerOptions
{
StartingPath = pathToPlugins,
PluginType = typeof(T)
}));
}
public async virtual Task<IAssemblyShim> LoadPluginAssembly<T>(AssemblyScanResult plugin, Action<PluginLoadContext> configureLoadContext = null)
{
var pluginLoadContext = ToPluginLoadContext<T>(plugin);
configureLoadContext?.Invoke(pluginLoadContext);
pluginLoadContext.AddMvcTypes();
var pluginAssembly = await this.assemblyLoader.Load(pluginLoadContext);
this.pluginCache.Add(pluginAssembly, pluginLoadContext.HostServices.Select(s => s.ServiceType));
return pluginAssembly;
}
public async virtual Task UnloadPluginAssembly<T>(AssemblyScanResult plugin)
{
var pluginLoadContext = ToPluginLoadContext<T>(plugin);
await this.assemblyLoader.Unload(pluginLoadContext);
var pathToAssembly = Path.Combine(plugin.AssemblyPath, plugin.AssemblyName);
this.pluginCache.Remove(pathToAssembly);
}
protected PluginLoadContext ToPluginLoadContext<T>(AssemblyScanResult plugin)
{
var hostFramework = HostFrameworkUtils.GetHostframeworkFromHost();
var pathToAssembly = Path.Combine(plugin.AssemblyPath, plugin.AssemblyName);
return PluginLoadContext.DefaultPluginLoadContext(pathToAssembly, typeof(T), hostFramework);
}
}
}

View File

@ -0,0 +1,34 @@
using System;
using System.Threading.Tasks;
using Prise.Caching;
using Prise.AssemblyScanning;
using Prise.AssemblyLoading;
using System.Linq;
namespace Prise.Mvc
{
public class DefaultMvcRazorPluginLoader : DefaultMvcPluginLoader
{
public DefaultMvcRazorPluginLoader(IAssemblyScanner assemblyScanner,
IPluginTypeSelector pluginTypeSelector,
IAssemblyLoader assemblyLoader,
IPluginCache pluginCache) : base(assemblyScanner, pluginTypeSelector, assemblyLoader, pluginCache) { }
public override async Task<IAssemblyShim> LoadPluginAssembly<T>(AssemblyScanResult plugin, Action<PluginLoadContext> configureLoadContext = null)
{
var pluginLoadContext = ToPluginLoadContext<T>(plugin);
configureLoadContext?.Invoke(pluginLoadContext);
pluginLoadContext
.AddMvcRazorTypes();
var pluginAssembly = await this.assemblyLoader.Load(pluginLoadContext);
this.pluginCache.Add(pluginAssembly, pluginLoadContext.HostServices.Select(s => s.ServiceType));
return pluginAssembly;
}
}
}

View File

@ -0,0 +1,25 @@
using System.Threading;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Primitives;
namespace Prise.Mvc
{
public class DefaultPriseMvcActionDescriptorChangeProvider : IActionDescriptorChangeProvider, IPriseMvcActionDescriptorChangeProvider
{
public CancellationTokenSource TokenSource { get; private set; }
public bool HasChanged { get; set; }
public IChangeToken GetChangeToken()
{
TokenSource = new CancellationTokenSource();
return new CancellationChangeToken(TokenSource.Token);
}
public void TriggerPluginChanged()
{
HasChanged = true;
TokenSource.Cancel();
}
}
}

View File

@ -0,0 +1,76 @@
using System;
using System.Linq;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.Extensions.DependencyInjection;
using Prise.Activation;
using Prise.Infrastructure;
using Prise.Plugin;
using Prise.Proxy;
using Prise.Caching;
namespace Prise.Mvc
{
public class DefaultPriseMvcControllerActivator : IControllerActivator
{
public object Create(ControllerContext context)
{
var cache = context.HttpContext.RequestServices.GetRequiredService<IPluginCache>();
var controllerType = context.ActionDescriptor.ControllerTypeInfo.AsType();
foreach (var cachedPluginAssembly in cache.GetAll())
{
var pluginAssembly = cachedPluginAssembly.AssemblyShim.Assembly;
var pluginControllerType = pluginAssembly.GetTypes().FirstOrDefault(t => t.Name == controllerType.Name);
if (pluginControllerType != null)
{
var activatorContextProvider = context.HttpContext.RequestServices.GetRequiredService<IPluginActivationContextProvider>();
var remotePluginActivator = context.HttpContext.RequestServices.GetRequiredService<IRemotePluginActivator>();
var proxyCreator = context.HttpContext.RequestServices.GetRequiredService<IPluginProxyCreator>();
var resultConverter = context.HttpContext.RequestServices.GetRequiredService<IResultConverter>();
var parameterConverter = context.HttpContext.RequestServices.GetRequiredService<IParameterConverter>();
object controller = null;
IPluginBootstrapper bootstrapperProxy = null;
IServiceCollection hostServices = new ServiceCollection();
foreach (var hostServiceType in cachedPluginAssembly.HostTypes)
hostServices.Add(new ServiceDescriptor(hostServiceType, context.HttpContext.RequestServices.GetRequiredService(hostServiceType)));
var pluginActivationContext = activatorContextProvider.ProvideActivationContext(pluginControllerType, cachedPluginAssembly.AssemblyShim);
if (pluginActivationContext.PluginBootstrapperType != null)
{
var remoteBootstrapperInstance = remotePluginActivator.CreateRemoteBootstrapper(pluginActivationContext, hostServices);
var remoteBootstrapperProxy = proxyCreator.CreateBootstrapperProxy(remoteBootstrapperInstance);
bootstrapperProxy = remoteBootstrapperProxy;
}
controller = remotePluginActivator.CreateRemoteInstance(
pluginActivationContext,
bootstrapperProxy,
hostServices: hostServices
);
var controllerContext = new ControllerContext();
controllerContext.HttpContext = context.HttpContext;
var controllerContextProperty = controllerType.GetProperty("ControllerContext");
controllerContextProperty.SetValue(controller, controllerContext);
return controller;
}
}
// Use MSFT's own activator utilities to create a controller instance
// This avoids us to require to register all controllers as services
return ActivatorUtilities.CreateInstance(context.HttpContext.RequestServices, controllerType);
}
public void Release(ControllerContext context, object controller)
{
(controller as IDisposable)?.Dispose();
}
}
}

View File

@ -0,0 +1,72 @@
using System;
using System.IO;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Primitives;
using Prise.Caching;
namespace Prise.Mvc
{
public class DefaultPrisePluginViewsAssemblyFileProvider : IFileProvider
{
protected readonly PhysicalFileProvider webRootFileProvider;
protected readonly string pathToPlugins;
public DefaultPrisePluginViewsAssemblyFileProvider(string hostingRootPath, string pathToPlugins)
{
if (!Path.IsPathRooted(pathToPlugins))
throw new ArgumentException($"{nameof(pathToPlugins)} must be rooted (absolute path).");
this.pathToPlugins = pathToPlugins;
this.webRootFileProvider = new PhysicalFileProvider(hostingRootPath);
}
private IPluginCache GetLoadedPluginsCache()
{
return DefaultStaticPluginCacheAccessor.CurrentCache;
}
private IFileProvider GetPluginFileProvider(string subpath)
{
var cache = GetLoadedPluginsCache();
if (cache == null)
return null;
foreach (var loadedPlugin in cache.GetAll())
{
var pluginAssemblyName = loadedPlugin.AssemblyShim.Assembly.GetName().Name;
var pathToPlugin = Path.Combine(pathToPlugins, pluginAssemblyName);
var pathCandidate = Path.Combine(pathToPlugin, SanitizeSubPath(subpath));
if (File.Exists(pathCandidate))
return new PhysicalFileProvider(pathToPlugin);
}
return null;
}
private string SanitizeSubPath(string subPath)
{
if (subPath.StartsWith('/'))
return subPath.Substring(1);
return subPath;
}
public IDirectoryContents GetDirectoryContents(string subpath)
{
var pluginFileProvider = GetPluginFileProvider(subpath);
if (pluginFileProvider != null)
return pluginFileProvider.GetDirectoryContents(subpath);
return this.webRootFileProvider.GetDirectoryContents(subpath);
}
public IFileInfo GetFileInfo(string subpath)
{
var pluginFileProvider = GetPluginFileProvider(subpath);
if (pluginFileProvider != null)
return pluginFileProvider.GetFileInfo(subpath);
return this.webRootFileProvider.GetFileInfo(subpath);
}
public IChangeToken Watch(string filter)
{
return this.webRootFileProvider.Watch(filter);
}
}
}

View File

@ -0,0 +1,9 @@
using Prise.Caching;
namespace Prise.Mvc
{
public static class DefaultStaticPluginCacheAccessor
{
public static IPluginCache CurrentCache { get; internal set; }
}
}

View File

@ -0,0 +1,23 @@
using System;
using Prise.Caching;
namespace Prise.Mvc
{
public class DefaultStaticPluginCacheAccessorBootstrapper : IPluginCacheAccessorBootstrapper
{
protected bool isBootstrapped;
public DefaultStaticPluginCacheAccessorBootstrapper(IPluginCache cache)
{
if (this.isBootstrapped)
throw new NotSupportedException($"IPluginCache was already bootstrapped");
this.SetCurrentCache(cache);
this.isBootstrapped = true;
}
public void SetCurrentCache(IPluginCache cache)
{
DefaultStaticPluginCacheAccessor.CurrentCache = cache;
}
}
}

14
Vendor/Prise.Mvc/IMvcPluginLoader.cs vendored Normal file
View File

@ -0,0 +1,14 @@
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
namespace Prise.Mvc
{
public interface IMvcPluginLoader
{
Task<IEnumerable<AssemblyScanResult>> FindPlugins<T>(string pathToPlugins);
Task<IAssemblyShim> LoadPluginAssembly<T>(AssemblyScanResult plugin, Action<PluginLoadContext> configure = null);
Task UnloadPluginAssembly<T>(AssemblyScanResult plugin);
}
}

View File

@ -0,0 +1,9 @@
using Prise.Caching;
namespace Prise.Mvc
{
public interface IPluginCacheAccessorBootstrapper
{
void SetCurrentCache(IPluginCache cache);
}
}

View File

@ -0,0 +1,7 @@
namespace Prise.Mvc
{
public interface IPriseMvcActionDescriptorChangeProvider
{
void TriggerPluginChanged();
}
}

15
Vendor/Prise.Mvc/PluginAssemblyPart.cs vendored Normal file
View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
namespace Prise.Mvc
{
public class PluginAssemblyPart : AssemblyPart, ICompilationReferencesProvider
{
public PluginAssemblyPart(Assembly assembly) : base(assembly) { }
// This solves the NullRef bug for in-memory assemblies from Prise
IEnumerable<string> ICompilationReferencesProvider.GetReferencePaths() => Array.Empty<string>();
}
}

98
Vendor/Prise.Mvc/Prise.Mvc.csproj vendored Normal file
View File

@ -0,0 +1,98 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- <TargetFrameworks>netcoreapp2.1;</TargetFrameworks> -->
<TargetFrameworks>netcoreapp2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
<Title>Prise.Mvc</Title>
<PackageId>Prise.Mvc</PackageId>
<PackageDescription>Adds ASP.NET MVC features to an existing Prise setup!</PackageDescription>
<Authors>Maarten Merken</Authors>
<Company>MRKN</Company>
<PackageTags>mvc;controllers;plugin</PackageTags>
<PackageLicenseUrl>https://raw.githubusercontent.com/merken/Prise/master/LICENSE</PackageLicenseUrl>
<PackageProjectUrl>https://github.com/merken/Prise</PackageProjectUrl>
<RepositoryUrl>https://github.com/merken/Prise.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage</TargetsForTfmSpecificBuildOutput>
</PropertyGroup>
<Target DependsOnTargets="ResolveReferences" Name="CopyProjectReferencesToPackage">
<ItemGroup>
<BuildOutputInPackage Include="@(ReferenceCopyLocalPaths-&gt;WithMetadataValue('ReferenceSourceTarget', 'ProjectReference'))" />
</ItemGroup>
</Target>
<PropertyGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'">
<DefineConstants>NETCORE2_1</DefineConstants>
</PropertyGroup>
<!-- Constants (NETCORE2_1, NETCORE3_1) are one or the other, if multiple constants are required, prefix it with $(DefineConstants);-->
<PropertyGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1'">
<DefineConstants>NETCORE3_1</DefineConstants>
</PropertyGroup>
<PropertyGroup>
<PackageIcon>icon.png</PackageIcon>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<ItemGroup>
<None Include="../icon.png">
<Pack>True</Pack>
<PackagePath></PackagePath>
</None>
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'">
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.1.3" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor" Version="2.1.3" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Physical" Version="2.1.1" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.0'">
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Physical" Version="3.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.0.2" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1'">
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Physical" Version="3.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.1.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net5.0'">
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Physical" Version="5.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="5.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Physical" Version="6.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Prise.Plugin\Prise.Plugin.csproj">
<ReferenceOutputAssembly>true</ReferenceOutputAssembly>
<IncludeAssets>Prise.Plugin.dll</IncludeAssets>
</ProjectReference>
<ProjectReference Include="..\Prise.Proxy\Prise.Proxy.csproj">
<ReferenceOutputAssembly>true</ReferenceOutputAssembly>
<IncludeAssets>Prise.Proxy.dll</IncludeAssets>
</ProjectReference>
<ProjectReference Include="..\Prise\Prise.csproj">
<ReferenceOutputAssembly>true</ReferenceOutputAssembly>
<IncludeAssets>Prise.dll</IncludeAssets>
</ProjectReference>
</ItemGroup>
</Project>

118
Vendor/Prise.Mvc/PriseMvcExtensions.cs vendored Normal file
View File

@ -0,0 +1,118 @@
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Razor;
#if !NETCORE2_1
using Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation;
#endif
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Prise.DependencyInjection;
using Prise.Activation;
using Prise.AssemblyLoading;
using Prise.AssemblyScanning;
using Prise.Proxy;
using Prise.Caching;
namespace Prise.Mvc
{
public static class PriseMvcExtensions
{
internal static PluginLoadContext AddMvcTypes(this PluginLoadContext loadContext)
{
return loadContext.AddHostTypes(new[] { typeof(ControllerBase) });
}
internal static PluginLoadContext AddMvcRazorTypes(this PluginLoadContext loadContext)
{
return loadContext.AddHostTypes(new[] { typeof(ControllerBase), typeof(ITempDataDictionaryFactory) });
}
public static IServiceCollection AddCorePriseServices(this IServiceCollection services)
{
return services
.AddFactory<IAssemblyScanner>(DefaultFactories.DefaultAssemblyScanner, ServiceLifetime.Scoped)
.AddFactory<IPluginTypeSelector>(DefaultFactories.DefaultPluginTypeSelector, ServiceLifetime.Scoped)
.AddFactory<IParameterConverter>(DefaultFactories.DefaultParameterConverter, ServiceLifetime.Scoped)
.AddFactory<IResultConverter>(DefaultFactories.DefaultResultConverter, ServiceLifetime.Scoped)
.AddFactory<IPluginActivationContextProvider>(DefaultFactories.DefaultPluginActivationContextProvider, ServiceLifetime.Scoped)
.AddFactory<IRemotePluginActivator>(DefaultFactories.DefaultRemotePluginActivator, ServiceLifetime.Scoped)
.AddFactory<IPluginProxyCreator>(DefaultFactories.DefaultPluginProxyCreator, ServiceLifetime.Scoped)
.AddFactory<IAssemblyLoader>(DefaultFactories.DefaultAssemblyLoader, ServiceLifetime.Singleton);
}
/// <summary>
/// Does all of the plumbing to load API Controllers as Prise Plugins.
/// Limitiations:
/// - No DispatchProxy can be used, backwards compatability is compromised (DispatchProxy requires an interface as base class, not ControllerBase)
/// - Plugin Cache is set to Singleton because we cannot unload assemblies, this would disable the controller routing (and result in 404)
/// - Assembly unloading is disabled, memory leaks can occur
/// </summary>
/// <returns>A fully configured Prise setup that allows you to load plugins via the IMvcPluginLoader</returns>
public static IServiceCollection AddPriseMvc(this IServiceCollection services)
{
return services
.AddCorePriseServices()
.AddSingleton<IPluginCache, DefaultScopedPluginCache>()
.AddScoped<IMvcPluginLoader, DefaultMvcPluginLoader>()
.ConfigureMvcServices()
;
}
/// <summary>
/// Does all of the plumbing to load API Controllers and RAZOR Controllers as Prise Plugins.
/// Limitiations:
/// - No DispatchProxy can be used, backwards compatability is compromised (DispatchProxy requires an interface as base class, not ControllerBase)
/// - Plugin Cache is set to Singleton because we cannot unload assemblies, this would disable the controller routing (and result in 404)
/// - Assembly unloading is disabled, memory leaks can occur
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="builder"></param>
/// <param name="webRootPath">By default, this should be the IWebHostEnvironment.WebRootPaht or IHostingEnvironment.WebRootPath</param>
/// <returns></returns>
public static IServiceCollection AddPriseRazorPlugins(this IServiceCollection services, string webRootPath, string pathToPlugins)
{
return services
.AddCorePriseServices()
.AddSingleton<IPluginCache, DefaultScopedPluginCache>()
.AddScoped<IMvcPluginLoader, DefaultMvcRazorPluginLoader>()
.ConfigureMvcServices()
.ConfigureRazorServices(webRootPath, pathToPlugins)
;
}
private static IServiceCollection ConfigureMvcServices(this IServiceCollection services)
{
var actionDescriptorChangeProvider = new DefaultPriseMvcActionDescriptorChangeProvider();
// Registers the change provider
return services
.AddSingleton<IPriseMvcActionDescriptorChangeProvider>(actionDescriptorChangeProvider)
.AddSingleton<IActionDescriptorChangeProvider>(actionDescriptorChangeProvider)
// Registers the activator for controllers from plugin assemblies
.Replace(ServiceDescriptor.Transient<IControllerActivator, DefaultPriseMvcControllerActivator>());
}
private static IServiceCollection ConfigureRazorServices(this IServiceCollection services, string webRootPath, string pathToPlugins)
{
return services
#if NETCORE2_1
.Configure<RazorViewEngineOptions>(options =>
{
options.FileProviders.Add(new DefaultPrisePluginViewsAssemblyFileProvider(webRootPath, pathToPlugins));
})
#else
.Configure<MvcRazorRuntimeCompilationOptions>(options =>
{
options.FileProviders.Add(new DefaultPrisePluginViewsAssemblyFileProvider(webRootPath, pathToPlugins));
})
#endif
// Registers the static Plugin Cache Accessor
.AddSingleton<IPluginCacheAccessorBootstrapper, DefaultStaticPluginCacheAccessorBootstrapper>();
}
}
}

View File

@ -0,0 +1,30 @@
using System;
namespace Prise.Plugin
{
[System.AttributeUsage(System.AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
public sealed class BootstrapperServiceAttribute : System.Attribute
{
Type serviceType;
public Type ServiceType
{
get { return this.serviceType; }
set { this.serviceType = value; }
}
Type bridgeType;
[Obsolete("Usage of a BridgeType is obsolete, please use ProxyType instead. Existing plugins will continue to function as normal.", false)]
public Type BridgeType
{
get { return this.bridgeType; }
set { this.bridgeType = value; }
}
Type proxyType;
public Type ProxyType
{
get { return this.proxyType; }
set { this.proxyType = value; }
}
}
}

View File

@ -0,0 +1,9 @@
using Microsoft.Extensions.DependencyInjection;
namespace Prise.Plugin
{
public interface IPluginBootstrapper
{
IServiceCollection Bootstrap(IServiceCollection services);
}
}

View File

@ -0,0 +1,5 @@
namespace Prise.Plugin
{
[System.AttributeUsage(System.AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
public sealed class PluginActivatedAttribute : System.Attribute { }
}

15
Vendor/Prise.Plugin/PluginAttribute.cs vendored Normal file
View File

@ -0,0 +1,15 @@
using System;
namespace Prise.Plugin
{
[System.AttributeUsage(System.AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public sealed class PluginAttribute : System.Attribute
{
Type pluginType;
public Type PluginType
{
get { return this.pluginType; }
set { this.pluginType = value; }
}
}
}

View File

@ -0,0 +1,15 @@
using System;
namespace Prise.Plugin
{
[System.AttributeUsage(System.AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
public sealed class PluginBootstrapperAttribute : System.Attribute
{
Type pluginType;
public Type PluginType
{
get { return this.pluginType; }
set { this.pluginType = value; }
}
}
}

View File

@ -0,0 +1,8 @@
using System;
namespace Prise.Plugin
{
[Obsolete("Usage of a PluginFactory is obsolete, please use field injection instead. Existing plugins will continue to function as normal.", false)]
[System.AttributeUsage(System.AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
public sealed class PluginFactoryAttribute : System.Attribute { }
}

View File

@ -0,0 +1,37 @@
using System;
namespace Prise.Plugin
{
[System.AttributeUsage(System.AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
public sealed class PluginServiceAttribute : System.Attribute
{
Type serviceType;
public Type ServiceType
{
get { return this.serviceType; }
set { this.serviceType = value; }
}
ProvidedBy providedBy;
public ProvidedBy ProvidedBy
{
get { return this.providedBy; }
set { this.providedBy = value; }
}
Type bridgeType;
[Obsolete("Usage of a BridgeType is obsolete, please use ProxyType instead. Existing plugins will continue to function as normal.", false)]
public Type BridgeType
{
get { return this.bridgeType; }
set { this.bridgeType = value; }
}
Type proxyType;
public Type ProxyType
{
get { return this.proxyType; }
set { this.proxyType = value; }
}
}
}

32
Vendor/Prise.Plugin/Prise.Plugin.csproj vendored Normal file
View File

@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Title>Prise.Plugin</Title>
<PackageId>Prise.Plugin</PackageId>
<PackageDescription>Prise, A .NET Plugin Framework!</PackageDescription>
<Authors>Maarten Merken</Authors>
<Company>MRKN</Company>
<PackageTags>plugin;framework;prise;decoupling;assembly;dispatchproxy;proxy</PackageTags>
<PackageLicenseUrl>https://raw.githubusercontent.com/merken/Prise/master/LICENSE</PackageLicenseUrl>
<PackageProjectUrl>https://github.com/merken/Prise</PackageProjectUrl>
<RepositoryUrl>https://github.com/merken/Prise.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<LangVersion>default</LangVersion>
</PropertyGroup>
<PropertyGroup>
<PackageIcon>icon.png</PackageIcon>
</PropertyGroup>
<ItemGroup>
<None Include="../icon.png">
<Pack>True</Pack>
<PackagePath></PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.1.0" />
</ItemGroup>
</Project>

8
Vendor/Prise.Plugin/ProvidedBy.cs vendored Normal file
View File

@ -0,0 +1,8 @@
namespace Prise.Plugin
{
public enum ProvidedBy
{
Plugin = 0,
Host
}
}

View File

@ -0,0 +1,9 @@
using System;
namespace Prise.Proxy
{
public interface IParameterConverter : IDisposable
{
object ConvertToRemoteType(Type localType, object value);
}
}

View File

@ -0,0 +1,28 @@
using System;
using System.Threading.Tasks;
namespace Prise.Proxy
{
public interface IResultConverter : IDisposable
{
/// <summary>
/// This method should convert a remote value into a local one.
/// This method will be called in case the remote type is not a Task.
/// </summary>
/// <param name="localType">The return type of the host</param>
/// <param name="remoteType">The return type of the remote</param>
/// <param name="value">The value from the remote, this can be different from the remoteType if the remote uses an old contract, in this case, convert to correct type.</param>
/// <returns>A converted local instance</returns>
object ConvertToLocalType(Type localType, Type remoteType, object value);
/// <summary>
/// This method should convert a remote value into a local one.
/// This method will be called in case the remote type is a Task.
/// </summary>
/// <param name="localType">The return type of the host, Task<localType></param>
/// <param name="remoteType">The return type of the remote, Task<remoteType></param>
/// <param name="task">The Task that holds the value from the remote, this can be different from the remoteType if the remote uses an old contract, in this case, convert to correct type.</param>
/// <returns>A Task containing the local type with the remote value</returns>
object ConvertToLocalTypeAsync(Type localType, Type remoteType, Task task);
}
}

16
Vendor/Prise.Proxy/Method.cs vendored Normal file
View File

@ -0,0 +1,16 @@
using System;
namespace Prise.Proxy
{
public struct Method
{
public string Name { get; }
public Type? ReturnType { get; }
public Method(string name, Type? returnType = null)
{
this.Name = name;
this.ReturnType = returnType;
}
}
}

View File

@ -0,0 +1,13 @@
using System;
namespace Prise.Proxy
{
[Flags]
public enum MethodFindingStrategy
{
MethodNameMustMatch,
MethodReturnTypeMustMatch,
ParameterCountMustMatch,
ParameterTypeMustMatch
}
}

16
Vendor/Prise.Proxy/Parameter.cs vendored Normal file
View File

@ -0,0 +1,16 @@
using System;
namespace Prise.Proxy
{
public struct Parameter
{
public string Name { get; }
public Type? Type { get; }
public Parameter(string name, Type? type = null)
{
this.Name = name;
this.Type = type;
}
}
}

View File

@ -0,0 +1,29 @@
using System;
namespace Prise.Proxy
{
public class PassthroughParameterConverter : IParameterConverter
{
protected bool disposed = false;
public object ConvertToRemoteType(Type localType, object value)
{
return value;
}
protected virtual void Dispose(bool disposing)
{
if (!this.disposed && disposing)
{
// Nothing to do here
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}

View File

@ -0,0 +1,12 @@
using System;
namespace Prise.Proxy
{
public class PassthroughResultConverter : ResultConverter
{
public override object Deserialize(Type localType, Type remoteType, object value)
{
return value;
}
}
}

42
Vendor/Prise.Proxy/Prise.Proxy.csproj vendored Normal file
View File

@ -0,0 +1,42 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Title>Prise.Proxy</Title>
<PackageId>Prise.Proxy</PackageId>
<PackageDescription>Prise, A .NET Plugin Framework!</PackageDescription>
<Authors>Maarten Merken</Authors>
<Company>MRKN</Company>
<PackageTags>plugin;framework;prise;decoupling;assembly;dispatchproxy;proxy</PackageTags>
<TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage</TargetsForTfmSpecificBuildOutput>
<Nullable>enable</Nullable>
<LangVersion>default</LangVersion>
<PackageLicenseUrl>https://raw.githubusercontent.com/merken/Prise/master/LICENSE</PackageLicenseUrl>
<PackageProjectUrl>https://github.com/merken/Prise</PackageProjectUrl>
<RepositoryUrl>https://github.com/merken/Prise.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
</PropertyGroup>
<Target DependsOnTargets="ResolveReferences" Name="CopyProjectReferencesToPackage">
<ItemGroup>
<BuildOutputInPackage Include="@(ReferenceCopyLocalPaths-&gt;WithMetadataValue('ReferenceSourceTarget', 'ProjectReference'))" />
</ItemGroup>
</Target>
<ItemGroup>
<PackageReference Include="System.Reflection.Emit" Version="4.7.0" />
</ItemGroup>
<PropertyGroup>
<PackageIcon>icon.png</PackageIcon>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<ItemGroup>
<None Include="../icon.png">
<Pack>True</Pack>
<PackagePath></PackagePath>
</None>
</ItemGroup>
</Project>

254
Vendor/Prise.Proxy/PriseProxy.cs vendored Normal file
View File

@ -0,0 +1,254 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Prise.Proxy
{
/// <summary>
/// This is the PriseProxy static class that encapsulates most of the boilerplate static methods in order to interact with the remote object (Plugin)
/// TODO
/// - Generic Methods (Task<string> Do<T>(T stuff))
/// - Events (not sure if this is ever possible)
/// </summary>
public static class PriseProxy
{
public static object Invoke(object remoteObject, MethodInfo targetMethod, object[] args)
=> Invoke(remoteObject, targetMethod, args, new PassthroughParameterConverter(),
new PassthroughResultConverter());
public static object Invoke(object remoteObject, MethodInfo targetMethod, object[] args,
IParameterConverter parameterConverter, IResultConverter resultConverter)
{
try
{
var localType = targetMethod.ReturnType;
var remoteMethod = FindMethodOnObject(targetMethod, remoteObject);
if (remoteMethod == null)
throw new PriseProxyException(
$"Target method {targetMethod.Name} is not found on Proxy Type {remoteObject.GetType().Name}.");
object result = null;
if (remoteMethod.IsGenericMethod)
{
var generic = remoteMethod.MakeGenericMethod(targetMethod.GetGenericArguments());
result = generic.Invoke(remoteObject, SerializeParameters(parameterConverter, remoteMethod, args));
}
else
result = remoteMethod.Invoke(remoteObject,
SerializeParameters(parameterConverter, remoteMethod, args));
var remoteType = remoteMethod.ReturnType;
if (remoteType.BaseType == typeof(System.Threading.Tasks.Task))
return resultConverter.ConvertToLocalTypeAsync(localType, remoteType,
result as System.Threading.Tasks.Task);
if (remoteType == typeof(System.Threading.Tasks.Task))
return resultConverter.ConvertToLocalTypeAsync(localType, remoteType,
result as System.Threading.Tasks.Task);
if (remoteType == typeof(void))
return null;
return resultConverter.ConvertToLocalType(localType, remoteType, result);
}
catch (Exception ex) when (ex is TargetInvocationException)
{
throw ex.InnerException ?? ex;
}
}
public static MethodInfo FindMethodOnObject(MethodInfo callingMethod, object targetObject)
=> FindMethodOnObject(
targetObject,
new Method(callingMethod.Name, callingMethod.ReturnType),
callingMethod.GetParameters().Select(p => new Parameter(p.Name, p.ParameterType)).ToArray(),
throwOnError: true)!; // Throws error when null
public static MethodInfo? TryFindMethodOnObject(MethodInfo callingMethod, object targetObject)
=> FindMethodOnObject(
targetObject,
new Method(callingMethod.Name, callingMethod.ReturnType),
callingMethod.GetParameters().Select(p => new Parameter(p.Name, p.ParameterType)).ToArray());
public static MethodInfo? FindMethodOnObject(
object targetObject,
Method method,
Parameter[] parameters,
MethodFindingStrategy strategy = MethodFindingStrategy.MethodNameMustMatch |
MethodFindingStrategy.MethodReturnTypeMustMatch |
MethodFindingStrategy.ParameterCountMustMatch |
MethodFindingStrategy.ParameterTypeMustMatch,
bool throwOnError = false)
{
bool isNameCorrect(MethodInfo targetMethod) => targetMethod.Name == method.Name;
// First, find by method name
var targetMethods = targetObject.GetType().GetMethods().AsEnumerable();
if (strategy.HasFlag(MethodFindingStrategy.MethodNameMustMatch))
targetMethods = targetMethods.Where(isNameCorrect);
if (!targetMethods.Any())
if (throwOnError)
throw new PriseProxyException(
$"Target method {method.Name} is not found on Proxy Type {targetObject.GetType().Name}.");
else
return null;
if (targetMethods.Count() == 1)
return targetMethods.First();
bool isReturnTypeCorrect(MethodInfo targetMethod) => targetMethod.ReturnType == method.ReturnType;
if (strategy.HasFlag(MethodFindingStrategy.MethodReturnTypeMustMatch))
// Second, find by method name AND return type
targetMethods = targetMethods.Where(isReturnTypeCorrect);
if (targetMethods.Count() == 1)
return targetMethods.First();
bool isParameterCountCorrect(MethodInfo targetMethod) =>
targetMethod.GetParameters().Count() == parameters.Length;
bool doAllParametersMatch(MethodInfo targetMethod)
{
var callingMethodParameters = parameters;
var targetMethodParameters = targetMethod.GetParameters();
for (var index = 0; index < callingMethodParameters.Count(); index++)
{
var callingParam = callingMethodParameters[index];
if (callingParam.Type is null)
throw new PriseProxyException(
$"When using {nameof(MethodFindingStrategy.ParameterTypeMustMatch)}, Parameter.Type must be provided");
var targetParam = targetMethodParameters[index];
if (!(targetParam.Name == callingParam.Name &&
targetParam.ParameterType.Name == callingParam.Type.Name))
return false;
}
return true;
}
targetMethods = targetMethods.Where(targetMethod =>
(!strategy.HasFlag(MethodFindingStrategy.ParameterCountMustMatch) ||
isParameterCountCorrect(targetMethod)) &&
(!strategy.HasFlag(MethodFindingStrategy.ParameterTypeMustMatch) || doAllParametersMatch(targetMethod))
);
if (!targetMethods.Any())
if (throwOnError)
throw new PriseProxyException(
$"Target method {method.Name} is not found on Proxy Type {targetObject.GetType().Name}.");
else
return null;
if (targetMethods.Count() > 1)
if (throwOnError)
throw new PriseProxyException(
$"Target method {method.Name} could not be determined on object {targetObject.GetType().Name}.");
else
return null;
return targetMethods.First();
}
internal static object[] SerializeParameters(IParameterConverter parameterConverter, MethodInfo targetMethod,
object[] args)
{
var parameters = targetMethod.GetParameters();
var results = new List<object>();
for (var index = 0; index < parameters.Count(); index++)
{
var parameter = parameters[index];
var parameterValue = args[index];
if (parameter.ParameterType.BaseType == typeof(System.MulticastDelegate))
{
if (parameter.ParameterType.GenericTypeArguments.Any(g => g != typeof(EventArgs)))
throw new PriseProxyException($"Custom EventArgs are not supported in Prise");
results.Add(parameterValue);
continue;
}
object result = null;
if (parameter.ParameterType.IsGenericParameter)
{
var runtimeType = parameterValue.GetType();
result = parameterConverter.ConvertToRemoteType(runtimeType, parameterValue);
}
else
result = parameterConverter.ConvertToRemoteType(parameter.ParameterType, parameterValue);
results.Add(result);
}
return results.ToArray();
}
}
/// <summary>
/// This is the PriseProxy wrapper class that will acts as the communication layer between the Host and the Plugin.
/// Every call from the Host to the Plugin will go through here.
/// </summary>
/// <typeparam name="T">The Plugin type</typeparam>
public class PriseProxy<T> : DispatchProxy, IDisposable
{
private IParameterConverter parameterConverter;
private IResultConverter resultConverter;
private object remoteObject;
protected bool disposed = false;
protected override object Invoke(MethodInfo targetMethod, object[] args) => PriseProxy.Invoke(this.remoteObject,
targetMethod, args, this.parameterConverter, this.resultConverter);
public static object Create() => Create<T, PriseProxy<T>>();
internal PriseProxy<T> SetRemoteObject(object remoteObject)
{
if (remoteObject == null)
throw new PriseProxyException($"Remote object for Proxy<{typeof(T).Name}> was null");
this.remoteObject = remoteObject;
return this;
}
internal PriseProxy<T> SetParameterConverter(IParameterConverter parameterConverter)
{
if (parameterConverter == null)
throw new PriseProxyException($"IParameterConverter for Proxy<{typeof(T).Name}> was null");
this.parameterConverter = parameterConverter;
return this;
}
internal PriseProxy<T> SetResultConverter(IResultConverter resultConverter)
{
if (resultConverter == null)
throw new PriseProxyException($"IResultConverter for Proxy<{typeof(T).Name}> was null");
this.resultConverter = resultConverter;
return this;
}
protected virtual void Dispose(bool disposing)
{
if (!this.disposed && disposing)
{
this.parameterConverter = null;
this.resultConverter = null;
this.remoteObject = null;
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}

View File

@ -0,0 +1,25 @@
using System;
using System.Runtime.Serialization;
namespace Prise.Proxy
{
[Serializable]
public class PriseProxyException : Exception
{
public PriseProxyException(string message) : base(message)
{
}
public PriseProxyException(string message, Exception innerException) : base(message, innerException)
{
}
public PriseProxyException()
{
}
protected PriseProxyException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}
}

32
Vendor/Prise.Proxy/ProxyCreator.cs vendored Normal file
View File

@ -0,0 +1,32 @@
using System;
namespace Prise.Proxy
{
public static class ProxyCreator
{
public static object CreateGenericProxy(Type proxyType, object remoteObject)
{
return typeof(ProxyCreator).GetMethod(nameof(ProxyCreator.CreateProxy)).MakeGenericMethod(proxyType).Invoke(null, new[] { remoteObject, null, null });
}
public static TProxyType CreateProxy<TProxyType>(
object remoteObject,
IParameterConverter parameterConverter = null,
IResultConverter resultConverter = null)
{
if (parameterConverter == null)
parameterConverter = new PassthroughParameterConverter();
if (resultConverter == null)
resultConverter = new PassthroughResultConverter();
var proxy = PriseProxy<TProxyType>.Create();
((PriseProxy<TProxyType>)proxy)
.SetRemoteObject(remoteObject)
.SetParameterConverter(parameterConverter)
.SetResultConverter(resultConverter);
return (TProxyType)proxy;
}
}
}

63
Vendor/Prise.Proxy/ResultConverter.cs vendored Normal file
View File

@ -0,0 +1,63 @@
using System;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
namespace Prise.Proxy
{
public abstract class ResultConverter : IResultConverter
{
private bool disposed = false;
public abstract object Deserialize(Type localType, Type remoteType, object value);
public object ConvertToLocalType(Type localType, Type remoteType, object value)
{
return Deserialize(localType, remoteType, value);
}
public object ConvertToLocalTypeAsync(Type localType, Type remoteType, Task task)
{
var taskResultType = localType.GenericTypeArguments != null && localType.GenericTypeArguments.Any() ? localType.GenericTypeArguments[0] : null;
var taskCompletionSource = new TaskCompletionSource(taskResultType ?? typeof(object));
task.ContinueWith(t =>
{
if (t.IsCanceled)
taskCompletionSource.TrySetCanceled();
else if (t.IsFaulted)
taskCompletionSource.TrySetException(t.Exception);
else if (taskResultType == null)
taskCompletionSource.TrySetResult(null);
else
{
var property = t.GetType()
.GetTypeInfo()
.GetProperties()
.FirstOrDefault(p => p.Name == "Result");
if (property != null)
{
var value = Deserialize(localType, remoteType, property.GetValue(task));
taskCompletionSource.TrySetResult(value);
}
}
});
return taskCompletionSource.Task;
}
protected virtual void Dispose(bool disposing)
{
if (!this.disposed && disposing)
{
// Nothing to do here
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}

View File

@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
namespace Prise.Proxy
{
/// <summary>
/// This is a non-generic TaskCompletionSource<T>
/// It allows you to set a type via its constructor
/// </summary>
public class TaskCompletionSource
{
private readonly Type type;
private readonly object taskCompletionSource;
public TaskCompletionSource(Type type)
{
this.type = type;
this.taskCompletionSource = typeof(TaskCompletionSource<>)
.MakeGenericType(type)
.GetConstructors(BindingFlags.Public | BindingFlags.Instance)[0]
.Invoke(null);
}
public bool TrySetCanceled()
{
var trySetCanceled = taskCompletionSource.GetType().GetMethod("TrySetCanceled");
return (bool)trySetCanceled.Invoke(this.taskCompletionSource, null);
}
public bool TrySetException(Exception ex)
{
var trySetException = taskCompletionSource.GetType()
.GetMethods()
.First(m => m.Name == "TrySetException" && m.GetParameters().First().ParameterType == typeof(IEnumerable<Exception>));
return (bool)trySetException.Invoke(this.taskCompletionSource, new[] { new[] { ex } });
}
public bool TrySetResult(object result)
{
var trySetResult = taskCompletionSource.GetType().GetMethod("TrySetResult");
return (bool)trySetResult.Invoke(this.taskCompletionSource, new[] { result });
}
public Task Task
{
get
{
var taskProperty = taskCompletionSource.GetType().GetProperty("Task");
return taskProperty.GetValue(this.taskCompletionSource) as Task;
}
}
}
}

View File

@ -0,0 +1,38 @@
namespace System.Reflection
{
/// <summary>
/// DispatchProxy provides a mechanism for the instantiation of proxy objects and handling of
/// their method dispatch.
/// Original file: https://github.com/dotnet/runtime/blob/6072e4d3a7a2a1493f514cdf4be75a3d56580e84/src/libraries/System.Reflection.DispatchProxy/src/System/Reflection/DispatchProxy.cs
/// </summary>
public abstract class DispatchProxy
{
protected DispatchProxy()
{
}
/// <summary>
/// Whenever any method on the generated proxy type is called, this method
/// will be invoked to dispatch control.
/// </summary>
/// <param name="targetMethod">The method the caller invoked</param>
/// <param name="args">The arguments the caller passed to the method</param>
/// <returns>The object to return to the caller, or <c>null</c> for void methods</returns>
protected abstract object? Invoke(MethodInfo? targetMethod, object?[]? args);
/// <summary>
/// Creates an object instance that derives from class <typeparamref name="TProxy"/>
/// and implements interface <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The interface the proxy should implement.</typeparam>
/// <typeparam name="TProxy">The base class to use for the proxy class.</typeparam>
/// <returns>An object instance that implements <typeparamref name="T"/>.</returns>
/// <exception cref="System.ArgumentException"><typeparamref name="T"/> is a class,
/// or <typeparamref name="TProxy"/> is sealed or does not have a parameterless constructor</exception>
public static T Create<T, TProxy>()
where TProxy : DispatchProxy
{
return (T)DispatchProxyGenerator.CreateProxyInstance(typeof(TProxy), typeof(T));
}
}
}

View File

@ -0,0 +1,935 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// Original file: https://github.com/dotnet/runtime/blob/6072e4d3a7a2a1493f514cdf4be75a3d56580e84/src/libraries/System.Reflection.DispatchProxy/src/System/Reflection/DispatchProxyGenerator.cs
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.ExceptionServices;
using System.Threading;
namespace System.Reflection
{
// Helper class to handle the IL EMIT for the generation of proxies.
// Much of this code was taken directly from the Silverlight proxy generation.
// Differences between this and the Silverlight version are:
// 1. This version is based on DispatchProxy from NET Native and CoreCLR, not RealProxy in Silverlight ServiceModel.
// There are several notable differences between them.
// 2. Both DispatchProxy and RealProxy permit the caller to ask for a proxy specifying a pair of types:
// the interface type to implement, and a base type. But they behave slightly differently:
// - RealProxy generates a proxy type that derives from Object and *implements" all the base type's
// interfaces plus all the interface type's interfaces.
// - DispatchProxy generates a proxy type that *derives* from the base type and implements all
// the interface type's interfaces. This is true for both the CLR version in NET Native and this
// version for CoreCLR.
// 3. DispatchProxy and RealProxy use different type hierarchies for the generated proxies:
// - RealProxy type hierarchy is:
// proxyType : proxyBaseType : object
// Presumably the 'proxyBaseType' in the middle is to allow it to implement the base type's interfaces
// explicitly, preventing collision for same name methods on the base and interface types.
// - DispatchProxy hierarchy is:
// proxyType : baseType (where baseType : DispatchProxy)
// The generated DispatchProxy proxy type does not need to generate implementation methods
// for the base type's interfaces, because the base type already must have implemented them.
// 4. RealProxy required a proxy instance to hold a backpointer to the RealProxy instance to mirror
// the .NET Remoting design that required the proxy and RealProxy to be separate instances.
// But the DispatchProxy design encourages the proxy type to *be* an DispatchProxy. Therefore,
// the proxy's 'this' becomes the equivalent of RealProxy's backpointer to RealProxy, so we were
// able to remove an extraneous field and ctor arg from the DispatchProxy proxies.
//
internal static class DispatchProxyGenerator
{
// Generated proxies have a private Action field that all generated methods
// invoke. It is the first field in the class and the first ctor parameter.
private const int InvokeActionFieldAndCtorParameterIndex = 0;
// Proxies are requested for a pair of types: base type and interface type.
// The generated proxy will subclass the given base type and implement the interface type.
// We maintain a cache keyed by 'base type' containing a dictionary keyed by interface type,
// containing the generated proxy type for that pair. There are likely to be few (maybe only 1)
// base type in use for many interface types.
// Note: this differs from Silverlight's RealProxy implementation which keys strictly off the
// interface type. But this does not allow the same interface type to be used with more than a
// single base type. The implementation here permits multiple interface types to be used with
// multiple base types, and the generated proxy types will be unique.
// This cache of generated types grows unbounded, one element per unique T/ProxyT pair.
// This approach is used to prevent regenerating identical proxy types for identical T/Proxy pairs,
// which would ultimately be a more expensive leak.
// Proxy instances are not cached. Their lifetime is entirely owned by the caller of DispatchProxy.Create.
private static readonly Dictionary<Type, Dictionary<Type, Type>> s_baseTypeAndInterfaceToGeneratedProxyType = new Dictionary<Type, Dictionary<Type, Type>>();
private static readonly ProxyAssembly s_proxyAssembly = new ProxyAssembly();
private static readonly MethodInfo s_dispatchProxyInvokeMethod = typeof(DispatchProxy).GetTypeInfo().GetDeclaredMethod("Invoke")!;
// Returns a new instance of a proxy the derives from 'baseType' and implements 'interfaceType'
internal static object CreateProxyInstance(Type baseType, Type interfaceType)
{
Debug.Assert(baseType != null);
Debug.Assert(interfaceType != null);
Type proxiedType = GetProxyType(baseType!, interfaceType!);
return Activator.CreateInstance(proxiedType, (Action<object[]>)DispatchProxyGenerator.Invoke)!;
}
private static Type GetProxyType(Type baseType, Type interfaceType)
{
lock (s_baseTypeAndInterfaceToGeneratedProxyType)
{
if (!s_baseTypeAndInterfaceToGeneratedProxyType.TryGetValue(baseType, out Dictionary<Type, Type>? interfaceToProxy))
{
interfaceToProxy = new Dictionary<Type, Type>();
s_baseTypeAndInterfaceToGeneratedProxyType[baseType] = interfaceToProxy;
}
if (!interfaceToProxy.TryGetValue(interfaceType, out Type? generatedProxy))
{
generatedProxy = GenerateProxyType(baseType, interfaceType);
interfaceToProxy[interfaceType] = generatedProxy;
}
return generatedProxy;
}
}
// Unconditionally generates a new proxy type derived from 'baseType' and implements 'interfaceType'
private static Type GenerateProxyType(Type baseType, Type interfaceType)
{
// Parameter validation is deferred until the point we need to create the proxy.
// This prevents unnecessary overhead revalidating cached proxy types.
TypeInfo baseTypeInfo = baseType.GetTypeInfo();
// The interface type must be an interface, not a class
if (!interfaceType.GetTypeInfo().IsInterface)
{
// "T" is the generic parameter seen via the public contract
throw new ArgumentException("SR.Format(SR.InterfaceType_Must_Be_Interface, interfaceType.FullName)", "T");
}
// The base type cannot be sealed because the proxy needs to subclass it.
if (baseTypeInfo.IsSealed)
{
// "TProxy" is the generic parameter seen via the public contract
throw new ArgumentException("SR.Format(SR.BaseType_Cannot_Be_Sealed, baseTypeInfo.FullName)", "TProxy");
}
// The base type cannot be abstract
if (baseTypeInfo.IsAbstract)
{
throw new ArgumentException("SR.Format(SR.BaseType_Cannot_Be_Abstract, baseType.FullName)", "TProxy");
}
// The base type must have a public default ctor
if (!baseTypeInfo.DeclaredConstructors.Any(c => c.IsPublic && c.GetParameters().Length == 0))
{
throw new ArgumentException("SR.Format(SR.BaseType_Must_Have_Default_Ctor, baseType.FullName)", "TProxy");
}
// Create a type that derives from 'baseType' provided by caller
ProxyBuilder pb = s_proxyAssembly.CreateProxy("generatedProxy", baseType);
foreach (Type t in interfaceType.GetTypeInfo().ImplementedInterfaces)
pb.AddInterfaceImpl(t);
pb.AddInterfaceImpl(interfaceType);
Type generatedProxyType = pb.CreateType();
return generatedProxyType;
}
// All generated proxy methods call this static helper method to dispatch.
// Its job is to unpack the arguments and the 'this' instance and to dispatch directly
// to the (abstract) DispatchProxy.Invoke() method.
private static void Invoke(object?[] args)
{
PackedArgs packed = new PackedArgs(args);
MethodBase method = s_proxyAssembly.ResolveMethodToken(packed.DeclaringType, packed.MethodToken);
if (method.IsGenericMethodDefinition)
method = ((MethodInfo)method).MakeGenericMethod(packed.GenericTypes!);
// Call (protected method) DispatchProxy.Invoke()
try
{
Debug.Assert(s_dispatchProxyInvokeMethod != null);
object? returnValue = s_dispatchProxyInvokeMethod!.Invoke(packed.DispatchProxy,
new object?[] { method, packed.Args });
packed.ReturnValue = returnValue;
}
catch (TargetInvocationException tie)
{
Debug.Assert(tie.InnerException != null);
ExceptionDispatchInfo.Capture(tie.InnerException).Throw();
}
}
private class PackedArgs
{
internal const int DispatchProxyPosition = 0;
internal const int DeclaringTypePosition = 1;
internal const int MethodTokenPosition = 2;
internal const int ArgsPosition = 3;
internal const int GenericTypesPosition = 4;
internal const int ReturnValuePosition = 5;
internal static readonly Type[] PackedTypes = new Type[] { typeof(object), typeof(Type), typeof(int), typeof(object[]), typeof(Type[]), typeof(object) };
private readonly object?[] _args;
internal PackedArgs() : this(new object[PackedTypes.Length]) { }
internal PackedArgs(object?[] args) { _args = args; }
internal DispatchProxy? DispatchProxy { get { return (DispatchProxy?)_args[DispatchProxyPosition]; } }
internal Type? DeclaringType { get { return (Type?)_args[DeclaringTypePosition]; } }
internal int MethodToken { get { return (int)_args[MethodTokenPosition]!; } }
internal object[]? Args { get { return (object[]?)_args[ArgsPosition]; } }
internal Type[]? GenericTypes { get { return (Type[]?)_args[GenericTypesPosition]; } }
internal object? ReturnValue { /*get { return args[ReturnValuePosition]; }*/ set { _args[ReturnValuePosition] = value; } }
}
private class ProxyAssembly
{
private readonly AssemblyBuilder _ab;
private readonly ModuleBuilder _mb;
private int _typeId;
// Maintain a MethodBase-->int, int-->MethodBase mapping to permit generated code
// to pass methods by token
private readonly Dictionary<MethodBase, int> _methodToToken = new Dictionary<MethodBase, int>();
private readonly List<MethodBase> _methodsByToken = new List<MethodBase>();
private readonly HashSet<string?> _ignoresAccessAssemblyNames = new HashSet<string?>();
private ConstructorInfo? _ignoresAccessChecksToAttributeConstructor;
public ProxyAssembly()
{
_ab = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("ProxyBuilder"), AssemblyBuilderAccess.Run);
_mb = _ab.DefineDynamicModule("testmod");
}
// Gets or creates the ConstructorInfo for the IgnoresAccessChecksAttribute.
// This attribute is both defined and referenced in the dynamic assembly to
// allow access to internal types in other assemblies.
internal ConstructorInfo IgnoresAccessChecksAttributeConstructor
{
get
{
if (_ignoresAccessChecksToAttributeConstructor == null)
{
_ignoresAccessChecksToAttributeConstructor = IgnoreAccessChecksToAttributeBuilder.AddToModule(_mb);
}
return null;
}
}
public ProxyBuilder CreateProxy(string name, Type proxyBaseType)
{
int nextId = Interlocked.Increment(ref _typeId);
TypeBuilder tb = _mb.DefineType(name + "_" + nextId, TypeAttributes.Public, proxyBaseType);
return new ProxyBuilder(this, tb, proxyBaseType);
}
// Generates an instance of the IgnoresAccessChecksToAttribute to
// identify the given assembly as one which contains internal types
// the dynamic assembly will need to reference.
internal void GenerateInstanceOfIgnoresAccessChecksToAttribute(string assemblyName)
{
// Add this assembly level attribute:
// [assembly: System.Runtime.CompilerServices.IgnoresAccessChecksToAttribute(assemblyName)]
ConstructorInfo attributeConstructor = IgnoresAccessChecksAttributeConstructor;
CustomAttributeBuilder customAttributeBuilder =
new CustomAttributeBuilder(attributeConstructor, new object[] { assemblyName });
_ab.SetCustomAttribute(customAttributeBuilder);
}
// Ensures the type we will reference from the dynamic assembly
// is visible. Non-public types need to emit an attribute that
// allows access from the dynamic assembly.
internal void EnsureTypeIsVisible(Type type)
{
TypeInfo typeInfo = type.GetTypeInfo();
if (!typeInfo.IsVisible)
{
string assemblyName = typeInfo.Assembly.GetName().Name!;
if (!_ignoresAccessAssemblyNames.Contains(assemblyName))
{
GenerateInstanceOfIgnoresAccessChecksToAttribute(assemblyName);
_ignoresAccessAssemblyNames.Add(assemblyName);
}
}
}
internal void GetTokenForMethod(MethodBase method, out Type type, out int token)
{
Debug.Assert(method.DeclaringType != null);
type = method.DeclaringType!;
token = 0;
if (!_methodToToken.TryGetValue(method, out token))
{
_methodsByToken.Add(method);
token = _methodsByToken.Count - 1;
_methodToToken[method] = token;
}
}
internal MethodBase ResolveMethodToken(Type? type, int token)
{
Debug.Assert(token >= 0 && token < _methodsByToken.Count);
return _methodsByToken[token];
}
}
private class ProxyBuilder
{
private static readonly MethodInfo s_delegateInvoke = typeof(Action<object[]>).GetTypeInfo().GetDeclaredMethod("Invoke")!;
private readonly ProxyAssembly _assembly;
private readonly TypeBuilder _tb;
private readonly Type _proxyBaseType;
private readonly List<FieldBuilder> _fields;
internal ProxyBuilder(ProxyAssembly assembly, TypeBuilder tb, Type proxyBaseType)
{
_assembly = assembly;
_tb = tb;
_proxyBaseType = proxyBaseType;
_fields = new List<FieldBuilder>();
_fields.Add(tb.DefineField("invoke", typeof(Action<object[]>), FieldAttributes.Private));
}
private void Complete()
{
Type[] args = new Type[_fields.Count];
for (int i = 0; i < args.Length; i++)
{
args[i] = _fields[i].FieldType;
}
ConstructorBuilder cb = _tb.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, args);
ILGenerator il = cb.GetILGenerator();
// chained ctor call
ConstructorInfo? baseCtor = _proxyBaseType.GetTypeInfo().DeclaredConstructors.SingleOrDefault(c => c.IsPublic && c.GetParameters().Length == 0);
Debug.Assert(baseCtor != null);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, baseCtor!);
// store all the fields
for (int i = 0; i < args.Length; i++)
{
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg, i + 1);
il.Emit(OpCodes.Stfld, _fields[i]);
}
il.Emit(OpCodes.Ret);
}
internal Type CreateType()
{
this.Complete();
return _tb.CreateTypeInfo()!.AsType();
}
internal void AddInterfaceImpl(Type iface)
{
// If necessary, generate an attribute to permit visibility
// to internal types.
_assembly.EnsureTypeIsVisible(iface);
_tb.AddInterfaceImplementation(iface);
// AccessorMethods -> Metadata mappings.
var propertyMap = new Dictionary<MethodInfo, PropertyAccessorInfo>(MethodInfoEqualityComparer.Instance);
foreach (PropertyInfo pi in iface.GetRuntimeProperties())
{
var ai = new PropertyAccessorInfo(pi.GetMethod, pi.SetMethod);
if (pi.GetMethod != null)
propertyMap[pi.GetMethod] = ai;
if (pi.SetMethod != null)
propertyMap[pi.SetMethod] = ai;
}
var eventMap = new Dictionary<MethodInfo, EventAccessorInfo>(MethodInfoEqualityComparer.Instance);
foreach (EventInfo ei in iface.GetRuntimeEvents())
{
var ai = new EventAccessorInfo(ei.AddMethod, ei.RemoveMethod, ei.RaiseMethod);
if (ei.AddMethod != null)
eventMap[ei.AddMethod] = ai;
if (ei.RemoveMethod != null)
eventMap[ei.RemoveMethod] = ai;
if (ei.RaiseMethod != null)
eventMap[ei.RaiseMethod] = ai;
}
foreach (MethodInfo mi in iface.GetRuntimeMethods())
{
// Skip regular/non-virtual instance methods, static methods, and methods that cannot be overriden
// ("methods that cannot be overriden" includes default implementation of other interface methods).
if (!mi.IsVirtual || mi.IsFinal)
continue;
MethodBuilder mdb = AddMethodImpl(mi);
if (propertyMap.TryGetValue(mi, out PropertyAccessorInfo? associatedProperty))
{
if (MethodInfoEqualityComparer.Instance.Equals(associatedProperty.InterfaceGetMethod, mi))
associatedProperty.GetMethodBuilder = mdb;
else
associatedProperty.SetMethodBuilder = mdb;
}
if (eventMap.TryGetValue(mi, out EventAccessorInfo? associatedEvent))
{
if (MethodInfoEqualityComparer.Instance.Equals(associatedEvent.InterfaceAddMethod, mi))
associatedEvent.AddMethodBuilder = mdb;
else if (MethodInfoEqualityComparer.Instance.Equals(associatedEvent.InterfaceRemoveMethod, mi))
associatedEvent.RemoveMethodBuilder = mdb;
else
associatedEvent.RaiseMethodBuilder = mdb;
}
}
foreach (PropertyInfo pi in iface.GetRuntimeProperties())
{
PropertyAccessorInfo ai = propertyMap[pi.GetMethod ?? pi.SetMethod!];
// If we didn't make an overriden accessor above, this was a static property, non-virtual property,
// or a default implementation of a property of a different interface. In any case, we don't need
// to redeclare it.
if (ai.GetMethodBuilder == null && ai.SetMethodBuilder == null)
continue;
PropertyBuilder pb = _tb.DefineProperty(pi.Name, pi.Attributes, pi.PropertyType, pi.GetIndexParameters().Select(p => p.ParameterType).ToArray());
if (ai.GetMethodBuilder != null)
pb.SetGetMethod(ai.GetMethodBuilder);
if (ai.SetMethodBuilder != null)
pb.SetSetMethod(ai.SetMethodBuilder);
}
foreach (EventInfo ei in iface.GetRuntimeEvents())
{
EventAccessorInfo ai = eventMap[ei.AddMethod ?? ei.RemoveMethod!];
// If we didn't make an overriden accessor above, this was a static event, non-virtual event,
// or a default implementation of an event of a different interface. In any case, we don't
// need to redeclare it.
if (ai.AddMethodBuilder == null && ai.RemoveMethodBuilder == null && ai.RaiseMethodBuilder == null)
continue;
Debug.Assert(ei.EventHandlerType != null);
EventBuilder eb = _tb.DefineEvent(ei.Name, ei.Attributes, ei.EventHandlerType!);
if (ai.AddMethodBuilder != null)
eb.SetAddOnMethod(ai.AddMethodBuilder);
if (ai.RemoveMethodBuilder != null)
eb.SetRemoveOnMethod(ai.RemoveMethodBuilder);
if (ai.RaiseMethodBuilder != null)
eb.SetRaiseMethod(ai.RaiseMethodBuilder);
}
}
private MethodBuilder AddMethodImpl(MethodInfo mi)
{
ParameterInfo[] parameters = mi.GetParameters();
Type[] paramTypes = ParamTypes(parameters, false);
MethodBuilder mdb = _tb.DefineMethod(mi.Name, MethodAttributes.Public | MethodAttributes.Virtual, mi.ReturnType, paramTypes);
if (mi.ContainsGenericParameters)
{
Type[] ts = mi.GetGenericArguments();
string[] ss = new string[ts.Length];
for (int i = 0; i < ts.Length; i++)
{
ss[i] = ts[i].Name;
}
GenericTypeParameterBuilder[] genericParameters = mdb.DefineGenericParameters(ss);
for (int i = 0; i < genericParameters.Length; i++)
{
genericParameters[i].SetGenericParameterAttributes(ts[i].GetTypeInfo().GenericParameterAttributes);
}
}
ILGenerator il = mdb.GetILGenerator();
ParametersArray args = new ParametersArray(il, paramTypes);
// object[] args = new object[paramCount];
il.Emit(OpCodes.Nop);
GenericArray<object> argsArr = new GenericArray<object>(il, ParamTypes(parameters, true).Length);
for (int i = 0; i < parameters.Length; i++)
{
// args[i] = argi;
bool isOutRef = parameters[i].IsOut && parameters[i].ParameterType.IsByRef && !parameters[i].IsIn;
if (!isOutRef)
{
argsArr.BeginSet(i);
args.Get(i);
argsArr.EndSet(parameters[i].ParameterType);
}
}
// object[] packed = new object[PackedArgs.PackedTypes.Length];
GenericArray<object> packedArr = new GenericArray<object>(il, PackedArgs.PackedTypes.Length);
// packed[PackedArgs.DispatchProxyPosition] = this;
packedArr.BeginSet(PackedArgs.DispatchProxyPosition);
il.Emit(OpCodes.Ldarg_0);
packedArr.EndSet(typeof(DispatchProxy));
// packed[PackedArgs.DeclaringTypePosition] = typeof(iface);
MethodInfo Type_GetTypeFromHandle = typeof(Type).GetRuntimeMethod("GetTypeFromHandle", new Type[] { typeof(RuntimeTypeHandle) })!;
_assembly.GetTokenForMethod(mi, out Type declaringType, out int methodToken);
packedArr.BeginSet(PackedArgs.DeclaringTypePosition);
il.Emit(OpCodes.Ldtoken, declaringType);
il.Emit(OpCodes.Call, Type_GetTypeFromHandle);
packedArr.EndSet(typeof(object));
// packed[PackedArgs.MethodTokenPosition] = iface method token;
packedArr.BeginSet(PackedArgs.MethodTokenPosition);
il.Emit(OpCodes.Ldc_I4, methodToken);
packedArr.EndSet(typeof(int));
// packed[PackedArgs.ArgsPosition] = args;
packedArr.BeginSet(PackedArgs.ArgsPosition);
argsArr.Load();
packedArr.EndSet(typeof(object[]));
// packed[PackedArgs.GenericTypesPosition] = mi.GetGenericArguments();
if (mi.ContainsGenericParameters)
{
packedArr.BeginSet(PackedArgs.GenericTypesPosition);
Type[] genericTypes = mi.GetGenericArguments();
GenericArray<Type> typeArr = new GenericArray<Type>(il, genericTypes.Length);
for (int i = 0; i < genericTypes.Length; ++i)
{
typeArr.BeginSet(i);
il.Emit(OpCodes.Ldtoken, genericTypes[i]);
il.Emit(OpCodes.Call, Type_GetTypeFromHandle);
typeArr.EndSet(typeof(Type));
}
typeArr.Load();
packedArr.EndSet(typeof(Type[]));
}
// Call static DispatchProxyHelper.Invoke(object[])
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, _fields[InvokeActionFieldAndCtorParameterIndex]); // delegate
packedArr.Load();
il.Emit(OpCodes.Call, s_delegateInvoke);
for (int i = 0; i < parameters.Length; i++)
{
if (parameters[i].ParameterType.IsByRef)
{
args.BeginSet(i);
argsArr.Get(i);
args.EndSet(i, typeof(object));
}
}
if (mi.ReturnType != typeof(void))
{
packedArr.Get(PackedArgs.ReturnValuePosition);
Convert(il, typeof(object), mi.ReturnType, false);
}
il.Emit(OpCodes.Ret);
_tb.DefineMethodOverride(mdb, mi);
return mdb;
}
private static Type[] ParamTypes(ParameterInfo[] parms, bool noByRef)
{
Type[] types = new Type[parms.Length];
for (int i = 0; i < parms.Length; i++)
{
types[i] = parms[i].ParameterType;
if (noByRef && types[i].IsByRef)
types[i] = types[i].GetElementType()!;
}
return types;
}
// TypeCode does not exist in ProjectK or ProjectN.
// This lookup method was copied from PortableLibraryThunks\Internal\PortableLibraryThunks\System\TypeThunks.cs
// but returns the integer value equivalent to its TypeCode enum.
private static int GetTypeCode(Type? type)
{
if (type == null)
return 0; // TypeCode.Empty;
if (type == typeof(bool))
return 3; // TypeCode.Boolean;
if (type == typeof(char))
return 4; // TypeCode.Char;
if (type == typeof(sbyte))
return 5; // TypeCode.SByte;
if (type == typeof(byte))
return 6; // TypeCode.Byte;
if (type == typeof(short))
return 7; // TypeCode.Int16;
if (type == typeof(ushort))
return 8; // TypeCode.UInt16;
if (type == typeof(int))
return 9; // TypeCode.Int32;
if (type == typeof(uint))
return 10; // TypeCode.UInt32;
if (type == typeof(long))
return 11; // TypeCode.Int64;
if (type == typeof(ulong))
return 12; // TypeCode.UInt64;
if (type == typeof(float))
return 13; // TypeCode.Single;
if (type == typeof(double))
return 14; // TypeCode.Double;
if (type == typeof(decimal))
return 15; // TypeCode.Decimal;
if (type == typeof(DateTime))
return 16; // TypeCode.DateTime;
if (type == typeof(string))
return 18; // TypeCode.String;
if (type.GetTypeInfo().IsEnum)
return GetTypeCode(Enum.GetUnderlyingType(type));
return 1; // TypeCode.Object;
}
private static readonly OpCode[] s_convOpCodes = new OpCode[] {
OpCodes.Nop, //Empty = 0,
OpCodes.Nop, //Object = 1,
OpCodes.Nop, //DBNull = 2,
OpCodes.Conv_I1, //Boolean = 3,
OpCodes.Conv_I2, //Char = 4,
OpCodes.Conv_I1, //SByte = 5,
OpCodes.Conv_U1, //Byte = 6,
OpCodes.Conv_I2, //Int16 = 7,
OpCodes.Conv_U2, //UInt16 = 8,
OpCodes.Conv_I4, //Int32 = 9,
OpCodes.Conv_U4, //UInt32 = 10,
OpCodes.Conv_I8, //Int64 = 11,
OpCodes.Conv_U8, //UInt64 = 12,
OpCodes.Conv_R4, //Single = 13,
OpCodes.Conv_R8, //Double = 14,
OpCodes.Nop, //Decimal = 15,
OpCodes.Nop, //DateTime = 16,
OpCodes.Nop, //17
OpCodes.Nop, //String = 18,
};
private static readonly OpCode[] s_ldindOpCodes = new OpCode[] {
OpCodes.Nop, //Empty = 0,
OpCodes.Nop, //Object = 1,
OpCodes.Nop, //DBNull = 2,
OpCodes.Ldind_I1, //Boolean = 3,
OpCodes.Ldind_I2, //Char = 4,
OpCodes.Ldind_I1, //SByte = 5,
OpCodes.Ldind_U1, //Byte = 6,
OpCodes.Ldind_I2, //Int16 = 7,
OpCodes.Ldind_U2, //UInt16 = 8,
OpCodes.Ldind_I4, //Int32 = 9,
OpCodes.Ldind_U4, //UInt32 = 10,
OpCodes.Ldind_I8, //Int64 = 11,
OpCodes.Ldind_I8, //UInt64 = 12,
OpCodes.Ldind_R4, //Single = 13,
OpCodes.Ldind_R8, //Double = 14,
OpCodes.Nop, //Decimal = 15,
OpCodes.Nop, //DateTime = 16,
OpCodes.Nop, //17
OpCodes.Ldind_Ref, //String = 18,
};
private static readonly OpCode[] s_stindOpCodes = new OpCode[] {
OpCodes.Nop, //Empty = 0,
OpCodes.Nop, //Object = 1,
OpCodes.Nop, //DBNull = 2,
OpCodes.Stind_I1, //Boolean = 3,
OpCodes.Stind_I2, //Char = 4,
OpCodes.Stind_I1, //SByte = 5,
OpCodes.Stind_I1, //Byte = 6,
OpCodes.Stind_I2, //Int16 = 7,
OpCodes.Stind_I2, //UInt16 = 8,
OpCodes.Stind_I4, //Int32 = 9,
OpCodes.Stind_I4, //UInt32 = 10,
OpCodes.Stind_I8, //Int64 = 11,
OpCodes.Stind_I8, //UInt64 = 12,
OpCodes.Stind_R4, //Single = 13,
OpCodes.Stind_R8, //Double = 14,
OpCodes.Nop, //Decimal = 15,
OpCodes.Nop, //DateTime = 16,
OpCodes.Nop, //17
OpCodes.Stind_Ref, //String = 18,
};
private static void Convert(ILGenerator il, Type source, Type target, bool isAddress)
{
Debug.Assert(!target.IsByRef);
if (target == source)
return;
TypeInfo sourceTypeInfo = source.GetTypeInfo();
TypeInfo targetTypeInfo = target.GetTypeInfo();
if (source.IsByRef)
{
Debug.Assert(!isAddress);
Type argType = source.GetElementType()!;
Ldind(il, argType);
Convert(il, argType, target, isAddress);
return;
}
if (targetTypeInfo.IsValueType)
{
if (sourceTypeInfo.IsValueType)
{
OpCode opCode = s_convOpCodes[GetTypeCode(target)];
Debug.Assert(!opCode.Equals(OpCodes.Nop));
il.Emit(opCode);
}
else
{
Debug.Assert(sourceTypeInfo.IsAssignableFrom(targetTypeInfo));
il.Emit(OpCodes.Unbox, target);
if (!isAddress)
Ldind(il, target);
}
}
else if (targetTypeInfo.IsAssignableFrom(sourceTypeInfo))
{
if (sourceTypeInfo.IsValueType || source.IsGenericParameter)
{
if (isAddress)
Ldind(il, source);
il.Emit(OpCodes.Box, source);
}
}
else
{
Debug.Assert(sourceTypeInfo.IsAssignableFrom(targetTypeInfo) || targetTypeInfo.IsInterface || sourceTypeInfo.IsInterface);
if (target.IsGenericParameter)
{
il.Emit(OpCodes.Unbox_Any, target);
}
else
{
il.Emit(OpCodes.Castclass, target);
}
}
}
private static void Ldind(ILGenerator il, Type type)
{
OpCode opCode = s_ldindOpCodes[GetTypeCode(type)];
if (!opCode.Equals(OpCodes.Nop))
{
il.Emit(opCode);
}
else
{
il.Emit(OpCodes.Ldobj, type);
}
}
private static void Stind(ILGenerator il, Type type)
{
OpCode opCode = s_stindOpCodes[GetTypeCode(type)];
if (!opCode.Equals(OpCodes.Nop))
{
il.Emit(opCode);
}
else
{
il.Emit(OpCodes.Stobj, type);
}
}
private class ParametersArray
{
private readonly ILGenerator _il;
private readonly Type[] _paramTypes;
internal ParametersArray(ILGenerator il, Type[] paramTypes)
{
_il = il;
_paramTypes = paramTypes;
}
internal void Get(int i)
{
_il.Emit(OpCodes.Ldarg, i + 1);
}
internal void BeginSet(int i)
{
_il.Emit(OpCodes.Ldarg, i + 1);
}
internal void EndSet(int i, Type stackType)
{
Debug.Assert(_paramTypes[i].IsByRef);
Type argType = _paramTypes[i].GetElementType()!;
Convert(_il, stackType, argType, false);
Stind(_il, argType);
}
}
private class GenericArray<T>
{
private readonly ILGenerator _il;
private readonly LocalBuilder _lb;
internal GenericArray(ILGenerator il, int len)
{
_il = il;
_lb = il.DeclareLocal(typeof(T[]));
il.Emit(OpCodes.Ldc_I4, len);
il.Emit(OpCodes.Newarr, typeof(T));
il.Emit(OpCodes.Stloc, _lb);
}
internal void Load()
{
_il.Emit(OpCodes.Ldloc, _lb);
}
internal void Get(int i)
{
_il.Emit(OpCodes.Ldloc, _lb);
_il.Emit(OpCodes.Ldc_I4, i);
_il.Emit(OpCodes.Ldelem_Ref);
}
internal void BeginSet(int i)
{
_il.Emit(OpCodes.Ldloc, _lb);
_il.Emit(OpCodes.Ldc_I4, i);
}
internal void EndSet(Type stackType)
{
Convert(_il, stackType, typeof(T), false);
_il.Emit(OpCodes.Stelem_Ref);
}
}
private sealed class PropertyAccessorInfo
{
public MethodInfo? InterfaceGetMethod { get; }
public MethodInfo? InterfaceSetMethod { get; }
public MethodBuilder? GetMethodBuilder { get; set; }
public MethodBuilder? SetMethodBuilder { get; set; }
public PropertyAccessorInfo(MethodInfo? interfaceGetMethod, MethodInfo? interfaceSetMethod)
{
InterfaceGetMethod = interfaceGetMethod;
InterfaceSetMethod = interfaceSetMethod;
}
}
private sealed class EventAccessorInfo
{
public MethodInfo? InterfaceAddMethod { get; }
public MethodInfo? InterfaceRemoveMethod { get; }
public MethodInfo? InterfaceRaiseMethod { get; }
public MethodBuilder? AddMethodBuilder { get; set; }
public MethodBuilder? RemoveMethodBuilder { get; set; }
public MethodBuilder? RaiseMethodBuilder { get; set; }
public EventAccessorInfo(MethodInfo? interfaceAddMethod, MethodInfo? interfaceRemoveMethod, MethodInfo? interfaceRaiseMethod)
{
InterfaceAddMethod = interfaceAddMethod;
InterfaceRemoveMethod = interfaceRemoveMethod;
InterfaceRaiseMethod = interfaceRaiseMethod;
}
}
private sealed class MethodInfoEqualityComparer : EqualityComparer<MethodInfo>
{
public static readonly MethodInfoEqualityComparer Instance = new MethodInfoEqualityComparer();
private MethodInfoEqualityComparer() { }
public sealed override bool Equals(MethodInfo? left, MethodInfo? right)
{
if (ReferenceEquals(left, right))
return true;
if (left == null)
return right == null;
else if (right == null)
return false;
// This assembly should work in netstandard1.3,
// so we cannot use MemberInfo.MetadataToken here.
// Therefore, it compares honestly referring ECMA-335 I.8.6.1.6 Signature Matching.
if (!Equals(left.DeclaringType, right.DeclaringType))
return false;
if (!Equals(left.ReturnType, right.ReturnType))
return false;
if (left.CallingConvention != right.CallingConvention)
return false;
if (left.IsStatic != right.IsStatic)
return false;
if (left.Name != right.Name)
return false;
Type[] leftGenericParameters = left.GetGenericArguments();
Type[] rightGenericParameters = right.GetGenericArguments();
if (leftGenericParameters.Length != rightGenericParameters.Length)
return false;
for (int i = 0; i < leftGenericParameters.Length; i++)
{
if (!Equals(leftGenericParameters[i], rightGenericParameters[i]))
return false;
}
ParameterInfo[] leftParameters = left.GetParameters();
ParameterInfo[] rightParameters = right.GetParameters();
if (leftParameters.Length != rightParameters.Length)
return false;
for (int i = 0; i < leftParameters.Length; i++)
{
if (!Equals(leftParameters[i].ParameterType, rightParameters[i].ParameterType))
return false;
}
return true;
}
public sealed override int GetHashCode(MethodInfo obj)
{
if (obj == null)
return 0;
Debug.Assert(obj.DeclaringType != null);
int hashCode = obj.DeclaringType!.GetHashCode();
hashCode ^= obj.Name.GetHashCode();
foreach (ParameterInfo parameter in obj.GetParameters())
{
hashCode ^= parameter.ParameterType.GetHashCode();
}
return hashCode;
}
}
}
}
}

View File

@ -0,0 +1,101 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Original File: https://github.com/dotnet/runtime/blob/6072e4d3a7a2a1493f514cdf4be75a3d56580e84/src/libraries/Common/src/System/Reflection/Emit/IgnoreAccessChecksToAttributeBuilder.cs
using System.Linq;
namespace System.Reflection.Emit
{
internal static class IgnoreAccessChecksToAttributeBuilder
{
/// <summary>
/// Generate the declaration for the IgnoresAccessChecksToAttribute type.
/// This attribute will be both defined and used in the dynamic assembly.
/// Each usage identifies the name of the assembly containing non-public
/// types the dynamic assembly needs to access. Normally those types
/// would be inaccessible, but this attribute allows them to be visible.
/// It works like a reverse InternalsVisibleToAttribute.
/// This method returns the ConstructorInfo of the generated attribute.
/// </summary>
public static ConstructorInfo AddToModule(ModuleBuilder mb)
{
TypeBuilder attributeTypeBuilder =
mb.DefineType("System.Runtime.CompilerServices.IgnoresAccessChecksToAttribute",
TypeAttributes.Public | TypeAttributes.Class,
typeof(Attribute));
// Create backing field as:
// private string assemblyName;
FieldBuilder assemblyNameField =
attributeTypeBuilder.DefineField("assemblyName", typeof(string), FieldAttributes.Private);
// Create ctor as:
// public IgnoresAccessChecksToAttribute(string)
ConstructorBuilder constructorBuilder = attributeTypeBuilder.DefineConstructor(MethodAttributes.Public,
CallingConventions.HasThis,
new Type[] { assemblyNameField.FieldType });
ILGenerator il = constructorBuilder.GetILGenerator();
// Create ctor body as:
// this.assemblyName = {ctor parameter 0}
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg, 1);
il.Emit(OpCodes.Stfld, assemblyNameField);
// return
il.Emit(OpCodes.Ret);
// Define property as:
// public string AssemblyName {get { return this.assemblyName; } }
_ = attributeTypeBuilder.DefineProperty(
"AssemblyName",
PropertyAttributes.None,
CallingConventions.HasThis,
returnType: typeof(string),
parameterTypes: null);
MethodBuilder getterMethodBuilder = attributeTypeBuilder.DefineMethod(
"get_AssemblyName",
MethodAttributes.Public,
CallingConventions.HasThis,
returnType: typeof(string),
parameterTypes: null);
// Generate body:
// return this.assemblyName;
il = getterMethodBuilder.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, assemblyNameField);
il.Emit(OpCodes.Ret);
// Generate the AttributeUsage attribute for this attribute type:
// [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
TypeInfo attributeUsageTypeInfo = typeof(AttributeUsageAttribute).GetTypeInfo();
// Find the ctor that takes only AttributeTargets
ConstructorInfo attributeUsageConstructorInfo =
attributeUsageTypeInfo.DeclaredConstructors
.Single(c => c.GetParameters().Length == 1 &&
c.GetParameters()[0].ParameterType == typeof(AttributeTargets));
// Find the property to set AllowMultiple
PropertyInfo allowMultipleProperty =
attributeUsageTypeInfo.DeclaredProperties
.Single(f => string.Equals(f.Name, "AllowMultiple"));
// Create a builder to construct the instance via the ctor and property
CustomAttributeBuilder customAttributeBuilder =
new CustomAttributeBuilder(attributeUsageConstructorInfo,
new object[] { AttributeTargets.Assembly },
new PropertyInfo[] { allowMultipleProperty },
new object[] { true });
// Attach this attribute instance to the newly defined attribute type
attributeTypeBuilder.SetCustomAttribute(customAttributeBuilder);
// Make the TypeInfo real so the constructor can be used.
return attributeTypeBuilder.CreateTypeInfo()!.DeclaredConstructors.Single();
}
}
}

View File

@ -0,0 +1,61 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Title>Prise.ReverseProxy</Title>
<PackageId>Prise.ReverseProxy</PackageId>
<PackageDescription>Adds support for sharing services between a Prise Host and a Prise Plugin</PackageDescription>
<Authors>Maarten Merken</Authors>
<Company>MRKN</Company>
<PackageTags>plugin;framework;prise;decoupling;assembly;dispatchproxy;proxy</PackageTags>
<TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage</TargetsForTfmSpecificBuildOutput>
<Nullable>enable</Nullable>
<LangVersion>default</LangVersion>
<PackageLicenseUrl>https://raw.githubusercontent.com/merken/Prise/master/LICENSE</PackageLicenseUrl>
<PackageProjectUrl>https://github.com/merken/Prise</PackageProjectUrl>
<RepositoryUrl>https://github.com/merken/Prise.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
</PropertyGroup>
<Target DependsOnTargets="ResolveReferences" Name="CopyProjectReferencesToPackage">
<ItemGroup>
<BuildOutputInPackage Include="@(ReferenceCopyLocalPaths-&gt;WithMetadataValue('ReferenceSourceTarget', 'ProjectReference'))" />
</ItemGroup>
</Target>
<ItemGroup>
<PackageReference Include="System.Text.Json" Version="4.6.1" />
<PackageReference Include="System.Reflection.Emit" Version="4.7.0" />
</ItemGroup>
<ItemGroup>
<Compile Include="../Prise.Proxy/runtime/DispatchProxy.cs" Link="DispatchProxy.cs" />
<Compile Include="../Prise.Proxy/runtime/DispatchProxyGenerator.cs" Link="DispatchProxyGenerator.cs" />
<Compile Include="../Prise.Proxy/runtime/IngoreAccessChecksToAttributeBuilder.cs" Link="IngoreAccessChecksToAttributeBuilder.cs" />
<Compile Include="../Prise.Proxy/PriseProxyException.cs" Link="PriseProxyException.cs" />
<Compile Include="../Prise.Proxy/Infrastructure/IParameterConverter.cs" Link="IParameterConverter.cs" />
<Compile Include="../Prise.Proxy/Infrastructure/IResultConverter.cs" Link="IResultConverter.cs" />
<Compile Include="../Prise.Proxy/PassthroughParameterConverter.cs" Link="PassthroughParameterConverter.cs" />
<Compile Include="../Prise.Proxy/PassthroughResultConverter.cs" Link="PassthroughResultConverter.cs" />
<Compile Include="../Prise.Proxy/PriseProxy.cs" Link="PriseProxy.cs" />
<Compile Include="../Prise.Proxy/Method.cs" Link="Method.cs" />
<Compile Include="../Prise.Proxy/Parameter.cs" Link="Parameter.cs" />
<Compile Include="../Prise.Proxy/MethodFindingStrategy.cs" Link="MethodFindingStrategy.cs" />
<Compile Include="../Prise.Proxy/ResultConverter.cs" Link="ResultConverter.cs" />
<Compile Include="../Prise.Proxy/TaskCompletionSource.cs" Link="TaskCompletionSource.cs" />
<Compile Include="../Prise/Infrastructure/JsonSerializerParameterConverter.cs" Link="JsonSerializerParameterConverter.cs" />
<Compile Include="../Prise/Infrastructure/JsonSerializerResultConverter.cs" Link="JsonSerializerResultConverter.cs" />
</ItemGroup>
<PropertyGroup>
<PackageIcon>icon.png</PackageIcon>
</PropertyGroup>
<ItemGroup>
<None Include="../icon.png">
<Pack>True</Pack>
<PackagePath></PackagePath>
</None>
</ItemGroup>
</Project>

View File

@ -0,0 +1,53 @@
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using Prise.Infrastructure;
namespace Prise.Proxy
{
/// <summary>
/// The ReverseProxy is a base class that will provide a proxy to a Host Service from the Plugin (in reverse).
/// </summary>
public abstract class ReverseProxy
{
protected object hostService;
protected ReverseProxy(object hostService)
{
this.hostService = hostService;
}
private MethodBase GetCallingMethod() => new StackTrace().GetFrame(2).GetMethod();
/// <summary>
/// This handles void proxy calls to the hostService
/// </summary>
/// <param name="parameters">The list of method parameters</param>
protected void InvokeOnHostService(params object[] parameters)
{
var callingMethod = GetCallingMethod();
var methodInfo = PriseProxy.FindMethodOnObject(callingMethod as MethodInfo, this);
if (methodInfo.GetParameters().Count() != parameters.Count())
throw new ReverseProxyException($"The number of parameters provided to this ReverseProxy {parameters?.Count()} do not match the actual parameter count of the hostService method ({methodInfo.GetParameters().Count()}). Did you forget to provide the correct number of parameters?");
this.Invoke(hostService, methodInfo, parameters ?? new object[] { });
}
/// <summary>
/// This handles proxy calls to the hostService
/// </summary>
/// <param name="parameters">The list of method parameters</param>
/// <typeparam name="T">Return reference type of the calling method</typeparam>
/// <returns>The response of the invocation on the host object</returns>
protected T InvokeOnHostService<T>(params object[] parameters)
{
var callingMethod = GetCallingMethod();
var methodInfo = PriseProxy.FindMethodOnObject(callingMethod as MethodInfo, this);
if (methodInfo.GetParameters().Count() != parameters.Count())
throw new ReverseProxyException($"The number of parameters provided to this ReverseProxy {parameters?.Count()} do not match the actual parameter count of the hostService method ({methodInfo.GetParameters().Count()}). Did you forget to provide the correct number of parameters?");
return (T)this.Invoke(hostService, methodInfo, parameters ?? new object[] { });
}
private object Invoke(object hostService, MethodInfo methodInfo, object[] parameters) => PriseProxy.Invoke(hostService, methodInfo, parameters ?? new object[] { }, new JsonSerializerParameterConverter(), new JsonSerializerResultConverter());
}
}

View File

@ -0,0 +1,13 @@
namespace Prise.Proxy
{
[System.Serializable]
public class ReverseProxyException : System.Exception
{
public ReverseProxyException() { }
public ReverseProxyException(string message) : base(message) { }
public ReverseProxyException(string message, System.Exception inner) : base(message, inner) { }
protected ReverseProxyException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
}
}

View File

@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Title>Prise.Testing</Title>
<PackageId>Prise.Testing</PackageId>
<PackageDescription>Testing support for your Prise Plugins!</PackageDescription>
<Authors>Maarten Merken</Authors>
<Company>MRKN</Company>
<PackageTags>plugin;framework;prise;decoupling;assembly;dispatchproxy;proxy</PackageTags>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../Prise.Plugin/Prise.Plugin.csproj" />
</ItemGroup>
<PropertyGroup>
<PackageIcon>icon.png</PackageIcon>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<ItemGroup>
<None Include="../icon.png">
<Pack>True</Pack>
<PackagePath></PackagePath>
</None>
</ItemGroup>
</Project>

35
Vendor/Prise.Testing/Testing.cs vendored Normal file
View File

@ -0,0 +1,35 @@
using System;
using System.Linq;
using System.Reflection;
namespace Prise
{
public static class Testing
{
public static T CreateTestPluginInstance<T>(params object[] pluginServices)
{
var pluginType = typeof(T);
var pluginInstance = typeof(T).Assembly.CreateInstance(typeof(T).FullName);
var services = pluginType.GetTypeInfo().DeclaredFields.Where(f => f.CustomAttributes.Any(c => c.AttributeType.Name == typeof(Prise.Plugin.PluginServiceAttribute).Name));
foreach (var service in services)
{
var serviceType = service.FieldType;
var pluginService = pluginServices.FirstOrDefault(p => serviceType.IsAssignableFrom(p.GetType()));
if (pluginService == null)
throw new ArgumentException($"A pluginService of type {serviceType.Name} is required for activating plugin {pluginType.Name}.");
pluginInstance
.GetType()
.GetTypeInfo()
.DeclaredFields
.First(f => f.Name == service.Name)
.SetValue(pluginInstance, pluginService);
var activationMethod = pluginType.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public).FirstOrDefault(m => m.CustomAttributes.Any(c => c.AttributeType.Name == typeof(Prise.Plugin.PluginActivatedAttribute).Name));
activationMethod.Invoke(pluginInstance, null);
}
return (T)pluginInstance;
}
}
}

View File

@ -0,0 +1,19 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "IntegrationTestHost",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/Prise.IntegrationTestsHost/bin/Debug/netcoreapp3.1/Prise.IntegrationTestsHost.dll",
"args": [],
"cwd": "${workspaceFolder}/Prise.IntegrationTestsHost",
"console": "externalTerminal",
"stopAtEntry": false
},
]
}

View File

@ -0,0 +1,17 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/Prise.IntegrationTestsHost/Prise.IntegrationTestsHost.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
}
]
}

View File

@ -0,0 +1,23 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v3.1",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v3.1": {
"FileBulkDateChanger/1.0.0": {
"runtime": {
"FileBulkDateChanger.dll": {}
}
}
}
},
"libraries": {
"FileBulkDateChanger/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
}
}
}

View File

@ -0,0 +1,10 @@
{
"runtimeOptions": {
"additionalProbingPaths": [
"C:\\Users\\alper\\.dotnet\\store\\|arch|\\|tfm|",
"C:\\Users\\alper\\.nuget\\packages",
"C:\\Microsoft\\Xamarin\\NuGet",
"C:\\Program Files\\dotnet\\sdk\\NuGetFallbackFolder"
]
}
}

View File

@ -0,0 +1,9 @@
{
"runtimeOptions": {
"tfm": "netcoreapp3.1",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "3.1.0"
}
}
}

View File

@ -0,0 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,18 @@
namespace DomainForPluginC
{
public interface IDiscount
{
decimal Amount { get; }
}
public class Discount : IDiscount
{
private readonly decimal amount;
public Discount(decimal amount)
{
this.amount = amount;
}
public decimal Amount => amount;
}
}

View File

@ -0,0 +1,23 @@
using System;
namespace DomainForPluginC
{
public interface IDiscountService
{
decimal ApplyDiscount(decimal result);
}
public class DiscountService : IDiscountService
{
private readonly IDiscount discount;
public DiscountService(IDiscount discount)
{
this.discount = discount;
}
public decimal ApplyDiscount(decimal result)
{
return result * this.discount.Amount;
}
}
}

View File

@ -0,0 +1,64 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Prise.IntegrationTestsContract;
using Prise.Plugin;
namespace PluginA
{
/// <summary>
/// This plugin does not require any 3rd party dependencies or dependency injection,
/// as long as a default parameterless constructor is present (implicitly or explicitly), this plugin will get loaded.
/// </summary>
[Plugin(PluginType = typeof(ICalculationPlugin))]
public class AdditionCalculationPlugin : ICalculationPlugin
{
public string Name => nameof(AdditionCalculationPlugin);
public string Description => "This plugin performs addition";
public int Calculate(int a, int b)
{
return a + b;
}
public decimal Calculate(decimal a, decimal b)
{
return a + b;
}
public decimal CalculateComplex(CalculationContext context)
{
return context.A + context.B;
}
public CalculationResult CalculateComplexResult(CalculationContext context)
{
return new CalculationResult
{
Result = context.A + context.B
};
}
public ComplexCalculationResult CalculateMutiple(ComplexCalculationContext context)
{
var results = new List<CalculationResult>();
results.AddRange(context.Calculations.Select(c => new CalculationResult { Result = c.A + c.B }));
return new ComplexCalculationResult
{
Results = results.ToArray()
};
}
public async Task<ComplexCalculationResult> CalculateMutipleAsync(ComplexCalculationContext context)
{
var results = new List<CalculationResult>();
results.AddRange(context.Calculations.Select(c => new CalculationResult { Result = c.A + c.B }));
await Task.Delay(2500);
return new ComplexCalculationResult
{
Results = results.ToArray()
};
}
}
}

View File

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Prise.Plugin\Prise.Plugin.csproj" />
<ProjectReference Include="..\..\Prise.IntegrationTestsContract\Prise.IntegrationTestsContract.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyName>PluginA</AssemblyName>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Prise.Plugin\Prise.Plugin.csproj" />
<ProjectReference Include="..\..\Prise.IntegrationTestsContract\Prise.IntegrationTestsContract.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,65 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Prise.IntegrationTestsContract;
using Prise.Plugin;
namespace PluginA
{
/// <summary>
/// By default, only the first plugin is loaded, the first in alphabethical order
/// In order to execute the ZAdditionPlusOneCalculationPlugin, you need to have an IEnumerable<ICalculationPlugin> injected
/// or inject a IPluginLoader<ICalculationPlugin> and call the .LoadAll() method.
/// </summary>
[Plugin(PluginType = typeof(ICalculationPlugin))]
public class ZAdditionPlusOneCalculationPlugin : ICalculationPlugin
{
public string Name => nameof(ZAdditionPlusOneCalculationPlugin);
public string Description => "This plugin performs addition +1";
public int Calculate(int a, int b)
{
return a + b + 1;
}
public decimal Calculate(decimal a, decimal b)
{
return a + b + 1;
}
public decimal CalculateComplex(CalculationContext context)
{
return context.A + context.B + 1;
}
public CalculationResult CalculateComplexResult(CalculationContext context)
{
return new CalculationResult
{
Result = context.A + context.B + 1
};
}
public ComplexCalculationResult CalculateMutiple(ComplexCalculationContext context)
{
var results = new List<CalculationResult>();
results.AddRange(context.Calculations.Select(c => new CalculationResult { Result = c.A + c.B + 1 }));
return new ComplexCalculationResult
{
Results = results.ToArray()
};
}
public async Task<ComplexCalculationResult> CalculateMutipleAsync(ComplexCalculationContext context)
{
var results = new List<CalculationResult>();
results.AddRange(context.Calculations.Select(c => new CalculationResult { Result = c.A + c.B + 1 }));
await Task.Delay(2500);
return new ComplexCalculationResult
{
Results = results.ToArray()
};
}
}
}

View File

@ -0,0 +1,6 @@
{
"publishDir": "../../_dist",
"configuration": "Debug",
"nuspecFile": null,
"includeProjectNameInPublishDir": true
}

View File

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Prise.Plugin\Prise.Plugin.csproj" />
<ProjectReference Include="..\..\Prise.IntegrationTestsContract\Prise.IntegrationTestsContract.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyName>PluginB</AssemblyName>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Prise.Plugin\Prise.Plugin.csproj" />
<ProjectReference Include="..\..\Prise.IntegrationTestsContract\Prise.IntegrationTestsContract.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Prise.IntegrationTestsContract;
using Prise.Plugin;
namespace PluginB
{
// This class does not implement the ICalculationPlugin interface
// Since all the methods are present, it will continue to work because the PluginAttribute is still present and the interface contract is respected
// This improves backwards compatibility.
[Plugin(PluginType = typeof(ICalculationPlugin))]
public class SubtractionCalculationPlugin
{
public string Name => nameof(SubtractionCalculationPlugin);
// Property Description will not be implemented in this plugin, all other methods can still be called
// public string Description => "This plugin performs subtraction";
// However, you could expose it this way:
// public string get_Description() => "This plugin performs subtraction";
public int Calculate(int a, int b)
{
return a - b;
}
public decimal Calculate(decimal a, decimal b)
{
return a - b;
}
public decimal CalculateComplex(CalculationContext context)
{
return context.A - context.B;
}
public CalculationResult CalculateComplexResult(CalculationContext context)
{
return new CalculationResult { Result = context.A - context.B };
}
public ComplexCalculationResult CalculateMutiple(ComplexCalculationContext context)
{
var results = new List<CalculationResult>();
results.AddRange(context.Calculations.Select(c => new CalculationResult { Result = c.A - c.B }));
return new ComplexCalculationResult
{
Results = results.ToArray()
};
}
public async Task<ComplexCalculationResult> CalculateMutipleAsync(ComplexCalculationContext context)
{
var results = new List<CalculationResult>();
results.AddRange(context.Calculations.Select(c => new CalculationResult { Result = c.A - c.B }));
await Task.Delay(2500);
return new ComplexCalculationResult
{
Results = results.ToArray()
};
}
}
}

View File

@ -0,0 +1,6 @@
{
"publishDir": "../../_dist",
"configuration": "Debug",
"nuspecFile": null,
"includeProjectNameInPublishDir": true
}

View File

@ -0,0 +1,31 @@
using DomainForPluginC;
namespace PluginC.Calculations
{
public interface ICanCalculate
{
decimal DoCalculation(decimal a, decimal b);
}
public class DivideCalculation : ICanCalculate
{
public decimal DoCalculation(decimal a, decimal b)
{
return a / b;
}
}
public class MultiplyCalculation : ICanCalculate
{
private readonly IDiscountService discountService;
public MultiplyCalculation(IDiscountService discountService)
{
this.discountService = discountService;
}
public decimal DoCalculation(decimal a, decimal b)
{
return this.discountService.ApplyDiscount((a * b));
}
}
}

View File

@ -0,0 +1,31 @@
using System;
using DomainForPluginC;
using Microsoft.Extensions.DependencyInjection;
using PluginC.Calculations;
using Prise.Plugin;
namespace PluginC
{
[PluginBootstrapper(PluginType = typeof(DivideOrMultiplyCalculationPlugin))]
public class DivideOrMultiplyCalculationBootstrapper : IPluginBootstrapper
{
public IServiceCollection Bootstrap(IServiceCollection services)
{
// Discount and DiscountService come from a third party assembly called Domain
// Add a fixed discount of 10%
services.AddSingleton<IDiscount>(new Discount(1.10m));
services.AddScoped<IDiscountService, DiscountService>();
// Randomly choose what service to use
// var random = new Random();
// if (random.Next() % 2 == 0)
// services.AddScoped<ICanCalculate, DivideCalculation>();
// else
// services.AddScoped<ICanCalculate, MultiplyCalculation>();
services.AddScoped<ICanCalculate, MultiplyCalculation>();
return services;
}
}
}

View File

@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Prise.IntegrationTestsContract;
using PluginC.Calculations;
using Prise.Plugin;
using System.Threading.Tasks;
namespace PluginC
{
// This plugin will Divide or Multiple, who knows, it's always a guess.
// The decision is made in a service, which dependend a discount from a third party dependency that no other plugin shares
[Plugin(PluginType = typeof(ICalculationPlugin))]
public class DivideOrMultiplyCalculationPlugin : ICalculationPlugin
{
public string Name => nameof(DivideOrMultiplyCalculationPlugin);
public string Description => $"This plugin performs division OR multiplication, check out {nameof(DivideOrMultiplyCalculationBootstrapper)} for more details";
private readonly ICanCalculate calculation;
internal DivideOrMultiplyCalculationPlugin(ICanCalculate calculation)
{
this.calculation = calculation;
}
public int Calculate(int a, int b)
{
return (int)this.calculation.DoCalculation(a, b);
}
public decimal Calculate(decimal a, decimal b)
{
return this.calculation.DoCalculation(a, b);
}
public decimal CalculateComplex(CalculationContext context)
{
return this.calculation.DoCalculation(context.A, context.B);
}
public CalculationResult CalculateComplexResult(CalculationContext context)
{
return new CalculationResult { Result = this.calculation.DoCalculation(context.A, context.B) };
}
public ComplexCalculationResult CalculateMutiple(ComplexCalculationContext context)
{
var results = new List<CalculationResult>();
results.AddRange(context.Calculations.Select(c => new CalculationResult { Result = this.calculation.DoCalculation(c.A, c.B) }));
return new ComplexCalculationResult
{
Results = results.ToArray()
};
}
public async Task<ComplexCalculationResult> CalculateMutipleAsync(ComplexCalculationContext context)
{
var results = new List<CalculationResult>();
results.AddRange(context.Calculations.Select(c => new CalculationResult { Result = this.calculation.DoCalculation(c.A, c.B) }));
await Task.Delay(2500);
return new ComplexCalculationResult
{
Results = results.ToArray()
};
}
[PluginFactory]
public static ICalculationPlugin ThisNameDoesNotMatterFactoryMethod(IServiceProvider serviceProvider)
{
return new DivideOrMultiplyCalculationPlugin((ICanCalculate)serviceProvider.GetService(typeof(ICanCalculate)));
}
}
}

View File

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Prise.Plugin\Prise.Plugin.csproj" />
<ProjectReference Include="..\..\Prise.IntegrationTestsContract\Prise.IntegrationTestsContract.csproj" />
<ProjectReference Include="..\DomainForPluginC\DomainForPluginC.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyName>PluginC</AssemblyName>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Prise.Plugin\Prise.Plugin.csproj" />
<ProjectReference Include="..\..\Prise.IntegrationTestsContract\Prise.IntegrationTestsContract.csproj" />
<ProjectReference Include="..\DomainForPluginC\DomainForPluginC.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,6 @@
{
"publishDir": "../../_dist",
"configuration": "Debug",
"nuspecFile": null,
"includeProjectNameInPublishDir": true
}

View File

@ -0,0 +1,12 @@
using Xunit;
namespace Prise.IntegrationTests
{
[CollectionDefinition("AppHost collection")]
public class AppHostCollection : ICollectionFixture<AppHostWebApplicationFactory>
{
// This class has no code, and is never created. Its purpose is simply
// to be the place to apply [CollectionDefinition] and all the
// ICollectionFixture<> interfaces.
}
}

View File

@ -0,0 +1,27 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.Testing;
using Prise.IntegrationTestsHost;
namespace Prise.IntegrationTests
{
public class CommandLineArgumentsLazy : ICommandLineArguments
{
public bool UseLazyService { get; set; }
public bool UseCollectibleAssemblies { get; set; }
}
public partial class AppHostWebApplicationFactory
: WebApplicationFactory<Prise.IntegrationTestsHost.Startup>
{
internal static AppHostWebApplicationFactory _instance = new AppHostWebApplicationFactory(new CommandLineArgumentsLazy(), null);
internal static AppHostWebApplicationFactory Default() => _instance;
private readonly Dictionary<string, string> settings;
private readonly ICommandLineArguments commandLineArguments;
public AppHostWebApplicationFactory(ICommandLineArguments commandLineArguments, Dictionary<string, string> settings = null)
{
this.commandLineArguments = commandLineArguments;
this.settings = settings;
}
}
}

View File

@ -0,0 +1,24 @@
using Prise.IntegrationTestsHost;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Configuration;
namespace Prise.IntegrationTests
{
public partial class AppHostWebApplicationFactory
: WebApplicationFactory<Prise.IntegrationTestsHost.Startup>
{
#if NETCORE2_1
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureAppConfiguration((c, b) => b.AddInMemoryCollection(this.settings));
builder.ConfigureServices(services =>
{
services.AddSingleton<ICommandLineArguments>((s) => this.commandLineArguments);
});
}
#endif
}
}

View File

@ -0,0 +1,27 @@
using Prise.IntegrationTestsHost;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Configuration;
namespace Prise.IntegrationTests
{
public partial class AppHostWebApplicationFactory
: WebApplicationFactory<Prise.IntegrationTestsHost.Startup>
{
#if NETCORE3_1
protected override IHostBuilder CreateHostBuilder()
{
return Host.CreateDefaultBuilder()
.ConfigureServices((hostContext, services) =>
services.AddSingleton<ICommandLineArguments>((s) => this.commandLineArguments))
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Prise.IntegrationTestsHost.Startup>();
webBuilder.ConfigureAppConfiguration(builder => builder.AddInMemoryCollection(this.settings));
});
}
#endif
}
}

View File

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Xunit;
namespace Prise.IntegrationTests
{
#if NETCORE3_1
public class BreakTheServerTests : CalculationPluginTestsBase
{
public BreakTheServerTests() : base(AppHostWebApplicationFactory.Default()) { }
[Fact]
public async Task BreakWithLoop()
{
var tasks = new List<Task<string>>();
for (var i = 0; i < 250; i++)
{
tasks.Add(GetRaw(_client, "PluginA", "/disco"));
}
var results = await Task.WhenAll(tasks.ToArray());
Assert.All<string>(results, s => Assert.Equal("AdditionCalculationPlugin,ZAdditionPlusOneCalculationPlugin", s));
}
}
#endif
}

View File

@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace Prise.IntegrationTests
{
public abstract class CalculationPluginTestsBase : PluginTestBase
{
protected CalculationPluginTestsBase(AppHostWebApplicationFactory factory) : base(factory) { }
protected async Task<T> Post<T>(HttpClient client, string pluginType, string endpoint, object content)
{
client.DefaultRequestHeaders.Add("PluginType", pluginType);
var response = await client.PostAsync(endpoint, new StringContent(JsonConvert.SerializeObject(content), Encoding.UTF8,
"application/json"));
if (!response.IsSuccessStatusCode)
throw new Exception("Result was not success!");
var responseContent = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<T>(responseContent);
}
protected async Task<string> GetRaw(HttpClient client, string pluginType, string endpoint)
{
client.DefaultRequestHeaders.Add("PluginType", pluginType);
var response = await client.GetAsync(endpoint);
if (!response.IsSuccessStatusCode)
throw new Exception("Result was not success!");
return await response.Content.ReadAsStringAsync();
}
}
}

View File

@ -0,0 +1,329 @@
using System.Net.Http;
using System.Threading.Tasks;
using Prise.IntegrationTestsHost.Models;
using Microsoft.AspNetCore.Mvc.Testing;
using Xunit;
namespace Prise.IntegrationTests
{
public class CalculationTests : CalculationPluginTestsBase
{
public CalculationTests() : base(AppHostWebApplicationFactory.Default()) { }
[Fact]
public async Task PluginA_Works()
{
// Arrange
var payload = new CalculationRequestModel
{
A = 100,
B = 150
};
//Act
var result = await Post<CalculationResponseModel>(_client, "PluginA", "/calculation", payload);
// Assert 100 + 150
Assert.Equal(250, result.Result);
}
[Fact]
public async Task PluginB_Works()
{
// Arrange
var payload = new CalculationRequestModel
{
A = 150,
B = 50
};
//Act
var result = await Post<CalculationResponseModel>(_client, "PluginB", "/calculation", payload);
// Assert 150 - 50
Assert.Equal(100, result.Result);
}
[Fact]
public async Task PluginC_Works()
{
// Arrange
var payload = new CalculationRequestModel
{
A = 50,
B = 2
};
//Act
var result = await Post<CalculationResponseModel>(_client, "PluginC", "/calculation", payload);
// Assert 50 * 2 + 10% discount
Assert.Equal(110, result.Result);
}
[Fact]
public async Task PluginA_int_Works()
{
// Arrange
var payload = new CalculationRequestModel
{
A = 100,
B = 150
};
//Act
var result = await Post<CalculationResponseModel>(_client, "PluginA", "/calculation/int", payload);
// Assert 100 + 150
Assert.Equal(250, result.Result);
}
[Fact]
public async Task PluginB_int_Works()
{
// Arrange
var payload = new CalculationRequestModel
{
A = 150,
B = 50
};
//Act
var result = await Post<CalculationResponseModel>(_client, "PluginB", "/calculation/int", payload);
// Assert 150 - 50
Assert.Equal(100, result.Result);
}
[Fact]
public async Task PluginC_int_Works()
{
// Arrange
var payload = new CalculationRequestModel
{
A = 50,
B = 2
};
//Act
var result = await Post<CalculationResponseModel>(_client, "PluginC", "/calculation/int", payload);
// Assert 50 * 2 + 10% discount
Assert.Equal(110, result.Result);
}
[Fact]
public async Task PluginA_complex_input_Works()
{
// Arrange
var payload = new CalculationRequestModel
{
A = 100,
B = 150
};
//Act
var result = await Post<CalculationResponseModel>(_client, "PluginA", "/calculation/complex-input", payload);
// Assert 100 + 150
Assert.Equal(250, result.Result);
}
[Fact]
public async Task PluginB_complex_input_Works()
{
// Arrange
var payload = new CalculationRequestModel
{
A = 150,
B = 50
};
//Act
var result = await Post<CalculationResponseModel>(_client, "PluginB", "/calculation/complex-input", payload);
// Assert 150 - 50
Assert.Equal(100, result.Result);
}
[Fact]
public async Task PluginC_complex_input_Works()
{
// Arrange
var payload = new CalculationRequestModel
{
A = 50,
B = 2
};
//Act
var result = await Post<CalculationResponseModel>(_client, "PluginC", "/calculation/complex-input", payload);
// Assert 50 * 2 + 10% discount
Assert.Equal(110, result.Result);
}
[Fact]
public async Task PluginA_complex_output_Works()
{
// Arrange
var payload = new CalculationRequestModel
{
A = 100,
B = 150
};
//Act
var result = await Post<CalculationResponseModel>(_client, "PluginA", "/calculation/complex-output", payload);
// Assert 100 + 150
Assert.Equal(250, result.Result);
}
[Fact]
public async Task PluginB_complex_output_Works()
{
// Arrange
var payload = new CalculationRequestModel
{
A = 150,
B = 50
};
//Act
var result = await Post<CalculationResponseModel>(_client, "PluginB", "/calculation/complex-output", payload);
// Assert 150 - 50
Assert.Equal(100, result.Result);
}
[Fact]
public async Task PluginC_complex_output_Works()
{
// Arrange
var payload = new CalculationRequestModel
{
A = 50,
B = 2
};
//Act
var result = await Post<CalculationResponseModel>(_client, "PluginC", "/calculation/complex-output", payload);
// Assert 50 * 2 + 10% discount
Assert.Equal(110, result.Result);
}
[Fact]
public async Task PluginA_multi_Works()
{
// Arrange
var payload = new CalculationRequestMultiModel
{
Calculations = new[]
{
new CalculationRequestModel
{
A = 100,
B = 150
},
new CalculationRequestModel
{
A = 100,
B = 150
}
}
};
//Act
var result = await Post<CalculationResponseModel>(_client, "PluginA", "/calculation/multi", payload);
// Assert 100 + 150 + 100 + 150
Assert.Equal(500, result.Result);
}
[Fact]
public async Task PluginB_multi_Works()
{
// Arrange
var payload = new CalculationRequestMultiModel
{
Calculations = new[]
{
new CalculationRequestModel
{
A = 50,
B = 5
},
new CalculationRequestModel
{
A = 40,
B = 5
}
}
};
//Act
var result = await Post<CalculationResponseModel>(_client, "PluginB", "/calculation/multi", payload);
// Assert (50 - 5) + (40 - 5)
Assert.Equal(80, result.Result);
}
[Fact]
public async Task PluginC_multi_Works()
{
// Arrange
var payload = new CalculationRequestMultiModel
{
Calculations = new[]
{
new CalculationRequestModel
{
A = 50,
B = 2
},
new CalculationRequestModel
{
A = 40,
B = 2
}
}
};
//Act
var result = await Post<CalculationResponseModel>(_client, "PluginC", "/calculation/multi", payload);
// Assert (50 * 2 + 10% discount) + (40 * 2 + 10% discount)
Assert.Equal(198, result.Result);
}
[Fact]
public async Task PluginC_multi_async_Works()
{
// Arrange
var payload = new CalculationRequestMultiModel
{
Calculations = new[]
{
new CalculationRequestModel
{
A = 50,
B = 2
},
new CalculationRequestModel
{
A = 40,
B = 2
}
}
};
//Act
var result = await Post<CalculationResponseModel>(_client, "PluginC", "/calculation/multi-async", payload);
// Assert (50 * 2 + 10% discount) + (40 * 2 + 10% discount)
Assert.Equal(198, result.Result);
}
}
}

View File

@ -0,0 +1,61 @@
using System.Threading.Tasks;
using Xunit;
namespace Prise.IntegrationTests
{
public class DiscoTests : CalculationPluginTestsBase
{
public DiscoTests() : base(AppHostWebApplicationFactory.Default()) { }
[Fact]
public async Task PluginA_Works()
{
// Arrange, Act
var result = await GetRaw(_client, "PluginA", "/disco");
// Assert
Assert.Equal("AdditionCalculationPlugin,ZAdditionPlusOneCalculationPlugin", result);
}
[Fact]
public async Task PluginA_Description_Works()
{
// Arrange, Act
var result = await GetRaw(_client, "PluginA", "/disco/description");
// Assert
Assert.Equal("This plugin performs addition,This plugin performs addition +1", result);
}
[Fact]
public async Task PluginB_Works()
{
// Arrange, Act
var result = await GetRaw(_client, "PluginB", "/disco");
// Assert
Assert.Equal("SubtractionCalculationPlugin", result);
}
[Fact]
public async Task PluginC_Works()
{
// Arrange, Act
var result = await GetRaw(_client, "PluginC", "/disco");
// Assert
Assert.Equal("DivideOrMultiplyCalculationPlugin", result);
}
[Fact]
public async Task PluginC_Description_Works()
{
// Arrange, Act
var result = await GetRaw(_client, "PluginC", "/disco/description");
// Assert
Assert.Equal("This plugin performs division OR multiplication, check out DivideOrMultiplyCalculationBootstrapper for more details", result);
}
}
}

View File

@ -0,0 +1,64 @@
using System.Net.Http;
using System.Threading.Tasks;
using Prise.IntegrationTestsHost.Models;
using Microsoft.AspNetCore.Mvc.Testing;
using Xunit;
namespace Prise.IntegrationTests
{
public class MultipleTests : CalculationPluginTestsBase
{
public MultipleTests() : base(AppHostWebApplicationFactory.Default()) { }
[Fact]
public async Task PluginA_Works()
{
// Arrange
var payload = new CalculationRequestModel
{
A = 100,
B = 150
};
//Act
var result = await Post<CalculationResponseModel>(_client, "PluginA", "/multiple", payload);
// Assert (100 + 150) + (100 + 150 + 1)
Assert.Equal(501, result.Result);
}
[Fact]
public async Task PluginB_Works()
{
// Arrange
var payload = new CalculationRequestModel
{
A = 150,
B = 50
};
//Act
var result = await Post<CalculationResponseModel>(_client, "PluginB", "/multiple", payload);
// Assert 150 - 50
Assert.Equal(100, result.Result);
}
[Fact]
public async Task PluginC_Works()
{
// Arrange
var payload = new CalculationRequestModel
{
A = 50,
B = 2
};
//Act
var result = await Post<CalculationResponseModel>(_client, "PluginC", "/multiple", payload);
// Assert 50 * 2 + 10% discount
Assert.Equal(110, result.Result);
}
}
}

View File

@ -0,0 +1,37 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Testing;
using Newtonsoft.Json;
using Prise.IntegrationTestsContract;
namespace Prise.IntegrationTests
{
public abstract class PluginTestBase
{
protected readonly HttpClient _client;
protected readonly AppHostWebApplicationFactory _factory;
protected PluginTestBase(
AppHostWebApplicationFactory factory)
{
_factory = factory;
var local = Environment.GetEnvironmentVariable("LOCAL") == "true";
if (local)
{
_client = new HttpClient();
_client.BaseAddress = new Uri("https://localhost:5001");
}
else
_client = factory.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false,
BaseAddress = new Uri("https://localhost:5001")
});
_client.Timeout = new TimeSpan(0, 5, 0);
}
}
}

View File

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<PropertyGroup>
<DefineConstants>NETCORE3_1</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.0.0" />
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
<PackageReference Include="Xunit.SkippableFact" Version="1.3.12" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Prise.IntegrationTestsHost\Prise.IntegrationTestsHost.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,10 @@
{
"profiles": {
"Prise.IntegrationTests": {
"commandName": "Project",
"environmentVariables": {
"LOCAL": "true"
}
}
}
}

View File

@ -0,0 +1,45 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Prise.IntegrationTestsHost.Models;
using Microsoft.AspNetCore.Mvc.Testing;
using Xunit;
namespace Prise.IntegrationTests
{
public class SadPathTests : CalculationPluginTestsBase
{
public SadPathTests() : base(AppHostWebApplicationFactory.Default()) { }
[Fact]
public async Task PluginZ_DoesNotExists()
{
// Arrange
var payload = new CalculationRequestModel
{
A = 100,
B = 150
};
//Act
#if NETCORE3_1
await Assert.ThrowsAsync<System.IO.DirectoryNotFoundException>(async () => await Post<CalculationResponseModel>(_client, "PluginZ", "/calculation", payload));
#endif
#if NETCORE2_1
await Assert.ThrowsAsync<System.Exception>(async () => await Post<CalculationResponseModel>(_client, "PluginZ", "/calculation", payload));
#endif
}
[Fact]
public async Task PluginB_Description_Does_Not_Work()
{
// Arrange, Act
#if NETCORE3_1
await Assert.ThrowsAsync<Prise.Proxy.PriseProxyException>(async () => await GetRaw(_client, "PluginB", "/disco/description"));
#endif
#if NETCORE2_1
await Assert.ThrowsAsync<System.Exception>(async () => await GetRaw(_client, "PluginB", "/disco/description"));
#endif
}
}
}

Some files were not shown because too many files have changed in this diff Show More