├── src
├── TestApp
│ ├── hostsettings.json
│ ├── TestApp.csproj
│ ├── appsettings.json
│ ├── README.md
│ ├── Program.cs
│ └── .editorconfig
├── AuthApp
│ ├── Internal
│ │ ├── Constants.cs
│ │ ├── SfConfig.cs
│ │ └── HttpServer.cs
│ ├── Properties
│ │ └── launchSettings.json
│ ├── Program.cs
│ ├── AuthApp.csproj
│ ├── HostBuilderOptions.cs
│ ├── ConsoleHandler.cs
│ ├── README.md
│ ├── TokenGeneratorCommand.cs
│ ├── HostBuilderExtensions.cs
│ └── .editorconfig
└── CometD.NetCore.Salesforce
│ ├── Messaging
│ ├── MessagePayload.cs
│ ├── MessageEvent.cs
│ ├── MessageEnvelope.cs
│ └── MessageData.cs
│ ├── README.md
│ ├── CometD.NetCore.Salesforce.csproj
│ ├── IStreamingClient.cs
│ ├── SalesforceConfiguration.cs
│ ├── DependencyInjection
│ └── StreamingClientServiceExtensions.cs
│ ├── ResilientStreamingClient.cs
│ ├── Resilience
│ ├── IResilientForceClient.cs
│ └── ResilientForceClient.cs
│ └── .editorconfig
├── img
├── icon.png
├── logo64x64.png
├── new-app-api-auth.jpg
├── new-app-basic-info.jpg
├── new-platform-event.jpg
└── new-platform-event-field.jpg
├── Directory.Build.targets
├── clean.sh
├── .github
└── FUNDING.yml
├── Directory.Build.props
├── .vscode
├── extensions.json
└── settings.json
├── test
└── CometD.UnitTest
│ ├── CometD.UnitTest.csproj
│ ├── UnitTest1.cs
│ └── .editorconfig
├── CHANGELOG.md
├── LICENSE
├── appveyor.yml
├── CometD.NetCore.Salesforce.sln
├── README.md
└── .gitignore
/src/TestApp/hostsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "environment": "Development"
3 | }
4 |
--------------------------------------------------------------------------------
/img/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kdcllc/CometD.NetCore.Salesforce/HEAD/img/icon.png
--------------------------------------------------------------------------------
/img/logo64x64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kdcllc/CometD.NetCore.Salesforce/HEAD/img/logo64x64.png
--------------------------------------------------------------------------------
/Directory.Build.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/img/new-app-api-auth.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kdcllc/CometD.NetCore.Salesforce/HEAD/img/new-app-api-auth.jpg
--------------------------------------------------------------------------------
/img/new-app-basic-info.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kdcllc/CometD.NetCore.Salesforce/HEAD/img/new-app-basic-info.jpg
--------------------------------------------------------------------------------
/img/new-platform-event.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kdcllc/CometD.NetCore.Salesforce/HEAD/img/new-platform-event.jpg
--------------------------------------------------------------------------------
/clean.sh:
--------------------------------------------------------------------------------
1 |
2 | #!/bin/bash
3 | find . -name 'obj' -type d -exec rm -rv {} + ; find . -name 'bin' -type d -exec rm -rv {} + ;
4 |
--------------------------------------------------------------------------------
/img/new-platform-event-field.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kdcllc/CometD.NetCore.Salesforce/HEAD/img/new-platform-event-field.jpg
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [kdcllc]
4 | custom: ["https://www.buymeacoffee.com/vyve0og"]
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/AuthApp/Internal/Constants.cs:
--------------------------------------------------------------------------------
1 | namespace AuthApp.Internal
2 | {
3 | internal static class Constants
4 | {
5 | public const string CLIToolName = "salesforce";
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "ms-vscode.csharp",
4 | "jchannon.csharpextensions",
5 | "EditorConfig.EditorConfig",
6 | "ms-vscode-remote.vscode-remote-extensionpack",
7 | "docsmsft.docs-authoring-pack",
8 | "ms-vscode.vscode-node-azure-pack",
9 | "streetsidesoftware.code-spell-checker",
10 | "formulahendry.dotnet-test-explorer",
11 | "eamodio.gitlens",
12 | "marp-team.marp-vscode",
13 | "kdcllc.vscode-k8s-pack"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/src/AuthApp/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "AuthApp": {
4 | "commandName": "Project",
5 | "commandLineArgs": "get-tokens --azure https://kdcllc.vault.azure.net/",
6 | //"commandLineArgs": "get-tokens --verbose:information --section:sfconfig"
7 |
8 | //"commandLineArgs": "get-tokens --key: --secret: --verbose:information --section:Salesforce"
9 |
10 | //"commandLineArgs": "get-tokens --key: --secret: --verbose:debug --section:Salesforce"
11 |
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.trimTrailingWhitespace": true,
3 |
4 | "files.associations": {
5 | "*.*proj": "xml",
6 | "*.props": "xml",
7 | "*.targets": "xml",
8 | "*.tasks": "xml",
9 | "Dockerfile*": "dockerfile"
10 | },
11 |
12 | "dotnet-test-explorer.enableTelemetry": false,
13 | "dotnet-test-explorer.testProjectPath": "/test/**/*.csproj",
14 | "dotnet-test-explorer.autoExpandTree": true,
15 | "dotnet-test-explorer.pathForResultFile": "TestResults",
16 | "dotnet-test-explorer.addProblems": true
17 | }
18 |
--------------------------------------------------------------------------------
/src/CometD.NetCore.Salesforce/Messaging/MessagePayload.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace CometD.NetCore.Salesforce.Messaging
4 | {
5 | ///
6 | /// The payload base class for the other salesforce events.
7 | ///
8 | public class MessagePayload
9 | {
10 | ///
11 | /// The payload creation timestamp.
12 | ///
13 | public DateTimeOffset CreatedDate { get; set; }
14 |
15 | ///
16 | /// The user id.
17 | ///
18 | public string CreatedById { get; set; } = string.Empty;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/test/CometD.UnitTest/CometD.UnitTest.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | false
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ### 2021-04-06 v3.0.3
4 |
5 | * [Add support for handling 401 Authentication Errors from Salesforce](https://github.com/kdcllc/CometD.NetCore.Salesforce/issues/23) thanks @[apaulro](https://github.com/apaulro)
6 | ### 2021-04-06 v3.0.2
7 |
8 | * Updated dependencies
9 | - NetCoreForce.Client
10 | - netstandard2.1 and net5.0 framework support client support via Microsoft.Bcl.AsyncInterfaces
11 |
12 | * [Allow configuration of SF authentication token type](https://github.com/kdcllc/CometD.NetCore.Salesforce/pull/24)
13 | * [Allow injecting a strategy for handling invalid replay Ids](https://github.com/kdcllc/CometD.NetCore.Salesforce/pull/25)
14 | * [Silently fails when replay id is too old.](https://github.com/kdcllc/CometD.NetCore.Salesforce/issues/20)
--------------------------------------------------------------------------------
/src/CometD.NetCore.Salesforce/Messaging/MessageEvent.cs:
--------------------------------------------------------------------------------
1 | namespace CometD.NetCore.Salesforce.Messaging
2 | {
3 | ///
4 | /// Child element of .
5 | ///
6 | public class MessageEvent
7 | {
8 | ///
9 | /// Contains Salesforce replay id.
10 | ///
11 | /// "replayId": 10
12 | ///
13 | ///
14 | public int ReplayId { get; set; }
15 |
16 | ///
17 | /// Contains Salesforce event Uuid.
18 | ///
19 | /// "EventUuid": "e981b488-81f3-4fcc-bd6f-f7033c9d7ac3"
20 | ///
21 | ///
22 | public string EventUuid { get; set; }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/test/CometD.UnitTest/UnitTest1.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | using Microsoft.Extensions.Configuration;
4 | using Microsoft.Extensions.Hosting;
5 |
6 | using Xunit;
7 |
8 | namespace CometD.UnitTest
9 | {
10 | public class UnitTest1
11 | {
12 | [Fact]
13 | public void Test1()
14 | {
15 | var dic = new Dictionary
16 | {
17 | { "Salesforce:Id", "-2" },
18 | { "Salesforce:Name", string.Empty }
19 | };
20 |
21 | IConfiguration configuration = null;
22 |
23 | var host = new HostBuilder()
24 | .ConfigureAppConfiguration((context, builder) =>
25 | {
26 | configuration = builder.AddInMemoryCollection(dic).Build();
27 | })
28 | .ConfigureServices((services) =>
29 | {
30 | });
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/CometD.NetCore.Salesforce/Messaging/MessageEnvelope.cs:
--------------------------------------------------------------------------------
1 | namespace CometD.NetCore.Salesforce.Messaging
2 | {
3 | ///
4 | /// Top level objects of the salesforce reply message.
5 | ///
6 | /// "data": {}
7 | /// "channel": "/event/Custom_Event__e"
8 | ///
9 | ///
10 | ///
11 | public class MessageEnvelope where TPayload : MessagePayload
12 | {
13 | ///
14 | /// where TPayload is a generic message.
15 | ///
16 | public MessageData? Data { get; set; }
17 |
18 | ///
19 | /// Channel or event information.
20 | ///
21 | /// "channel": "/event/Custom_Event__e"
22 | ///
23 | ///
24 | public string Channel { get; set; } = string.Empty;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/CometD.NetCore.Salesforce/README.md:
--------------------------------------------------------------------------------
1 | # CometD.NetCore.Salesforce
2 |
3 | [](https://ci.appveyor.com/project/kdcllc/cometd-netcore-salesforce)
4 | [](https://www.nuget.org/packages?q=Bet.AspNetCore)
5 | [](https://www.myget.org/F/kdcllc/api/v2)
6 |
7 | Add the following to the project
8 |
9 | ```csharp
10 | dotnet add package CometD.NetCore.Salesforce
11 | ```
12 |
13 | This library includes:
14 |
15 | - `ResilientStreamingClient` class to create an event bus for Salesforce
16 | - `IResilientForceClient` wrapper class with Resilience for `NetCoreForce.Client`
17 |
18 | Complete Sample App using this library can be found at [Bet.BuildingBlocks.SalesforceEventBus](https://github.com/kdcllc/Bet.BuildingBlocks.SalesforceEventBus)
19 |
--------------------------------------------------------------------------------
/src/CometD.NetCore.Salesforce/CometD.NetCore.Salesforce.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0;netstandard2.1;net6.0
5 | enable
6 | CometD.NetCore2.Salesforce
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/CometD.NetCore.Salesforce/Messaging/MessageData.cs:
--------------------------------------------------------------------------------
1 | namespace CometD.NetCore.Salesforce.Messaging
2 | {
3 | ///
4 | /// Data from the Salesforce platform event reply.
5 | ///
6 | ///
7 | public class MessageData where TPayload : MessagePayload
8 | {
9 | ///
10 | /// Unique id of the data.
11 | /// "schema": "1qUPELmVz7qUv3ntwyN1eA"
12 | ///
13 | public string Schema { get; set; } = string.Empty;
14 |
15 | ///
16 | /// Generic type of the payload.
17 | /// "payload": {}
18 | ///
19 | public TPayload? Payload { get; set; }
20 |
21 | ///
22 | /// Contains Message event.
23 | ///
24 | /// "event": {
25 | /// "replayId": 10
26 | /// }
27 | ///
28 | ///
29 | public MessageEvent? Event { get; set; }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/AuthApp/Internal/SfConfig.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 |
3 | namespace AuthApp.Internal
4 | {
5 | internal class SfConfig
6 | {
7 | ///
8 | /// Salesforce Client Id.
9 | ///
10 | [Required]
11 | public string? ClientId { get; set; }
12 |
13 | ///
14 | /// Salesforece Secret Id.
15 | ///
16 | [Required]
17 | public string? ClientSecret { get; set; }
18 |
19 | ///
20 | /// i.e. https://login.salesforce.com.
21 | ///
22 | [Required]
23 | [Url]
24 | public string? LoginUrl { get; set; }
25 |
26 | ///
27 | /// Default set to /services/oauth2/token.
28 | ///
29 | public string OAuthUri { get; set; } = "/services/oauth2/token";
30 |
31 | ///
32 | /// Authorize Uri for Salesforce end point.
33 | ///
34 | public string OAuthorizeUri { get; set; } = "/services/oauth2/authorize";
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017-2021 King David Consulting LLC
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/AuthApp/Program.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Threading.Tasks;
3 |
4 | using AuthApp.Internal;
5 |
6 | using McMaster.Extensions.CommandLineUtils;
7 |
8 | using Console = Colorful.Console;
9 |
10 | namespace AuthApp
11 | {
12 | [Command(Name = Constants.CLIToolName, Description = "cli tool to help with Salesforce development by providing with Refresh token generation.")]
13 | [Subcommand(typeof(TokenGeneratorCommand))]
14 | [HelpOption("-?")]
15 | [VersionOptionFromMember("--version", MemberName = nameof(GetVersion))]
16 | public class Program
17 | {
18 | private static Task Main(string[] args)
19 | {
20 | return CommandLineApplication.ExecuteAsync(args);
21 | }
22 |
23 | private static string GetVersion()
24 | {
25 | return typeof(Program).Assembly.GetCustomAttribute().InformationalVersion;
26 | }
27 |
28 | private int OnExecute(CommandLineApplication app, IConsole console)
29 | {
30 | Console.WriteAscii(Constants.CLIToolName, Colorful.FigletFont.Default);
31 |
32 | console.WriteLine();
33 | console.WriteLine("You must specify at a subcommand.");
34 | app.ShowHelp();
35 | return 1;
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: 3.0.{build}
2 | branches:
3 | only:
4 | - master
5 | pull_requests:
6 | do_not_increment_build_number: true
7 | image: Visual Studio 2022
8 | ## temporary until 6.0.300 sdk is installed
9 | install:
10 | - ps: $urlCurrent = "https://dotnetcli.blob.core.windows.net/dotnet/Sdk/6.0.300/dotnet-sdk-6.0.300-win-x64.zip"
11 | - ps: $env:DOTNET_INSTALL_DIR = "$pwd\.dotnetsdk"
12 | - ps: mkdir $env:DOTNET_INSTALL_DIR -Force | Out-Null
13 | - ps: $tempFileCurrent = [System.IO.Path]::GetTempFileName()
14 | - ps: (New-Object System.Net.WebClient).DownloadFile($urlCurrent, $tempFileCurrent)
15 | - ps: Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory($tempFileCurrent, $env:DOTNET_INSTALL_DIR)
16 | - ps: $env:Path = "$env:DOTNET_INSTALL_DIR;$env:Path"
17 | nuget:
18 | disable_publish_on_pr: true
19 |
20 | build_script:
21 | - ps: dotnet restore CometD.NetCore.Salesforce.sln -v quiet
22 | - ps: dotnet build CometD.NetCore.Salesforce.sln /p:configuration=Release /p:Version=$($env:appveyor_build_version)
23 |
24 | #test: off
25 | test_script:
26 | - dotnet test test/CometD.UnitTest/CometD.UnitTest.csproj
27 |
28 | artifacts:
29 | - path: .\src\**\*.nupkg
30 | name: NuGet package
31 |
32 | deploy:
33 | - provider: NuGet
34 | artifact: /NuGet/
35 | api_key:
36 | secure: a8sCawSwgb2kYDJAN+xTUvy+MH5jdJR+DmKakUmc/Xom1c+uxyvV+yvpSTJs+ypF
37 | on:
38 | branch: master
39 |
--------------------------------------------------------------------------------
/src/TestApp/TestApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0
6 | false
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | appsettings.json
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/TestApp/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "AzureVault": {
3 | "BaseUrl": "https://kdcllc.vault.azure.net/"
4 | },
5 |
6 | "Logging": {
7 | "IncludeScopes": true,
8 | "Debug": {
9 | "LogLevel": {
10 | "Default": "Debug",
11 | "System": "Warning",
12 | "Microsoft": "Warning",
13 | "SetMinimumLevel": "Debug"
14 | }
15 | },
16 | "Console": {
17 | "LogLevel": {
18 | "Default": "Debug",
19 | "System": "Warning",
20 | "Microsoft": "Warning",
21 | "SetMinimumLevel": "Debug"
22 | }
23 | }
24 | },
25 |
26 | "AllowedHosts": "*",
27 |
28 | "ResilientForceClient": {
29 |
30 | },
31 |
32 | "Salesforce": {
33 | "ClientId": "",
34 | "ClientSecret": "",
35 |
36 | "//OAuth flow with refresh token": "",
37 | "RefreshToken": "",
38 | "AccessToken": "",
39 |
40 | "//Password grant type instead of using persistent refresh token": "",
41 | "Username": "",
42 | "Password": "",
43 | "UserPasswordToken": "",
44 |
45 | "RedirectUri": "http://localhost:5050/",
46 | "OrganizationUrl": "",
47 | "LoginUrl": "https://login.salesforce.com",
48 | "OAuthUri": "/services/oauth2/token",
49 | "PublishEndpoint": "/services/data/v42.0/sobjects/",
50 | "EventOrTopicUri": "/event",
51 | "CometDUri": "/cometd/42.0",
52 | "Retry": 3,
53 | "BackoffPower": 2,
54 | "CustomEvent": "Custom_Event__e",
55 | "ReplayId": "-2"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/AuthApp/AuthApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0
6 | enable
7 | salesforce
8 | salesforce
9 | True
10 |
11 |
12 |
13 | A command-line tool that enables Salesforce Access and Refresh Tokens Generation.
14 | Salesforce refresh tokens, Salesforce access tokens, DotNetCore, AspNetCore, salesforce, OAuth Authentication Flow
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/AuthApp/HostBuilderOptions.cs:
--------------------------------------------------------------------------------
1 | using AuthApp.Internal;
2 |
3 | using Microsoft.Extensions.Logging;
4 |
5 | namespace AuthApp
6 | {
7 | internal class HostBuilderOptions
8 | {
9 | ///
10 | /// Sets Configuration file besides appsettings.json.
11 | ///
12 | public string? ConfigFile { get; set; }
13 |
14 | ///
15 | /// Provides ability to get troubleshooting information.
16 | ///
17 | public bool Verbose { get; set; }
18 |
19 | ///
20 | /// TraceLevel if verbose is present.
21 | ///
22 | public LogLevel Level { get; set; }
23 |
24 | ///
25 | /// Ability to use Web project secrets.
26 | ///
27 | public bool UserSecrets { get; set; }
28 |
29 | ///
30 | /// Url for the azure key vault i.e. https://{vaultname}.vault.azure.net/.
31 | ///
32 | public string? AzureVault { get; set; }
33 |
34 | ///
35 | /// Prefix is based on Environment i.e. Development = dev, Production = prod.
36 | ///
37 | public bool UseAzureKeyPrefix { get; set; }
38 |
39 | ///
40 | /// Pass Hosting environment for the context of the application.
41 | ///
42 | public string HostingEnviroment { get; set; } = "Production";
43 |
44 | ///
45 | /// Salesforce options.
46 | ///
47 | public SfConfig? Settings { get; set; }
48 |
49 | ///
50 | /// The name of the configuration section for the options.
51 | ///
52 | public string SectionName { get; set; } = "Salesforce";
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/AuthApp/ConsoleHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.IO;
4 | using System.Runtime.InteropServices;
5 | using System.Xml.Linq;
6 |
7 | namespace AuthApp
8 | {
9 | internal static class ConsoleHandler
10 | {
11 | internal static Process OpenBrowser(string url)
12 | {
13 | try
14 | {
15 | return Process.Start(url);
16 | }
17 | catch
18 | {
19 | // hack because of this: https://github.com/dotnet/corefx/issues/10361
20 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
21 | {
22 | url = url.Replace("&", "^&");
23 | return Process.Start(new ProcessStartInfo("cmd", $"/c start {url}") { CreateNoWindow = true });
24 | }
25 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
26 | {
27 | return Process.Start("xdg-open", url);
28 | }
29 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
30 | {
31 | return Process.Start("open", url);
32 | }
33 | else
34 | {
35 | throw;
36 | }
37 | }
38 | }
39 |
40 | internal static string GetUserSecretsId()
41 | {
42 | var files = Directory.GetFiles("./", "*.csproj");
43 | if (files.Length == 0)
44 | {
45 | throw new Exception("This command must be run in a project directory");
46 | }
47 |
48 | var file = XElement.Load(files[0]);
49 | var groups = file.Elements(XName.Get("PropertyGroup"));
50 | foreach (var group in groups)
51 | {
52 | var secret = group.Element("UserSecretsId");
53 | if (secret != null)
54 | {
55 | return secret.Value;
56 | }
57 | }
58 |
59 | throw new Exception("The UserSecretsId element was not found in your csproj. Please ensure it has been configured.");
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/CometD.NetCore.Salesforce/IStreamingClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | using CometD.NetCore.Bayeux.Client;
4 |
5 | namespace CometD.NetCore.Salesforce
6 | {
7 | ///
8 | /// The a wrapper class around client.
9 | ///
10 | public interface IStreamingClient : IDisposable
11 | {
12 | ///
13 | /// Event handler that sends event if the client must reconnect.
14 | ///
15 | event EventHandler Reconnect;
16 |
17 | ///
18 | /// Inject a strategy for handling invalid replay ids.
19 | ///
20 | Action InvalidReplayIdStrategy { get; set; }
21 |
22 | ///
23 | /// True/False if the is connected.
24 | ///
25 | bool IsConnected { get; }
26 |
27 | ///
28 | /// Connects to the Bayeux server.
29 | ///
30 | void Handshake();
31 |
32 | ///
33 | /// Connects to the Bayeux server.
34 | ///
35 | ///
36 | void Handshake(int timeout);
37 |
38 | ///
39 | /// Disconnect Salesforce subscription to the platform events.
40 | ///
41 | void Disconnect();
42 |
43 | ///
44 | /// Disconnect Salesforce subscription to the platform events.
45 | ///
46 | ///
47 | void Disconnect(int timeout);
48 |
49 | ///
50 | /// Subscribe to Salesforce Platform event.
51 | ///
52 | ///
53 | ///
54 | ///
55 | void SubscribeTopic(string topicName, IMessageListener listener, long replayId = -1);
56 |
57 | ///
58 | /// Unsubscribe from Salesforce Platform event.
59 | ///
60 | ///
61 | ///
62 | ///
63 | ///
64 | bool UnsubscribeTopic(string topicName, IMessageListener? listener = null, long replayId = -1);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/TestApp/README.md:
--------------------------------------------------------------------------------
1 | # TestApp for CometD.NetCore.Salesforce
2 |
3 | [](https://www.buymeacoffee.com/vyve0og)
4 |
5 | ## Give a Star! :star:
6 |
7 | If you like or are using this project to learn or start your solution, please give it a star. Thanks!
8 |
9 | ```csharp
10 | override void ErrorExtension_ConnectionError(
11 | object sender,
12 | string e)
13 | {
14 | // authentication failure
15 | if (string.Equals(e, "403::Handshake denied", StringComparison.OrdinalIgnoreCase)
16 | || string.Equals(e, "403:denied_by_security_policy:create_denied", StringComparison.OrdinalIgnoreCase)
17 | || string.Equals(e, "403::unknown client", StringComparison.OrdinalIgnoreCase))
18 | {
19 | _logger.LogWarning("Handled CometD Exception: {message}", e);
20 |
21 | // 1. Disconnect existing client.
22 | Disconnect();
23 |
24 | // 2. Invalidate the access token.
25 | _tokenResponse.Invalidate();
26 |
27 | _logger.LogDebug("Invalidate token for {name} ...", nameof(BayeuxClient));
28 |
29 | // 3. Recreate BayeuxClient and populate it with a new transport with new security headers.
30 | CreateBayeuxClient();
31 |
32 | // 4. Invoke the Reconnect Event
33 | Reconnect?.Invoke(this, true);
34 | }
35 | else if (e.StartsWith("400::The replayId ", StringComparison.OrdinalIgnoreCase)
36 | && e.Contains("you provided was invalid. Please provide a valid ID, -2 to replay all events, or -1 to replay only new events", StringComparison.OrdinalIgnoreCase))
37 | {
38 | _logger.LogWarning("Handled CometD Exception: {message}", e);
39 |
40 | /*
41 | logic to persist replayId which is being reset to -1 for this particular channel.
42 | that value is read when the channel is initialized.
43 | */
44 |
45 | _logger.LogDebug($"Reset {channelName} with ReplayId -1");
46 | _logger.LogDebug($"Initiate reconnect process..");
47 |
48 | // 1. Disconnect existing client.
49 | Disconnect();
50 |
51 | // 2. Invalidate the access token.
52 | _tokenResponse.Invalidate();
53 |
54 | _logger.LogDebug("Invalidate token for {name} ...", nameof(BayeuxClient));
55 |
56 | // 3. Recreate BayeuxClient and populate it with a new transport with new security headers.
57 | CreateBayeuxClient();
58 |
59 | // 4. Invoke the Reconnect Event
60 | Reconnect?.Invoke(this, true);
61 | }
62 | ```
63 |
--------------------------------------------------------------------------------
/src/TestApp/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Threading.Tasks;
4 |
5 | using CometD.NetCore.Salesforce.Resilience;
6 |
7 | using Microsoft.Extensions.Configuration;
8 | using Microsoft.Extensions.DependencyInjection;
9 | using Microsoft.Extensions.Hosting;
10 | using Microsoft.Extensions.Logging;
11 |
12 | namespace TestApp
13 | {
14 | #pragma warning disable RCS1102 // Make class static.
15 | internal sealed class Program
16 | #pragma warning restore RCS1102 // Make class static.
17 | {
18 | public static async Task Main(string[] args)
19 | {
20 | var host = new HostBuilder()
21 | .ConfigureHostConfiguration(configHost =>
22 | {
23 | configHost.SetBasePath(Directory.GetCurrentDirectory());
24 | configHost.AddJsonFile("hostsettings.json", optional: true);
25 | configHost.AddCommandLine(args);
26 | })
27 | .ConfigureAppConfiguration((hostContext, configBuilder) =>
28 | {
29 | configBuilder.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
30 | configBuilder.AddJsonFile(
31 | $"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json",
32 | optional: true);
33 |
34 | configBuilder.AddAzureKeyVault(hostingEnviromentName: hostContext.HostingEnvironment.EnvironmentName);
35 | configBuilder.AddCommandLine(args);
36 |
37 | // print out the environment
38 | var config = configBuilder.Build();
39 | config.DebugConfigurations();
40 | })
41 | .ConfigureServices((context, services) =>
42 | {
43 | services.AddTransient, DefaultServiceProviderFactory>();
44 |
45 | services.AddResilientStreamingClient();
46 | })
47 | .ConfigureLogging((hostContext, configLogging) =>
48 | {
49 | configLogging.AddConfiguration(hostContext.Configuration.GetSection("Logging"));
50 | configLogging.AddConsole();
51 | configLogging.AddDebug();
52 | })
53 | .UseConsoleLifetime()
54 | .Build();
55 |
56 | var srv = host.Services;
57 |
58 | await host.StartAsync();
59 |
60 | var test = srv.GetRequiredService();
61 |
62 | var result = await test.CountQueryAsync("SELECT COUNT() FROM ACCOUNT");
63 |
64 | Console.WriteLine(result);
65 |
66 | await Task.Delay(TimeSpan.FromSeconds(15));
67 |
68 | var result1 = await test.CountQueryAsync("SELECT COUNT() FROM ACCOUNT");
69 | Console.WriteLine(result1);
70 |
71 | await host.StopAsync();
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/CometD.NetCore.Salesforce/SalesforceConfiguration.cs:
--------------------------------------------------------------------------------
1 | using NetCoreForce.Client;
2 |
3 | namespace CometD.NetCore.Salesforce
4 | {
5 | ///
6 | /// Represents the configuration settings in appsettings.json.
7 | ///
8 | public sealed class SalesforceConfiguration
9 | {
10 | ///
11 | /// Consumer Id of the Salesforce Connected App.
12 | ///
13 | public string ClientId { get; set; } = string.Empty;
14 |
15 | ///
16 | /// Consumer secret of the Salesforce Connected App.
17 | ///
18 | public string ClientSecret { get; set; } = string.Empty;
19 |
20 | ///
21 | /// Username to login to Salesforce
22 | ///
23 | public string Username { get; set; } = string.Empty;
24 |
25 | ///
26 | /// Password to login to Salesforce
27 | ///
28 | public string Password { get; set; } = string.Empty;
29 |
30 | ///
31 | /// API Token for user identification to Salesforce API
32 | ///
33 | public string UserPasswordToken { get; set; } = string.Empty;
34 |
35 | ///
36 | /// Url to login to Salesforce
37 | /// https://test.salesforce.com/services/oauth2/authorize
38 | /// or https://login.salesforce.com/services/oauth2/authorize.
39 | ///
40 | public string LoginUrl { get; set; } = string.Empty;
41 |
42 | ///
43 | /// Url of the Salesforce organization.
44 | ///
45 | public string OrganizationUrl { get; set; } = string.Empty;
46 |
47 | ///
48 | /// Path of the endpoint to publish platform events.
49 | ///
50 | public string PublishEndpoint { get; set; } = string.Empty;
51 |
52 | ///
53 | /// OAuth refresh token of the Salesforce Connected App.
54 | ///
55 | public string RefreshToken { get; set; } = string.Empty;
56 |
57 | ///
58 | /// Uri to connect to CometD.
59 | ///
60 | public string CometDUri { get; set; } = string.Empty;
61 |
62 | ///
63 | /// Topic or Event Uri.
64 | ///
65 | public string EventOrTopicUri { get; set; } = string.Empty;
66 |
67 | ///
68 | /// Salesforce uri for OAuth authentication.
69 | ///
70 | public string OAuthUri { get; set; } = string.Empty;
71 |
72 | ///
73 | /// Retry for the connections and authentications.
74 | ///
75 | public int Retry { get; set; }
76 |
77 | ///
78 | /// The number to be used for BackoffPower for policy. The default is 2.
79 | ///
80 | public int BackoffPower { get; set; } = 2;
81 |
82 | ///
83 | /// The expiration for token. The default is 1:00 hour.
84 | ///
85 | public string TokenExpiration { get; set; } = "01:00:00";
86 |
87 | ///
88 | /// The event that gets raised when a request for proposal is approved and the Deal is in working status.
89 | ///
90 | public string CustomEvent { get; set; } = string.Empty;
91 |
92 | ///
93 | /// Salesforce ReplayId for specific message.
94 | ///
95 | public int ReplayId { get; set; }
96 |
97 | ///
98 | /// Long polling duration. Default 120 * 1000.
99 | ///
100 | public long? ReadTimeOut { get; set; }
101 |
102 | ///
103 | /// The type of the Salesforce authentication token. Default "OAuth".
104 | ///
105 | public string TokenType { get; set; } = "OAuth";
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/CometD.NetCore.Salesforce.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.28606.126
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8031B188-9FF6-4932-90AC-98DD21AF4D13}"
7 | ProjectSection(SolutionItems) = preProject
8 | .gitignore = .gitignore
9 | appveyor.yml = appveyor.yml
10 | clean.sh = clean.sh
11 | Directory.Build.props = Directory.Build.props
12 | Directory.Build.targets = Directory.Build.targets
13 | LICENSE = LICENSE
14 | README.md = README.md
15 | EndProjectSection
16 | EndProject
17 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AuthApp", "src\AuthApp\AuthApp.csproj", "{0AC0A8E4-AF3E-4078-A472-0D2DC1392C00}"
18 | EndProject
19 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{DF67E08B-61B2-46CD-B3B0-56F6BDC4313E}"
20 | ProjectSection(SolutionItems) = preProject
21 | build\dependecies.props = build\dependecies.props
22 | build\settings.props = build\settings.props
23 | build\sources.props = build\sources.props
24 | EndProjectSection
25 | EndProject
26 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F0246231-B425-4C7A-BD19-EF4177AF48C0}"
27 | EndProject
28 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CometD.NetCore.Salesforce", "src\CometD.NetCore.Salesforce\CometD.NetCore.Salesforce.csproj", "{03FD9019-E9BC-4ADE-9BFF-F6FD459CD579}"
29 | EndProject
30 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CometD.UnitTest", "test\CometD.UnitTest\CometD.UnitTest.csproj", "{62A83077-E672-4D88-81AC-08667FE898E3}"
31 | EndProject
32 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{1F013CA3-FD86-46BC-A162-B50DF96DE262}"
33 | EndProject
34 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestApp", "src\TestApp\TestApp.csproj", "{5C96EB67-EC27-4C3B-ABFB-94B8B559CD6B}"
35 | EndProject
36 | Global
37 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
38 | Debug|Any CPU = Debug|Any CPU
39 | Release|Any CPU = Release|Any CPU
40 | EndGlobalSection
41 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
42 | {0AC0A8E4-AF3E-4078-A472-0D2DC1392C00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
43 | {0AC0A8E4-AF3E-4078-A472-0D2DC1392C00}.Debug|Any CPU.Build.0 = Debug|Any CPU
44 | {0AC0A8E4-AF3E-4078-A472-0D2DC1392C00}.Release|Any CPU.ActiveCfg = Release|Any CPU
45 | {0AC0A8E4-AF3E-4078-A472-0D2DC1392C00}.Release|Any CPU.Build.0 = Release|Any CPU
46 | {03FD9019-E9BC-4ADE-9BFF-F6FD459CD579}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
47 | {03FD9019-E9BC-4ADE-9BFF-F6FD459CD579}.Debug|Any CPU.Build.0 = Debug|Any CPU
48 | {03FD9019-E9BC-4ADE-9BFF-F6FD459CD579}.Release|Any CPU.ActiveCfg = Release|Any CPU
49 | {03FD9019-E9BC-4ADE-9BFF-F6FD459CD579}.Release|Any CPU.Build.0 = Release|Any CPU
50 | {62A83077-E672-4D88-81AC-08667FE898E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
51 | {62A83077-E672-4D88-81AC-08667FE898E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
52 | {62A83077-E672-4D88-81AC-08667FE898E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
53 | {62A83077-E672-4D88-81AC-08667FE898E3}.Release|Any CPU.Build.0 = Release|Any CPU
54 | {5C96EB67-EC27-4C3B-ABFB-94B8B559CD6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
55 | {5C96EB67-EC27-4C3B-ABFB-94B8B559CD6B}.Debug|Any CPU.Build.0 = Debug|Any CPU
56 | {5C96EB67-EC27-4C3B-ABFB-94B8B559CD6B}.Release|Any CPU.ActiveCfg = Release|Any CPU
57 | {5C96EB67-EC27-4C3B-ABFB-94B8B559CD6B}.Release|Any CPU.Build.0 = Release|Any CPU
58 | EndGlobalSection
59 | GlobalSection(SolutionProperties) = preSolution
60 | HideSolutionNode = FALSE
61 | EndGlobalSection
62 | GlobalSection(NestedProjects) = preSolution
63 | {0AC0A8E4-AF3E-4078-A472-0D2DC1392C00} = {F0246231-B425-4C7A-BD19-EF4177AF48C0}
64 | {03FD9019-E9BC-4ADE-9BFF-F6FD459CD579} = {F0246231-B425-4C7A-BD19-EF4177AF48C0}
65 | {62A83077-E672-4D88-81AC-08667FE898E3} = {1F013CA3-FD86-46BC-A162-B50DF96DE262}
66 | {5C96EB67-EC27-4C3B-ABFB-94B8B559CD6B} = {F0246231-B425-4C7A-BD19-EF4177AF48C0}
67 | EndGlobalSection
68 | GlobalSection(ExtensibilityGlobals) = postSolution
69 | SolutionGuid = {800A565D-7D93-4523-AD7A-EFA4067997DC}
70 | EndGlobalSection
71 | EndGlobal
72 |
--------------------------------------------------------------------------------
/src/AuthApp/README.md:
--------------------------------------------------------------------------------
1 | # `salesforce` CLI authentication tool
2 |
3 | [](https://raw.githubusercontent.com/kdcllc/cometd-netcore-salesforce/master/LICENSE)
4 | [](https://ci.appveyor.com/project/kdcllc/cometd-netcore-salesforce)
5 | [](https://www.nuget.org/packages?q=Bet.AspNetCore)
6 | [](https://f.feedz.io/kdcllc/kdcllc/packages/salesforce/latest/download)
7 |
8 | _Note: Pre-release packages are distributed via [feedz.io](https://f.feedz.io/kdcllc/kcllc/nuget/index.json)._
9 |
10 | ## Summary
11 |
12 | This is a dotnet cli Saleforce Refresh and Access Tokens Generation tool.
13 |
14 | [](https://www.buymeacoffee.com/vyve0og)
15 |
16 | ## Give a Star! :star:
17 |
18 | If you like or are using this project to learn or start your solution, please give it a star. Thanks!
19 |
20 | ## Install DotNetCore Cli `salesforce` tool
21 |
22 | ```bash
23 | dotnet tool install salesforce -g
24 | ```
25 |
26 | To verify the installation run:
27 |
28 | ```bash
29 | dotnet tool list -g
30 | ```
31 |
32 | ## Usage of Salesforce dotnet cli tool
33 |
34 | There are several ways to run this cli tool.
35 |
36 | This tool will open web browser and will require you to log in with your credentials to Salesforce portal in order to retrieve the tokens.
37 |
38 | 1. From any location with Consumer Key and Secret provided
39 |
40 | ```bash
41 | # specify the custom login url
42 | salesforce get-tokens --key:{key} --secret:{secret} --login:https://login.salesforce.com --verbose:information
43 |
44 | # use default login url
45 | salesforce get-tokens --key:{key} --secret:{secret} --verbose
46 | ```
47 |
48 | 2. Running the tool in the directory that contains `appsettings.json` file with configurations
49 |
50 | ```bash
51 | salesforce get-tokens --section:Salesforce
52 | ```
53 |
54 | Note: required configurations are as follows:
55 |
56 | ```json
57 | "Salesforce": {
58 | "ClientId": "",
59 | "ClientSecret": "",
60 | "LoginUrl": ""
61 | }
62 | ````
63 |
64 | 3. Running with Azure Vault
65 |
66 | a.) Location with `appsettings.json` file
67 |
68 | ```json
69 | "AzureVault": {
70 | "BaseUrl": "https://{name}.vault.azure.net/"
71 | },
72 | ```
73 |
74 | ```bash
75 | salesforce get-tokens --verbose:debug
76 | ```
77 | b.) From any location
78 |
79 | Or specify url within the dotnet cli tool like so:
80 |
81 | ```cmd
82 | salesforce get-tokens --azure https://{name}.vault.azure.net/"
83 | ```
84 |
85 | ## Tools possible switches
86 |
87 | - `--key` or `-k` (Salesforce `Consumer Key`)
88 | - `--secret` or `-s` (Salesforce `Consumer Secret`)
89 | - `--login` or `-l` (Salesforce login url)
90 | - `--azure` or `-a` (Azure Vault Url)
91 | - `--azureprefix` or `ax` ([Use Environment prefix for Azure vault](https://github.com/kdcllc/Bet.AspNetCore/blob/d8ff3b7bfb13817bc2b6b768d91ea19a2bc865a5/src/Bet.Extensions.AzureVault/AzureVaultKeyBuilder.cs#L24))
92 | - `--configfile` or `-c` (Specify configuration file)
93 | - `--verbose:debug` or `--verbose:information` or `--verbose:trave`
94 | - `--usesecrets` or `us` (Usually a Guid Id of the project that contains the secret)
95 | - `--environment` or `-e` (Production, Development, Stage)
96 | - `--section` or `-sn` (The root for the tools configuration the default is `Salesforce`)
97 |
98 | ## Build self contained
99 |
100 | [rid-catalog](https://docs.microsoft.com/en-us/dotnet/core/rid-catalog)
101 |
102 | ```bash
103 | # windows
104 | dotnet build -r win-x64 -c Release -p:PackAsTool=false
105 | dotnet publish -r win-x64 -c Release -p:PackAsTool=false -p:PublishSingleFile=true -p:PublishTrimmed=true -p:PublishReadyToRun=true -f netcoreapp3.0 -o ../../packages
106 |
107 | # linux
108 | dotnet build -r linux-x64 -c Release -p:PackAsTool=false
109 | dotnet publish -r linux-x64 -c Release -p:PackAsTool=false -p:PublishSingleFile=true -p:PublishTrimmed=true -p:PublishReadyToRun=false -f netcoreapp3.0 -o ../../packages
110 | ```
111 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CometD.NetCore.Salesforce
2 |
3 | [](https://raw.githubusercontent.com/kdcllc/cometd-netcore-salesforce/master/LICENSE)
4 | [](https://ci.appveyor.com/project/kdcllc/cometd-netcore-salesforce)
5 | [](https://www.nuget.org/packages?q=Bet.AspNetCore)
6 | 
7 | [](https://f.feedz.io/kdcllc/kdcllc/packages/CometD.NetCore.Salesforce/latest/download)
8 |
9 | _Note: Pre-release packages are distributed via [feedz.io](https://f.feedz.io/kdcllc/kcllc/nuget/index.json)._
10 |
11 | ## Summary
12 |
13 | This repo contains the CometD .NET Core implementation for Salesforce Platform events.
14 |
15 | These events can be subscribed to and listened to by your custom `Event Listener`. The sample application of this library can be found [here](https://github.com/kdcllc/Bet.BuildingBlocks.SalesforceEventBus).
16 |
17 |
18 | The solution contains the following:
19 |
20 | 1. [`CometD.NetCore2.Salesforce`](./src/CometD.NetCore.Salesforce/)
21 | - A Salesforce Platform Events implementation based [Even Bus idea of eShopOnContainers](https://github.com/dotnet-architecture/eShopOnContainers).
22 | - [Reusable Building Blocks and sample application that listens to Salesforce push events](https://github.com/kdcllc/Bet.BuildingBlocks.SalesforceEventBus).
23 |
24 | 2. [DotNet Cli tool `salesforce`](./src/AuthApp/)
25 | - This dotnet cli tool allows for retrieval of `Access` or `Refresh Tokens` to be used by any other application.
26 | Please refer to [How Are Apps Authenticated with the Web Server OAuth Authentication Flow](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/intro_understanding_web_server_oauth_flow.htm)
27 |
28 | [](https://www.buymeacoffee.com/vyve0og)
29 |
30 | ## Give a Star! :star:
31 |
32 | If you like or are using this project to learn or start your solution, please give it a star. Thanks!
33 |
34 | ## Install
35 |
36 | ```csharp
37 | dotnet add package CometD.NetCore.Salesforce
38 | ```
39 |
40 | ## Saleforce Setup
41 |
42 | [Watch Video](https://www.youtube.com/watch?v=L6OWyCfQD6U)
43 |
44 | 1. Sing up for development sandbox with Saleforce: [https://developer.salesforce.com/signup](https://developer.salesforce.com/signup).
45 | 2. Create Connected App in Salesforce.
46 | 3. Create a Platform Event.
47 |
48 | ### Create Connected App in Salesforce
49 |
50 | 1. Setup -> Quick Find -> manage -> App Manager -> New Connected App.
51 | 2. Basic Info:
52 |
53 | 
54 |
55 | 3. API (Enable OAuth Settings):
56 | 
57 |
58 | 4. Retrieve `Consumer Key` and `Consumer Secret` to be used within the Test App
59 |
60 | ### Create a Platform Event
61 | 1. Setup -> Quick Find -> Events -> Platform Events -> New Platform Event:
62 |
63 | 
64 |
65 | 2. Add Custom Field
66 |
67 | 
68 |
69 | (note: use sandbox custom domain for the login to workbench in order to install this app within your production)
70 |
71 | Use workbench to test the Event [workbench](https://workbench.developerforce.com/login.php?startUrl=%2Finsert.php)
72 |
73 | ## AuthApp
74 |
75 | ### OAuth Refresh Token Flow
76 |
77 | [Use login instead of test](https://github.com/developerforce/Force.com-Toolkit-for-NET/wiki/Web-Server-OAuth-Flow-Sample#am-i-using-the-test-environment)
78 | Simple application that provides with Web Server OAuth Authentication Flow to retrieve
79 | `Access Token` and `Refresh Token` to be used within the application.
80 |
81 | ### Username/Password Flow
82 |
83 | To enable Username/Password flow and grant type, simply omit the auth token and refresh token while providing the username, password and user api token.
84 |
85 | ## Special thanks to our contributors
86 |
87 | * [cwoolum](https://github.com/cwoolum)
88 | * [ts46235](https://github.com/ts46235)
89 | * [cternes](https://github.com/cternes)
90 | * [apaulro](https://github.com/apaulro)
91 |
92 | ## Related projects
93 |
94 | - [Oyatel/CometD.NET](https://github.com/Oyatel/CometD.NET)
95 | - [nthachus/CometD.NET](https://github.com/nthachus/CometD.NET)
96 | - [tdawgy/CometD.NetCore](https://github.com/tdawgy/CometD.NetCore)
97 | - [eShopOnContainers](https://github.com/dotnet-architecture/eShopOnContainers)
98 |
99 |
--------------------------------------------------------------------------------
/src/AuthApp/TokenGeneratorCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Drawing;
3 | using System.Threading.Tasks;
4 |
5 | using AuthApp.Internal;
6 |
7 | using McMaster.Extensions.CommandLineUtils;
8 |
9 | using Microsoft.Extensions.DependencyInjection;
10 | using Microsoft.Extensions.Hosting;
11 | using Microsoft.Extensions.Logging;
12 | using Microsoft.Extensions.Options;
13 |
14 | using Console = Colorful.Console;
15 |
16 | namespace AuthApp
17 | {
18 | [Command(
19 | "get-tokens",
20 | Description = "Generates Salesforce Access and Refresh Tokens",
21 | UnrecognizedArgumentHandling = UnrecognizedArgumentHandling.Throw,
22 | AllowArgumentSeparator = true)]
23 | [HelpOption("-?")]
24 | internal class TokenGeneratorCommand
25 | {
26 | [Option("-k|--key", Description = "The Salesforce Consumer Key.")]
27 | public string? ClientId { get; set; }
28 |
29 | [Option("-s|--secret", Description = "The Salesforce Consumer Secret.")]
30 | public string? ClientSecret { get; set; }
31 |
32 | [Option("-l|--login", Description = "The Salesforce login url. The default value is https://login.salesforce.com.")]
33 | public string? LoginUrl { get; set; }
34 |
35 | [Option(
36 | "-a|--azure",
37 | Description = "Allows to specify Azure Vault Url. It overrides url specified in the appsetting.json file or any other configuration provider.")]
38 | public string? AzureVault { get; set; }
39 |
40 | [Option("-ax|--azureprefix", Description = "Enables or disables Hosting Environment prefix to be used for Azure Key Vault. Default is true.")]
41 | public bool UseAzureKeyPrefix { get; set; }
42 |
43 | [Option("-c|--configfile", Description = "Allows to specify a configuration file besides appsettings.json to be specified.")]
44 | public string? ConfigFile { get; set; }
45 |
46 | ///
47 | /// Property types of ValueTuple{bool,T} translate to CommandOptionType.SingleOrNoValue.
48 | /// Input | Value
49 | /// ------------------------|--------------------------------
50 | /// (none) | (false, default(LogLevel))
51 | /// --verbose | (true, LogLevel.Information)
52 | /// --verbose:information | (true, LogLevel.Information)
53 | /// --verbose:debug | (true, LogLevel.Debug)
54 | /// --verbose:trace | (true, LogLevel.Trace).
55 | ///
56 | [Option(Description = "Allows Verbose logging for the tool. Enable this to get tracing information. Default is false and LogLevel.None.")]
57 | public (bool HasValue, LogLevel level) Verbose { get; } = (false, LogLevel.None);
58 |
59 | [Option("-us|--usesecrets", Description = "Enable UserSecrets.")]
60 | public bool UserSecrets { get; set; }
61 |
62 | [Option("-e|--environment", Description = "Specify Hosting Environment Name for the cli tool execution.")]
63 | public string? HostingEnviroment { get; set; }
64 |
65 | [Option("-sn|--section", Description = "Configuration Section Name to retrieve the options. The Default value is Salesforce.")]
66 | public string? SectionName { get; set; }
67 |
68 | private async Task OnExecuteAsync(CommandLineApplication app)
69 | {
70 | var builderConfig = new HostBuilderOptions
71 | {
72 | AzureVault = AzureVault,
73 | UseAzureKeyPrefix = !UseAzureKeyPrefix,
74 | ConfigFile = ConfigFile,
75 | Verbose = Verbose.HasValue,
76 | Level = Verbose.level,
77 | UserSecrets = UserSecrets,
78 | HostingEnviroment = !string.IsNullOrWhiteSpace(HostingEnviroment) ? HostingEnviroment ?? "Development" : "Development",
79 | Settings = new SfConfig
80 | {
81 | ClientId = ClientId,
82 | ClientSecret = ClientSecret,
83 | LoginUrl = !string.IsNullOrWhiteSpace(LoginUrl) ? LoginUrl : "https://login.salesforce.com"
84 | },
85 | SectionName = string.IsNullOrWhiteSpace(SectionName) ? "Salesforce" : SectionName ?? "Salesforce",
86 | };
87 |
88 | try
89 | {
90 | var builder = HostBuilderExtensions.CreateDefaultBuilder(builderConfig)
91 | .ConfigureServices((hostingContext, services) =>
92 | {
93 | services.ConfigureWithDataAnnotationsValidation(hostingContext.Configuration, builderConfig.SectionName);
94 | services.AddHostedService();
95 | });
96 |
97 | await builder.RunConsoleAsync();
98 |
99 | return 0;
100 | }
101 | catch (OptionsValidationException exv)
102 | {
103 | Console.WriteLine($"Not all of the required configurations has been provided. {exv.Message}", Color.Red);
104 |
105 | app.ShowHelp();
106 | }
107 | catch (Exception ex)
108 | {
109 | Console.WriteLine(ex.Message, Color.Red);
110 | }
111 |
112 | return 0;
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/AuthApp/HostBuilderExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Drawing;
3 | using System.IO;
4 |
5 | using Bet.Extensions.Options;
6 |
7 | using McMaster.Extensions.CommandLineUtils;
8 |
9 | using Microsoft.Extensions.Configuration;
10 | using Microsoft.Extensions.DependencyInjection;
11 | using Microsoft.Extensions.Hosting;
12 | using Microsoft.Extensions.Logging;
13 |
14 | using TextCopy;
15 |
16 | using Console = Colorful.Console;
17 |
18 | namespace AuthApp
19 | {
20 | internal static class HostBuilderExtensions
21 | {
22 | internal static IHostBuilder CreateDefaultBuilder(HostBuilderOptions options)
23 | {
24 | var builder = new HostBuilder();
25 |
26 | builder.UseEnvironment(options.HostingEnviroment);
27 | var fullPath = Directory.GetCurrentDirectory();
28 |
29 | if (!string.IsNullOrWhiteSpace(Path.GetDirectoryName(options.ConfigFile)))
30 | {
31 | fullPath = Path.GetDirectoryName(options.ConfigFile);
32 | }
33 |
34 | builder.UseContentRoot(fullPath);
35 |
36 | var defaultConfigName = !string.IsNullOrWhiteSpace(options.ConfigFile) ? Path.GetFileName(options.ConfigFile) : "appsettings.json";
37 |
38 | if (options.Verbose)
39 | {
40 | Console.WriteLine($"ContentRoot:{fullPath}", color: Color.Green);
41 | }
42 |
43 | builder
44 | .UseOptionValidation()
45 | .ConfigureAppConfiguration((context, config) =>
46 | {
47 | // appsettings file or others
48 | config.AddJsonFile(Path.Combine(fullPath, $"{defaultConfigName.Split(".")[0]}.json"), optional: true)
49 | .AddJsonFile(Path.Combine(fullPath, $"{defaultConfigName.Split(".")[0]}.{options.HostingEnviroment}.json"), optional: true);
50 |
51 | // add secrets if specified
52 | if (options.UserSecrets)
53 | {
54 | config.AddUserSecrets(ConsoleHandler.GetUserSecretsId());
55 | }
56 |
57 | // configure Azure Vault from the other settings.
58 | var appAzureVaultUrl = config.Build().Bind("AzureVault", enableValidation: false);
59 |
60 | var inputValues = new Dictionary
61 | {
62 | { $"{options.SectionName}:ClientId", options?.Settings?.ClientId ?? string.Empty },
63 | { $"{options.SectionName}:ClientSecret", options?.Settings?.ClientSecret ?? string.Empty },
64 | { $"{options.SectionName}:LoginUrl", options?.Settings?.LoginUrl ?? string.Empty },
65 | { $"{options.SectionName}:OAuthUri", options?.Settings?.OAuthUri ?? string.Empty },
66 | { $"{options.SectionName}:OAuthorizeUri", options?.Settings?.OAuthorizeUri ?? string.Empty },
67 | };
68 |
69 | config.AddInMemoryCollection(inputValues);
70 |
71 | // build azure key vault from passed in parameter
72 | if (!string.IsNullOrWhiteSpace(options?.AzureVault))
73 | {
74 | var dic = new Dictionary
75 | {
76 | { "AzureVault:BaseUrl", options.AzureVault }
77 | };
78 |
79 | config.AddInMemoryCollection(dic);
80 | }
81 |
82 | // use appsettings vault information
83 | if (!string.IsNullOrWhiteSpace(appAzureVaultUrl.BaseUrl)
84 | || !string.IsNullOrWhiteSpace(options?.AzureVault))
85 | {
86 | config.AddAzureKeyVault(hostingEnviromentName: options.HostingEnviroment, options.UseAzureKeyPrefix);
87 | }
88 |
89 | if ((options.Verbose && options.Level == LogLevel.Debug)
90 | || options.Level == LogLevel.Trace)
91 | {
92 | config.Build().DebugConfigurations();
93 | }
94 | });
95 |
96 | builder
97 | .ConfigureLogging((_, configureBuilder) =>
98 | {
99 | if (options.Verbose)
100 | {
101 | configureBuilder.AddConsole();
102 | configureBuilder.AddDebug();
103 | }
104 | });
105 |
106 | builder
107 | .ConfigureServices((context, services) =>
108 | {
109 | services.AddSingleton(options);
110 |
111 | services.AddHostedService();
112 |
113 | // disable hosting messages
114 | services.Configure(opt => opt.SuppressStatusMessages = true);
115 |
116 | if (options.Verbose)
117 | {
118 | services.AddLogging(x => x.AddFilter((loglevel) =>
119 | {
120 | return loglevel == options.Level;
121 | }));
122 | }
123 |
124 | services.AddSingleton();
125 |
126 | services.AddSingleton(PhysicalConsole.Singleton);
127 | });
128 |
129 | return builder;
130 | }
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/AuthApp/Internal/HttpServer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Drawing;
3 | using System.IO;
4 | using System.Net;
5 | using System.Net.Sockets;
6 | using System.Text;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 |
10 | using Microsoft.Extensions.Hosting;
11 |
12 | using NetCoreForce.Client;
13 |
14 | using TextCopy;
15 |
16 | using Console = Colorful.Console;
17 |
18 | namespace AuthApp.Internal
19 | {
20 | ///
21 | /// Web Server OAuth Authentication Flow
22 | /// https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/intro_understanding_web_server_oauth_flow.htm.
23 | ///
24 | internal class HttpServer : BackgroundService
25 | {
26 | private readonly HostBuilderOptions _hostOptions;
27 | private readonly SfConfig _config;
28 | private readonly IHostApplicationLifetime _applicationLifetime;
29 | private readonly IClipboard _clipboard;
30 | private bool _isCompleted = false;
31 |
32 | public HttpServer(
33 | HostBuilderOptions hostOptions,
34 | SfConfig config,
35 | IHostApplicationLifetime applicationLifetime,
36 | IClipboard clipboard)
37 | {
38 | _hostOptions = hostOptions;
39 | _config = config;
40 | _applicationLifetime = applicationLifetime;
41 | _clipboard = clipboard;
42 | }
43 |
44 | protected override async Task ExecuteAsync(CancellationToken stoppingToken)
45 | {
46 | Console.WriteLine();
47 | Console.WriteAscii("Token Generation Started...", Colorful.FigletFont.Default);
48 | Console.WriteLine();
49 |
50 | if (_hostOptions.Verbose)
51 | {
52 | Console.WriteLine($"{nameof(HttpServer)} is starting.");
53 | }
54 |
55 | var http = new HttpListener();
56 | var redirectURI = string.Format("http://{0}:{1}/", "localhost", GetRandomUnusedPort());
57 | http.Prefixes.Add(redirectURI);
58 | http.Start();
59 |
60 | var authUrl = GetAuthorizationUrl(redirectURI);
61 |
62 | if (_hostOptions.Verbose)
63 | {
64 | Console.WriteLine($"Opening a browser window with Url: {authUrl}", Color.Blue);
65 | }
66 |
67 | var process = ConsoleHandler.OpenBrowser(authUrl);
68 |
69 | var context = await http.GetContextAsync();
70 |
71 | while (!stoppingToken.IsCancellationRequested)
72 | {
73 | if (_isCompleted)
74 | {
75 | _applicationLifetime.StopApplication();
76 | return;
77 | }
78 |
79 | if (_hostOptions.Verbose)
80 | {
81 | Console.WriteLine($"{nameof(HttpServer)} is running");
82 | }
83 |
84 | if (context != null)
85 | {
86 | var responseOutput = await ShowBrowserMessage(context);
87 |
88 | responseOutput.Close();
89 |
90 | if (context.Request.QueryString.Get("error") != null)
91 | {
92 | Console.WriteLine($"OAuth authorization error: {context.Request.QueryString.Get("error")}.", Color.Red);
93 | }
94 |
95 | if (context.Request.QueryString.Get("code") == null)
96 | {
97 | Console.WriteLine($"Malformed authorization response {context.Request.QueryString}", Color.Red);
98 | }
99 |
100 | // Authorization code the consumer must use to obtain the access and refresh tokens.
101 | // The authorization code expires after 15 minutes.
102 | var code = context.Request.QueryString.Get("code");
103 | Console.WriteLine($"The authorization code will expire in 15 minutes: {code}", Color.Blue);
104 |
105 | var auth = new AuthenticationClient();
106 | await auth.WebServerAsync(
107 | _config.ClientId,
108 | _config.ClientSecret,
109 | redirectURI,
110 | code,
111 | $"{_config.LoginUrl}{_config.OAuthUri}");
112 |
113 | Console.WriteLineFormatted("Access_token = {0}", Color.Green, Color.Yellow, auth.AccessInfo.AccessToken);
114 |
115 | Console.WriteLineFormatted("Refresh_token = {0}", Color.Green, Color.Yellow, auth.AccessInfo.RefreshToken);
116 |
117 | await _clipboard.SetTextAsync(auth.AccessInfo.RefreshToken);
118 |
119 | Console.WriteLine($"Refresh_token copied to the Clipboard", color: Color.Yellow);
120 |
121 | _isCompleted = true;
122 |
123 | http.Stop();
124 | if (_hostOptions.Verbose)
125 | {
126 | Console.WriteLine($"{nameof(HttpServer)} is stopping.");
127 | }
128 | }
129 |
130 | await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
131 |
132 | Console.WriteLine();
133 | Console.Write("Thanks for using this cli tool");
134 | }
135 | }
136 |
137 | private static async Task ShowBrowserMessage(HttpListenerContext context)
138 | {
139 | var response = context.Response;
140 | var responseString = string.Format(@"
141 |
142 | Please return to the console to retrieve access and refresh tokens.
143 | ");
144 |
145 | var buffer = Encoding.UTF8.GetBytes(responseString);
146 | response.ContentLength64 = buffer.Length;
147 | var responseOutput = response.OutputStream;
148 | await responseOutput.WriteAsync(buffer, 0, buffer.Length);
149 | return responseOutput;
150 | }
151 |
152 | private int GetRandomUnusedPort()
153 | {
154 | var listener = new TcpListener(IPAddress.Loopback, 5050);
155 | listener.Start();
156 | var port = ((IPEndPoint)listener.LocalEndpoint).Port;
157 | listener.Stop();
158 | return port;
159 | }
160 |
161 | private string GetAuthorizationUrl(string redirectURI)
162 | {
163 | var authEndpoint = $"{_config.LoginUrl}{_config.OAuthorizeUri}";
164 | var url = $"{authEndpoint}?response_type=code&access_type=offline&scope=openid%20profile%20api%20refresh_token%20offline_access&redirect_uri={Uri.EscapeDataString(redirectURI)}&client_id={_config.ClientId}";
165 | return url;
166 | }
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 | *appsettings.Development.json
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Build results
17 | [Dd]ebug/
18 | [Dd]ebugPublic/
19 | [Rr]elease/
20 | [Rr]eleases/
21 | x64/
22 | x86/
23 | bld/
24 | [Bb]in/
25 | [Oo]bj/
26 | [Ll]og/
27 |
28 | # Visual Studio 2015/2017 cache/options directory
29 | .vs/
30 | # Uncomment if you have tasks that create the project's static files in wwwroot
31 | #wwwroot/
32 |
33 | # Visual Studio 2017 auto generated files
34 | Generated\ Files/
35 |
36 | # MSTest test Results
37 | [Tt]est[Rr]esult*/
38 | [Bb]uild[Ll]og.*
39 |
40 | # NUNIT
41 | *.VisualState.xml
42 | TestResult.xml
43 |
44 | # Build Results of an ATL Project
45 | [Dd]ebugPS/
46 | [Rr]eleasePS/
47 | dlldata.c
48 |
49 | # Benchmark Results
50 | BenchmarkDotNet.Artifacts/
51 |
52 | # .NET Core
53 | project.lock.json
54 | project.fragment.lock.json
55 | artifacts/
56 | #**/Properties/launchSettings.json
57 |
58 | # StyleCop
59 | StyleCopReport.xml
60 |
61 | # Files built by Visual Studio
62 | *_i.c
63 | *_p.c
64 | *_i.h
65 | *.ilk
66 | *.meta
67 | *.obj
68 | *.iobj
69 | *.pch
70 | *.pdb
71 | *.ipdb
72 | *.pgc
73 | *.pgd
74 | *.rsp
75 | *.sbr
76 | *.tlb
77 | *.tli
78 | *.tlh
79 | *.tmp
80 | *.tmp_proj
81 | *.log
82 | *.vspscc
83 | *.vssscc
84 | .builds
85 | *.pidb
86 | *.svclog
87 | *.scc
88 |
89 | # Chutzpah Test files
90 | _Chutzpah*
91 |
92 | # Visual C++ cache files
93 | ipch/
94 | *.aps
95 | *.ncb
96 | *.opendb
97 | *.opensdf
98 | *.sdf
99 | *.cachefile
100 | *.VC.db
101 | *.VC.VC.opendb
102 |
103 | # Visual Studio profiler
104 | *.psess
105 | *.vsp
106 | *.vspx
107 | *.sap
108 |
109 | # Visual Studio Trace Files
110 | *.e2e
111 |
112 | # TFS 2012 Local Workspace
113 | $tf/
114 |
115 | # Guidance Automation Toolkit
116 | *.gpState
117 |
118 | # ReSharper is a .NET coding add-in
119 | _ReSharper*/
120 | *.[Rr]e[Ss]harper
121 | *.DotSettings.user
122 |
123 | # JustCode is a .NET coding add-in
124 | .JustCode
125 |
126 | # TeamCity is a build add-in
127 | _TeamCity*
128 |
129 | # DotCover is a Code Coverage Tool
130 | *.dotCover
131 |
132 | # AxoCover is a Code Coverage Tool
133 | .axoCover/*
134 | !.axoCover/settings.json
135 |
136 | # Visual Studio code coverage results
137 | *.coverage
138 | *.coveragexml
139 |
140 | # NCrunch
141 | _NCrunch_*
142 | .*crunch*.local.xml
143 | nCrunchTemp_*
144 |
145 | # MightyMoose
146 | *.mm.*
147 | AutoTest.Net/
148 |
149 | # Web workbench (sass)
150 | .sass-cache/
151 |
152 | # Installshield output folder
153 | [Ee]xpress/
154 |
155 | # DocProject is a documentation generator add-in
156 | DocProject/buildhelp/
157 | DocProject/Help/*.HxT
158 | DocProject/Help/*.HxC
159 | DocProject/Help/*.hhc
160 | DocProject/Help/*.hhk
161 | DocProject/Help/*.hhp
162 | DocProject/Help/Html2
163 | DocProject/Help/html
164 |
165 | # Click-Once directory
166 | publish/
167 |
168 | # Publish Web Output
169 | *.[Pp]ublish.xml
170 | *.azurePubxml
171 | # Note: Comment the next line if you want to checkin your web deploy settings,
172 | # but database connection strings (with potential passwords) will be unencrypted
173 | *.pubxml
174 | *.publishproj
175 |
176 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
177 | # checkin your Azure Web App publish settings, but sensitive information contained
178 | # in these scripts will be unencrypted
179 | PublishScripts/
180 |
181 | # NuGet Packages
182 | *.nupkg
183 | # The packages folder can be ignored because of Package Restore
184 | **/[Pp]ackages/*
185 | # except build/, which is used as an MSBuild target.
186 | !**/[Pp]ackages/build/
187 | # Uncomment if necessary however generally it will be regenerated when needed
188 | #!**/[Pp]ackages/repositories.config
189 | # NuGet v3's project.json files produces more ignorable files
190 | *.nuget.props
191 | *.nuget.targets
192 |
193 | # Microsoft Azure Build Output
194 | csx/
195 | *.build.csdef
196 |
197 | # Microsoft Azure Emulator
198 | ecf/
199 | rcf/
200 |
201 | # Windows Store app package directories and files
202 | AppPackages/
203 | BundleArtifacts/
204 | Package.StoreAssociation.xml
205 | _pkginfo.txt
206 | *.appx
207 |
208 | # Visual Studio cache files
209 | # files ending in .cache can be ignored
210 | *.[Cc]ache
211 | # but keep track of directories ending in .cache
212 | !*.[Cc]ache/
213 |
214 | # Others
215 | ClientBin/
216 | ~$*
217 | *~
218 | *.dbmdl
219 | *.dbproj.schemaview
220 | *.jfm
221 | *.pfx
222 | *.publishsettings
223 | orleans.codegen.cs
224 |
225 | # Including strong name files can present a security risk
226 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
227 | #*.snk
228 |
229 | # Since there are multiple workflows, uncomment next line to ignore bower_components
230 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
231 | #bower_components/
232 |
233 | # RIA/Silverlight projects
234 | Generated_Code/
235 |
236 | # Backup & report files from converting an old project file
237 | # to a newer Visual Studio version. Backup files are not needed,
238 | # because we have git ;-)
239 | _UpgradeReport_Files/
240 | Backup*/
241 | UpgradeLog*.XML
242 | UpgradeLog*.htm
243 | ServiceFabricBackup/
244 | *.rptproj.bak
245 |
246 | # SQL Server files
247 | *.mdf
248 | *.ldf
249 | *.ndf
250 |
251 | # Business Intelligence projects
252 | *.rdl.data
253 | *.bim.layout
254 | *.bim_*.settings
255 | *.rptproj.rsuser
256 |
257 | # Microsoft Fakes
258 | FakesAssemblies/
259 |
260 | # GhostDoc plugin setting file
261 | *.GhostDoc.xml
262 |
263 | # Node.js Tools for Visual Studio
264 | .ntvs_analysis.dat
265 | node_modules/
266 |
267 | # Visual Studio 6 build log
268 | *.plg
269 |
270 | # Visual Studio 6 workspace options file
271 | *.opt
272 |
273 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
274 | *.vbw
275 |
276 | # Visual Studio LightSwitch build output
277 | **/*.HTMLClient/GeneratedArtifacts
278 | **/*.DesktopClient/GeneratedArtifacts
279 | **/*.DesktopClient/ModelManifest.xml
280 | **/*.Server/GeneratedArtifacts
281 | **/*.Server/ModelManifest.xml
282 | _Pvt_Extensions
283 |
284 | # Paket dependency manager
285 | .paket/paket.exe
286 | paket-files/
287 |
288 | # FAKE - F# Make
289 | .fake/
290 |
291 | # JetBrains Rider
292 | .idea/
293 | *.sln.iml
294 |
295 | # CodeRush
296 | .cr/
297 |
298 | # Python Tools for Visual Studio (PTVS)
299 | __pycache__/
300 | *.pyc
301 |
302 | # Cake - Uncomment if you are using it
303 | # tools/**
304 | # !tools/packages.config
305 |
306 | # Tabs Studio
307 | *.tss
308 |
309 | # Telerik's JustMock configuration file
310 | *.jmconfig
311 |
312 | # BizTalk build output
313 | *.btp.cs
314 | *.btm.cs
315 | *.odx.cs
316 | *.xsd.cs
317 |
318 | # OpenCover UI analysis results
319 | OpenCover/
320 |
321 | # Azure Stream Analytics local run output
322 | ASALocalRun/
323 |
324 | # MSBuild Binary and Structured Log
325 | *.binlog
326 |
327 | # NVidia Nsight GPU debugger configuration file
328 | *.nvuser
329 |
330 | # MFractors (Xamarin productivity tool) working folder
331 | .mfractor/
332 |
--------------------------------------------------------------------------------
/src/CometD.NetCore.Salesforce/DependencyInjection/StreamingClientServiceExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 |
4 | using CometD.NetCore.Salesforce;
5 | using CometD.NetCore.Salesforce.Resilience;
6 |
7 | using Microsoft.Extensions.DependencyInjection.Extensions;
8 | using Microsoft.Extensions.Options;
9 |
10 | using NetCoreForce.Client;
11 | using NetCoreForce.Client.Models;
12 |
13 | using Polly;
14 |
15 | namespace Microsoft.Extensions.DependencyInjection
16 | {
17 | ///
18 | /// An extension method for .
19 | ///
20 | public static class StreamingClientServiceExtensions
21 | {
22 | ///
23 | /// Add custom implementation for .
24 | /// There can be only one implementation registered with DI at any time.
25 | ///
26 | /// The DI services.
27 | /// The section name for the root options configuration. The default value is Salesforce.
28 | ///
29 | /// The option configuration that can be override the configuration provides.
30 | ///
31 | public static IServiceCollection AddResilientStreamingClient(
32 | this IServiceCollection services,
33 | string sectionName = "Salesforce",
34 | string optionName = "",
35 | Action? configureOptions = default)
36 | where T : IStreamingClient
37 | {
38 | services.AddChangeTokenOptions(
39 | sectionName,
40 | optionName: optionName,
41 | configureAction: (o, sp) => configureOptions?.Invoke(o, sp));
42 |
43 | services.AddResilentForceClient(optionName);
44 |
45 | services.TryAdd(new ServiceDescriptor(typeof(IStreamingClient), typeof(T), ServiceLifetime.Singleton));
46 |
47 | return services;
48 | }
49 |
50 |
51 | ///
52 | /// Adds ForecClient Resilient version of it with Refresh Token Authentication.
53 | ///
54 | /// Can be used in the code with .
55 | ///
56 | /// The DI services.
57 | /// The section name for the root options configuration. The default value is Salesforce.
58 | ///
59 | /// The option configuration that can be override the configuration provides.
60 | ///
61 | public static IServiceCollection AddResilientStreamingClient(
62 | this IServiceCollection services,
63 | string sectionName = "Salesforce",
64 | string optionName = "",
65 | Action? configureOptions = default)
66 | {
67 | services.AddChangeTokenOptions(
68 | sectionName,
69 | optionName: optionName,
70 | configureAction: x => configureOptions?.Invoke(x));
71 |
72 | services.AddResilentForceClient(optionName);
73 |
74 | services.TryAddSingleton();
75 |
76 | return services;
77 | }
78 |
79 | private static IServiceCollection AddResilentForceClient(this IServiceCollection services, string optionName)
80 | {
81 | services.TryAddSingleton();
82 |
83 | services.TryAddSingleton>>(sp => () =>
84 | {
85 | var options = sp.GetRequiredService>().Get(optionName);
86 |
87 | if (!TimeSpan.TryParse(options.TokenExpiration, out var tokenExpiration))
88 | {
89 | tokenExpiration = TimeSpan.FromHours(1);
90 | }
91 |
92 | return new AsyncExpiringLazy(async data =>
93 | {
94 | if (data.Result == null
95 | || DateTime.UtcNow > data.ValidUntil.Subtract(TimeSpan.FromSeconds(30)))
96 | {
97 | var policy = Policy
98 | .Handle()
99 | .WaitAndRetryAsync(
100 | retryCount: options.Retry,
101 | sleepDurationProvider: (retryAttempt) => TimeSpan.FromSeconds(Math.Pow(options.BackoffPower, retryAttempt)));
102 |
103 | var authClient = await policy.ExecuteAsync(async () =>
104 | {
105 | var auth = new AuthenticationClient();
106 |
107 | if (string.IsNullOrWhiteSpace(options.RefreshToken) &&
108 | !string.IsNullOrWhiteSpace(options.Username))
109 | {
110 | await auth.UsernamePasswordAsync(
111 | options.ClientId,
112 | options.ClientSecret,
113 | options.Username,
114 | options.Password + options.UserPasswordToken,
115 | $"{options.LoginUrl}{options.OAuthUri}");
116 | }
117 | else
118 | {
119 | await auth.TokenRefreshAsync(
120 | options.RefreshToken,
121 | options.ClientId,
122 | options.ClientSecret,
123 | $"{options.LoginUrl}{options.OAuthUri}");
124 | }
125 |
126 | return auth;
127 | });
128 |
129 | return new AsyncExpirationValue
130 | {
131 | Result = authClient.AccessInfo,
132 | ValidUntil = DateTimeOffset.UtcNow.Add(tokenExpiration)
133 | };
134 | }
135 |
136 | return data;
137 | });
138 | });
139 |
140 | services.AddSingleton>>(sp => () =>
141 | {
142 | var options = sp.GetRequiredService>().Get(optionName);
143 |
144 | if (!TimeSpan.TryParse(options.TokenExpiration, out var tokenExpiration))
145 | {
146 | tokenExpiration = TimeSpan.FromHours(1);
147 | }
148 |
149 | return new AsyncExpiringLazy(async data =>
150 | {
151 | if (data.Result == null
152 | || DateTime.UtcNow > data.ValidUntil.Subtract(TimeSpan.FromSeconds(30)))
153 | {
154 | var policy = Policy
155 | .Handle()
156 | .WaitAndRetryAsync(
157 | retryCount: options.Retry,
158 | sleepDurationProvider: (retryAttempt) => TimeSpan.FromSeconds(Math.Pow(options.BackoffPower, retryAttempt)));
159 |
160 | var client = await policy.ExecuteAsync(async () =>
161 | {
162 | var authClient = new AuthenticationClient();
163 |
164 | if (string.IsNullOrWhiteSpace(options.RefreshToken) &&
165 | !string.IsNullOrWhiteSpace(options.Username))
166 | {
167 | await authClient.UsernamePasswordAsync(
168 | options.ClientId,
169 | options.ClientSecret,
170 | options.Username,
171 | options.Password + options.UserPasswordToken,
172 | $"{options.LoginUrl}{options.OAuthUri}");
173 | }
174 | else
175 | {
176 | await authClient.TokenRefreshAsync(
177 | options.RefreshToken,
178 | options.ClientId,
179 | options.ClientSecret,
180 | $"{options.LoginUrl}{options.OAuthUri}");
181 | }
182 |
183 | return new ForceClient(
184 | authClient.AccessInfo.InstanceUrl,
185 | authClient.ApiVersion,
186 | authClient.AccessInfo.AccessToken);
187 | });
188 |
189 | return new AsyncExpirationValue
190 | {
191 | Result = client,
192 | ValidUntil = DateTimeOffset.UtcNow.Add(tokenExpiration)
193 | };
194 | }
195 |
196 | return data;
197 | });
198 | });
199 |
200 | return services;
201 | }
202 | }
203 | }
204 |
--------------------------------------------------------------------------------
/src/CometD.NetCore.Salesforce/ResilientStreamingClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.Specialized;
4 | using System.Net;
5 | using System.Threading;
6 |
7 | using CometD.NetCore.Bayeux.Client;
8 | using CometD.NetCore.Client;
9 | using CometD.NetCore.Client.Extension;
10 | using CometD.NetCore.Client.Transport;
11 |
12 | using Microsoft.Extensions.Logging;
13 | using Microsoft.Extensions.Options;
14 |
15 | using NetCoreForce.Client.Models;
16 |
17 | namespace CometD.NetCore.Salesforce
18 | {
19 | public class ResilientStreamingClient : IStreamingClient
20 | {
21 | private readonly ILogger _logger;
22 | private readonly SalesforceConfiguration _options;
23 | private readonly AsyncExpiringLazy _tokenResponse;
24 |
25 | // long polling duration
26 | private readonly int _readTimeOut = 120 * 1000;
27 |
28 | private BayeuxClient? _bayeuxClient = null;
29 | private bool _isDisposed = false;
30 | private ErrorExtension? _errorExtension;
31 | private LongPollingTransport? _clientTransport;
32 | private ReplayExtension? _replayIdExtension;
33 |
34 | public Action InvalidReplayIdStrategy { get; set; }
35 |
36 | ///
37 | /// Initializes a new instance of the class.
38 | ///
39 | ///
40 | ///
41 | ///
42 | public ResilientStreamingClient(
43 | Func> tokenResponse,
44 | IOptions options,
45 | ILogger logger)
46 | {
47 | _logger = logger;
48 | _options = options.Value;
49 | _tokenResponse = tokenResponse();
50 |
51 | CreateBayeuxClient();
52 | }
53 |
54 | ///
55 | /// Finalizes an instance of the class.
56 | ///
57 | ~ResilientStreamingClient()
58 | {
59 | Dispose(false);
60 | }
61 |
62 | public event EventHandler? Reconnect;
63 |
64 | public bool IsConnected
65 | {
66 | get
67 | {
68 | if (_bayeuxClient == null)
69 | {
70 | return false;
71 | }
72 |
73 | return _bayeuxClient.Connected;
74 | }
75 | }
76 |
77 | ///
78 | public void Disconnect()
79 | {
80 | Disconnect(1000);
81 | }
82 |
83 | ///
84 | public void Disconnect(int timeout)
85 | {
86 | if (_isDisposed)
87 | {
88 | throw new ObjectDisposedException("Cannot disconnect when disposed");
89 | }
90 |
91 | _bayeuxClient?.ResetSubscriptions();
92 |
93 | _logger.LogDebug("Disconnecting...");
94 | _bayeuxClient?.Disconnect();
95 | _bayeuxClient?.WaitFor(timeout, new[] { BayeuxClient.State.DISCONNECTED });
96 |
97 | if (_errorExtension != null)
98 | {
99 | _errorExtension.ConnectionError -= ErrorExtension_ConnectionError;
100 | _errorExtension.ConnectionException -= ErrorExtension_ConnectionException;
101 | _errorExtension.ConnectionMessage -= ErrorExtension_ConnectionMessage;
102 | }
103 |
104 | _logger.LogDebug("Disconnected...");
105 | }
106 |
107 | ///
108 | /// The Handshake uses the default value of 1000 ms.
109 | ///
110 | public void Handshake()
111 | {
112 | Handshake(1000);
113 | }
114 |
115 | ///
116 | public void Handshake(int timeout)
117 | {
118 | if (_bayeuxClient == null)
119 | {
120 | return;
121 | }
122 |
123 | if (_isDisposed)
124 | {
125 | throw new ObjectDisposedException("Cannot connect when disposed");
126 | }
127 |
128 | _logger.LogDebug("Handshaking...");
129 | _bayeuxClient.Handshake();
130 | _bayeuxClient.WaitFor(timeout, new[] { BayeuxClient.State.CONNECTED });
131 | _logger.LogDebug("Connected");
132 | }
133 |
134 | ///
135 | public void SubscribeTopic(
136 | string topicName,
137 | IMessageListener listener,
138 | long replayId = -1)
139 | {
140 | if (topicName == null || (topicName = topicName.Trim()).Length == 0)
141 | {
142 | throw new ArgumentNullException(nameof(topicName));
143 | }
144 |
145 | if (listener == null)
146 | {
147 | throw new ArgumentNullException(nameof(listener));
148 | }
149 |
150 | var channel = _bayeuxClient?.GetChannel(topicName, replayId);
151 | channel?.Subscribe(listener);
152 | }
153 |
154 | ///
155 | public bool UnsubscribeTopic(
156 | string topicName,
157 | IMessageListener? listener = null,
158 | long replayId = -1)
159 | {
160 | if (topicName == null || (topicName = topicName.Trim()).Length == 0)
161 | {
162 | throw new ArgumentNullException(nameof(topicName));
163 | }
164 |
165 | var channel = _bayeuxClient?.GetChannel(topicName, replayId);
166 | if (channel != null)
167 | {
168 | if (listener != null)
169 | {
170 | channel.Unsubscribe(listener);
171 | }
172 | else
173 | {
174 | channel.Unsubscribe();
175 | }
176 |
177 | return true;
178 | }
179 |
180 | return false;
181 | }
182 |
183 | ///
184 | /// Disposing of the resources.
185 | ///
186 | public void Dispose()
187 | {
188 | Dispose(true);
189 | GC.SuppressFinalize(this);
190 | }
191 |
192 | ///
193 | /// Disposing of the resources.
194 | ///
195 | ///
196 | protected virtual void Dispose(bool disposing)
197 | {
198 | if (disposing && !_isDisposed)
199 | {
200 | Disconnect();
201 | _isDisposed = true;
202 | }
203 | }
204 |
205 | protected virtual void ErrorExtension_ConnectionError(
206 | object sender,
207 | string e)
208 | {
209 | // authentication failure
210 | if (string.Equals(e, "403::Handshake denied", StringComparison.OrdinalIgnoreCase)
211 | || string.Equals(e, "403:denied_by_security_policy:create_denied", StringComparison.OrdinalIgnoreCase)
212 | || string.Equals(e, "403::unknown client", StringComparison.OrdinalIgnoreCase)
213 | || string.Equals(e, "401::Authentication invalid", StringComparison.OrdinalIgnoreCase))
214 | {
215 | _logger.LogWarning("Handled CometD Exception: {message}", e);
216 |
217 | // 1. Disconnect existing client.
218 | Disconnect();
219 |
220 | // 2. Invalidate the access token.
221 | _tokenResponse.Invalidate();
222 |
223 | _logger.LogDebug("Invalidate token for {name} ...", nameof(BayeuxClient));
224 |
225 | // 3. Recreate BayeuxClient and populate it with a new transport with new security headers.
226 | CreateBayeuxClient();
227 |
228 | // 4. Invoke the Reconnect Event
229 | Reconnect?.Invoke(this, true);
230 | }
231 | else if (e.Contains("you provided was invalid"))
232 | {
233 | var start = e.IndexOf('{');
234 | var end = e.IndexOf('}');
235 | var replayIdString = e.Substring(start + 1, end - (start + 1));
236 |
237 | if (int.TryParse(replayIdString, out var replayId))
238 | {
239 | InvalidReplayIdStrategy(replayId);
240 | }
241 | }
242 | else
243 | {
244 | _logger.LogError("{name} failed with the following message: {message}", nameof(ResilientStreamingClient), e);
245 | }
246 | }
247 |
248 | protected virtual void ErrorExtension_ConnectionException(
249 | object sender,
250 | Exception ex)
251 | {
252 | // ongoing time out issue not to be considered as error in the log.
253 | if (ex?.Message == "The operation has timed out.")
254 | {
255 | _logger.LogDebug(ex.Message);
256 | }
257 | else if (ex != null)
258 | {
259 | _logger.LogError(ex.ToString());
260 | }
261 | }
262 |
263 | protected virtual void ErrorExtension_ConnectionMessage(
264 | object sender,
265 | string message)
266 | {
267 | _logger.LogDebug(message);
268 | }
269 |
270 | private void CreateBayeuxClient()
271 | {
272 | if (_isDisposed)
273 | {
274 | throw new ObjectDisposedException("Cannot create connection when disposed");
275 | }
276 |
277 | _logger.LogDebug("Creating {name} ...", nameof(BayeuxClient));
278 |
279 | var accessToken = _tokenResponse.Value().Result;
280 |
281 | // only need the scheme and host, strip out the rest
282 | var serverUri = new Uri(accessToken.InstanceUrl);
283 | var endpoint = $"{serverUri.Scheme}://{serverUri.Host}{_options.CometDUri}";
284 |
285 | var headers = new NameValueCollection { { nameof(HttpRequestHeader.Authorization), $"{_options.TokenType} {accessToken.AccessToken}" } };
286 |
287 | // Salesforce socket timeout during connection(CometD session) = 110 seconds
288 | var options = new Dictionary(StringComparer.OrdinalIgnoreCase)
289 | {
290 | { ClientTransport.TIMEOUT_OPTION, _options.ReadTimeOut ?? _readTimeOut },
291 | { ClientTransport.MAX_NETWORK_DELAY_OPTION, _options.ReadTimeOut ?? _readTimeOut }
292 | };
293 |
294 | _clientTransport = new LongPollingTransport(options, headers);
295 |
296 | _bayeuxClient = new BayeuxClient(endpoint, _clientTransport);
297 |
298 | // adds logging and also raises an event to process reconnection to the server.
299 | _errorExtension = new ErrorExtension();
300 | _errorExtension.ConnectionError += ErrorExtension_ConnectionError;
301 | _errorExtension.ConnectionException += ErrorExtension_ConnectionException;
302 | _errorExtension.ConnectionMessage += ErrorExtension_ConnectionMessage;
303 | _bayeuxClient.AddExtension(_errorExtension);
304 |
305 | _replayIdExtension = new ReplayExtension();
306 | _bayeuxClient.AddExtension(_replayIdExtension);
307 |
308 | _logger.LogDebug("{name} was created...", nameof(BayeuxClient));
309 | }
310 | }
311 | }
312 |
--------------------------------------------------------------------------------
/src/CometD.NetCore.Salesforce/Resilience/IResilientForceClient.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 |
5 | using NetCoreForce.Client;
6 | using NetCoreForce.Client.Models;
7 |
8 | namespace CometD.NetCore.Salesforce.Resilience
9 | {
10 | ///
11 | /// Resilient Library for .
12 | ///
13 | public interface IResilientForceClient
14 | {
15 | ///
16 | /// Get a basic SOQL COUNT() query result
17 | /// The query must start with SELECT COUNT() FROM, with no named field in the count
18 | /// clause. COUNT() must be the only element in the SELECT list.
19 | ///
20 | /// SOQL query string starting with SELECT COUNT() FROM.
21 | /// True if deleted records are to be included. The default is false.
22 | ///
23 | /// The System.Threading.Tasks.Task`1 returning the count.
24 | Task CountQueryAsync(
25 | string queryString,
26 | bool queryAll = false,
27 | CancellationToken cancellationToken = default);
28 |
29 | ///
30 | /// Creates a new record.
31 | ///
32 | /// The type for the SObject name, e.g. "Account".
33 | ///
34 | /// The SObject name, e.g. "Account".
35 | /// Custom headers to include in request (Optional). await The HeaderFormatter helper
36 | /// class can be used to generate the custom header as needed.
37 | ///
38 | /// The Cancellation Token.
39 | /// CreateResponse object, includes new object's ID.
40 | Task CreateRecordAsync(
41 | string sObjectTypeName,
42 | T sObject,
43 | Dictionary? customHeaders = null,
44 | CancellationToken cancellationToken = default);
45 |
46 | ///
47 | /// Delete record.
48 | ///
49 | /// SObject name, e.g. "Account".
50 | /// Id of Object to update.
51 | ///
52 | /// void, API returns 204/NoContent.
53 | Task DeleteRecordAsync(
54 | string sObjectTypeName,
55 | string objectId,
56 | CancellationToken cancellationToken = default);
57 |
58 | ///
59 | /// Get a List of Objects
60 | /// Use the Describe Global resource to list the objects available in your org and
61 | /// available to the logged-in user. This resource also returns the org encoding,
62 | /// as well as maximum batch size permitted in queries. ///.
63 | ///
64 | /// Returns DescribeGlobal object with a SObjectDescribe collection.
65 | Task DescribeGlobalAsync(CancellationToken cancellationToken = default);
66 |
67 | ///
68 | /// List summary information about each REST API version currently available, including
69 | /// the version, label, and a link to each version's root. You do not need authentication
70 | /// to retrieve the list of versions.
71 | ///
72 | ///
73 | /// Current instance URL. If the client has been initialized, the parameter is optional
74 | /// and the client's current instance URL will be used.
75 | ///
76 | ///
77 | ///
78 | Task> GetAvailableRestApiVersionsAsync(
79 | string? currentInstanceUrl = null,
80 | CancellationToken cancellationToken = default);
81 |
82 | ///
83 | /// Retrieve(basic) metadata for an object.
84 | /// Use the SObject Basic Information resource to retrieve metadata for an object.
85 | ///
86 | /// SObject name, e.g. Account.
87 | ///
88 | ///
89 | Task GetObjectBasicInfoAsync(
90 | string objectTypeName,
91 | CancellationToken cancellationToken = default);
92 |
93 | ///
94 | /// Get SObject by ID.
95 | ///
96 | ///
97 | /// SObject name, e.g. "Account".
98 | /// SObject ID.
99 | /// (optional) List of fields to retrieve, if not supplied, all fields are retrieved.
100 | ///
101 | ///
102 | Task GetObjectByIdAsync(
103 | string sObjectTypeName,
104 | string objectId,
105 | List? fields = null,
106 | CancellationToken cancellationToken = default);
107 |
108 | ///
109 | /// Get field and other metadata for an object.
110 | /// Use the SObject Describe resource to retrieve all the metadata for an object,
111 | /// including information about each field, URLs, and child relationships.
112 | ///
113 | ///
114 | ///
115 | /// Returns SObjectMetadataAll with full object meta including field metadata.
116 | Task GetObjectDescribeAsync(
117 | string objectTypeName,
118 | CancellationToken cancellationToken = default);
119 |
120 | ///
121 | /// Lists information about limits in your org.
122 | /// This resource is available in REST API version 29.0 and later for API users with
123 | /// the View Setup and Configuration permission.
124 | ///
125 | ///
126 | ///
127 | Task GetOrganizationLimitsAsync(CancellationToken cancellationToken = default);
128 |
129 | ///
130 | /// Get current user's info via Identity URL
131 | /// https://developer.salesforce.com/docs/atlas.en-us.mobile_sdk.meta/mobile_sdk/oauth_using_identity_urls.htm.
132 | ///
133 | ///
134 | ///
135 | /// UserInfo.
136 | Task GetUserInfoAsync(
137 | string identityUrl,
138 | CancellationToken cancellationToken = default);
139 |
140 | ///
141 | /// Inserts or Updates a records based on external id.
142 | ///
143 | ///
144 | /// SObject name, e.g. "Account".
145 | /// External ID field name.
146 | /// External ID field value.
147 | /// Object to update.
148 | /// Custom headers to include in request (Optional). await The HeaderFormatter helper class
149 | /// can be used to generate the custom header as needed.
150 | /// .
151 | ///
152 | Task InsertOrUpdateRecordAsync(
153 | string sObjectTypeName,
154 | string fieldName,
155 | string fieldValue,
156 | T sObject,
157 | Dictionary? customHeaders = null,
158 | CancellationToken cancellationToken = default);
159 |
160 | ///
161 | /// Retrieve records using a SOQL query.
162 | /// Will automatically retrieve the complete result set if split into batches. If
163 | /// you want to limit results, use the LIMIT operator in your query.
164 | ///
165 | ///
166 | /// SOQL query string, without any URL escaping/encoding.
167 | /// True if deleted records are to be included.
168 | ///
169 | ///
170 | Task> QueryAsync(
171 | string queryString,
172 | bool queryAll = false,
173 | CancellationToken cancellationToken = default);
174 |
175 | ///
176 | /// Retrieve a single record using a SOQL query.
177 | /// Will throw an exception if multiple rows are retrieved by the query - if you
178 | /// are note sure of a single result, use Query{T} instead.
179 | ///
180 | ///
181 | /// SOQL query string, without any URL escaping/encoding.
182 | /// True if deleted records are to be included.
183 | ///
184 | /// result object.
185 | Task QuerySingleAsync(
186 | string queryString,
187 | bool queryAll = false,
188 | CancellationToken cancellationToken = default);
189 |
190 | ///
191 | /// Executes a SOSL search, returning a type T, e.g. when using "RETURNING Account"
192 | /// in the SOSL query.
193 | /// Not properly matching the return type T and the RETURNING clause of the SOSL
194 | /// query may return unexpected results.
195 | ///
196 | ///
197 | ///
198 | ///
199 | /// SearchResult{T}.
200 | Task> SearchAsync(
201 | string searchString,
202 | CancellationToken cancellationToken = default);
203 |
204 | ///
205 | /// Does a basic test of the client's connection to the current Salesforce instance,
206 | /// and that the API is responding to requests.
207 | /// This does not validate authentication.
208 | /// Makes a call to the Versions resource, since it requires no authentication or
209 | /// permissions.
210 | ///
211 | ///
212 | ///
213 | /// True or false. Does not throw exceptions, only false in case of any errors.
214 | Task TestConnectionAsync(
215 | string? currentInstanceUrl = null,
216 | CancellationToken cancellationToken = default);
217 |
218 | ///
219 | /// Updates.
220 | ///
221 | ///
222 | /// SObject name, e.g. "Account".
223 | /// Id of Object to update.
224 | /// Object to update.
225 | ///
226 | /// Custom headers to include in request (Optional). await The HeaderFormatter helper
227 | /// class can be used to generate the custom header as needed.
228 | ///
229 | ///
230 | ///
231 | Task UpdateRecordAsync(
232 | string sObjectTypeName,
233 | string objectId,
234 | T sObject,
235 | Dictionary? customHeaders = null,
236 | CancellationToken cancellationToken = default);
237 | }
238 | }
239 |
--------------------------------------------------------------------------------
/src/AuthApp/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: https://EditorConfig.org
2 | # top-most EditorConfig file
3 | root = true
4 |
5 | # Unix-style newlines with a newline ending every file
6 | [*]
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | # Matches multiple files with brace expansion notation
12 | # Set default charset
13 | [*.js]
14 | charset = utf-8
15 |
16 | # Solution Files
17 | [*.sln]
18 | indent_style = tab
19 |
20 | # XML Project Files
21 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
22 | indent_size = 2
23 |
24 | # YAML Files
25 | [*.{yml,yaml}]
26 | indent_size = 2
27 | indent_style = space
28 |
29 | # Markdown Files
30 | [*.md]
31 | trim_trailing_whitespace = false
32 |
33 | # .NET Code Style Settings
34 | [*.{cs,csx,cake,vb}]
35 |
36 | [*.cshtml.cs]
37 | dotnet_diagnostic.SA1649.severity = none
38 |
39 | # "this." and "Me." qualifiers
40 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#this_and_me
41 | dotnet_style_qualification_for_field = false:warning
42 | dotnet_style_qualification_for_property = false:warning
43 | dotnet_style_qualification_for_method = false:warning
44 | dotnet_style_qualification_for_event = false:warning
45 |
46 | # Language keywords instead of framework type names for type references
47 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#language_keywords
48 | dotnet_style_predefined_type_for_locals_parameters_members = true:warning
49 | dotnet_style_predefined_type_for_member_access = true:warning
50 |
51 | # Modifier preferences
52 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#normalize_modifiers
53 | dotnet_style_require_accessibility_modifiers = always:warning
54 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:warning
55 | dotnet_style_readonly_field = true:warning
56 |
57 | # Code-block preferences
58 | csharp_prefer_braces = true:silent
59 | csharp_prefer_simple_using_statement = true:suggestion
60 |
61 | # Expression-level preferences
62 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#expression_level
63 | dotnet_style_object_initializer = true:warning
64 | dotnet_style_collection_initializer = true:warning
65 | dotnet_style_explicit_tuple_names = true:warning
66 | dotnet_style_prefer_inferred_tuple_names = true:warning
67 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
68 | dotnet_style_prefer_auto_properties = true:warning
69 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
70 |
71 | # 'using' directive preferences
72 | csharp_using_directive_placement = outside_namespace:silent
73 |
74 | # Null-checking preferences
75 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#null_checking
76 | dotnet_style_coalesce_expression = true:warning
77 | dotnet_style_null_propagation = true:warning
78 |
79 | # Other (Undocumented)
80 | dotnet_style_prefer_conditional_expression_over_return = false
81 | dotnet_style_prefer_conditional_expression_over_assignment = false
82 |
83 | # C# Code Style Settings
84 | [*.{cs,csx,cake}]
85 | # Implicit and explicit types
86 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#implicit-and-explicit-types
87 | csharp_style_var_for_built_in_types = true:warning
88 | csharp_style_var_when_type_is_apparent = true:warning
89 | csharp_style_var_elsewhere = true:warning
90 |
91 | # Expression-bodied members
92 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#expression_bodied_members
93 | csharp_style_expression_bodied_methods = false:warning
94 | csharp_style_expression_bodied_constructors = false:warning
95 | csharp_style_expression_bodied_operators = true:warning
96 | csharp_style_expression_bodied_properties = true:warning
97 | csharp_style_expression_bodied_indexers = true:warning
98 | csharp_style_expression_bodied_accessors = true:warning
99 |
100 | # Pattern matching
101 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#pattern_matching
102 | csharp_style_pattern_matching_over_is_with_cast_check = true:warning
103 | csharp_style_pattern_matching_over_as_with_null_check = true:warning
104 |
105 | # Inlined variable declarations
106 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#inlined_variable_declarations
107 | csharp_style_inlined_variable_declaration = true:warning
108 |
109 | # Expression-level preferences
110 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#expression_level_csharp
111 | csharp_prefer_simple_default_expression = true:warning
112 | csharp_style_deconstructed_variable_declaration = true:warning
113 | csharp_style_pattern_local_over_anonymous_function = true:warning
114 |
115 | # "Null" checking preferences
116 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#null_checking_csharp
117 | csharp_style_throw_expression = true:warning
118 | csharp_style_conditional_delegate_call = true:warning
119 |
120 | # Code block preferences
121 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#code_block
122 | csharp_prefer_braces = true:warning
123 |
124 | #############################
125 | # .NET Formatting Conventions
126 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#formatting-conventions
127 | #############################
128 |
129 | # Organize usings
130 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#usings
131 | dotnet_sort_system_directives_first = true
132 | dotnet_separate_import_directive_groups = true
133 |
134 | # C# formatting settings
135 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#c-formatting-settings
136 | csharp_new_line_before_open_brace = all
137 | csharp_new_line_before_else = true:warning
138 | csharp_new_line_before_catch = true:warning
139 | csharp_new_line_before_finally = true:warning
140 | csharp_new_line_before_members_in_object_initializers = true:warning
141 | csharp_new_line_before_members_in_anonymous_types = true:warning
142 | csharp_new_line_between_query_expression_clauses = true:warning
143 |
144 | # Indentation options
145 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#indent
146 | csharp_indent_case_contents = true:warning
147 | csharp_indent_switch_labels = true:warning
148 | csharp_indent_labels = no_change:warning
149 |
150 | # Spacing options
151 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#spacing
152 | csharp_space_after_cast = false:warning
153 | csharp_space_after_keywords_in_control_flow_statements = true:warning
154 | csharp_space_between_method_declaration_parameter_list_parentheses = false:warning
155 | csharp_space_between_method_call_parameter_list_parentheses = false:warning
156 | csharp_space_between_parentheses = expressions:warning
157 | csharp_space_before_colon_in_inheritance_clause = true:warning
158 | csharp_space_after_colon_in_inheritance_clause = true:warning
159 | csharp_space_around_binary_operators = before_and_after:warning
160 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false:warning
161 | csharp_space_between_method_call_name_and_opening_parenthesis = false:warning
162 | csharp_space_between_method_call_empty_parameter_list_parentheses = false:warning
163 |
164 | # Wrapping options
165 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#wrapping
166 | csharp_preserve_single_line_statements = false:warning
167 | csharp_preserve_single_line_blocks = true:warning
168 |
169 | # More Indentation options (Undocumented)
170 | csharp_indent_block_contents = true:warning
171 | csharp_indent_braces = false:warning
172 |
173 | # Spacing Options (Undocumented)
174 | csharp_space_after_comma = true:warning
175 | csharp_space_after_dot = false:warning
176 | csharp_space_after_semicolon_in_for_statement = true:warning
177 | csharp_space_around_declaration_statements = do_not_ignore:warning
178 | csharp_space_before_comma = false:warning
179 | csharp_space_before_dot = false:warning
180 | csharp_space_before_semicolon_in_for_statement = false:warning
181 | csharp_space_before_open_square_brackets = false:warning
182 | csharp_space_between_empty_square_brackets = false:warning
183 | csharp_space_between_method_declaration_name_and_open_parenthesis = false:warning
184 | csharp_space_between_square_brackets = false:warning
185 |
186 | #########################
187 | # .NET Naming conventions
188 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-naming-conventions
189 | #########################
190 |
191 | [*.{cs,csx,cake,vb}]
192 | # Naming Symbols
193 | # constant_fields - Define constant fields
194 | dotnet_naming_symbols.constant_fields.applicable_kinds = field
195 | dotnet_naming_symbols.constant_fields.required_modifiers = const
196 |
197 | # non_private_readonly_fields - Define public, internal and protected readonly fields
198 | dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, internal, protected
199 | dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field
200 | dotnet_naming_symbols.non_private_readonly_fields.required_modifiers = readonly
201 |
202 | # static_readonly_fields - Define static and readonly fields
203 | dotnet_naming_symbols.static_readonly_fields.applicable_kinds = field
204 | dotnet_naming_symbols.static_readonly_fields.required_modifiers = static, readonly
205 |
206 | # private_readonly_fields - Define private readonly fields
207 | dotnet_naming_symbols.private_readonly_fields.applicable_accessibilities = private
208 | dotnet_naming_symbols.private_readonly_fields.applicable_kinds = field
209 | dotnet_naming_symbols.private_readonly_fields.required_modifiers = readonly
210 |
211 | # public_internal_fields - Define public and internal fields
212 | dotnet_naming_symbols.public_internal_fields.applicable_accessibilities = public, internal
213 | dotnet_naming_symbols.public_internal_fields.applicable_kinds = field
214 |
215 | # private_protected_fields - Define private and protected fields
216 | dotnet_naming_symbols.private_protected_fields.applicable_accessibilities = private, protected
217 | dotnet_naming_symbols.private_protected_fields.applicable_kinds = field
218 |
219 | # public_symbols - Define any public symbol
220 | dotnet_naming_symbols.public_symbols.applicable_accessibilities = public, internal, protected, protected_internal
221 | dotnet_naming_symbols.public_symbols.applicable_kinds = method, property, event, delegate
222 |
223 | # parameters - Defines any parameter
224 | dotnet_naming_symbols.parameters.applicable_kinds = parameter
225 |
226 | # non_interface_types - Defines class, struct, enum and delegate types
227 | dotnet_naming_symbols.non_interface_types.applicable_kinds = class, struct, enum, delegate
228 |
229 | # interface_types - Defines interfaces
230 | dotnet_naming_symbols.interface_types.applicable_kinds = interface
231 |
232 | #########################
233 | # Naming Styles
234 | #########################
235 |
236 | # camel_case - Define the camelCase style
237 | dotnet_naming_style.camel_case.capitalization = camel_case
238 |
239 | # pascal_case - Define the Pascal_case style
240 | dotnet_naming_style.pascal_case.capitalization = pascal_case
241 |
242 | # first_upper - The first character must start with an upper-case character
243 | dotnet_naming_style.first_upper.capitalization = first_word_upper
244 |
245 | # prefix_interface_interface_with_i - Interfaces must be PascalCase and the first character of an interface must be an 'I'
246 | dotnet_naming_style.prefix_interface_interface_with_i.capitalization = pascal_case
247 | dotnet_naming_style.prefix_interface_interface_with_i.required_prefix = I
248 |
249 | #########################
250 | # Naming Rules
251 | #########################
252 |
253 | # Constant fields must be PascalCase
254 | dotnet_naming_rule.constant_fields_must_be_pascal_case.severity = warning
255 | dotnet_naming_rule.constant_fields_must_be_pascal_case.symbols = constant_fields
256 | dotnet_naming_rule.constant_fields_must_be_pascal_case.style = pascal_case
257 |
258 | # Public, internal and protected readonly fields must be PascalCase
259 | dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.severity = warning
260 | dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.symbols = non_private_readonly_fields
261 | dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.style = pascal_case
262 |
263 | # Static readonly fields must be PascalCase
264 | dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.severity = warning
265 | dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.symbols = static_readonly_fields
266 | dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.style = pascal_case
267 |
268 | # Public and internal fields must be PascalCase
269 | dotnet_naming_rule.public_internal_fields_must_be_pascal_case.severity = warning
270 | dotnet_naming_rule.public_internal_fields_must_be_pascal_case.symbols = public_internal_fields
271 | dotnet_naming_rule.public_internal_fields_must_be_pascal_case.style = pascal_case
272 |
273 | # Public members must be capitalized
274 | dotnet_naming_rule.public_members_must_be_capitalized.severity = warning
275 | dotnet_naming_rule.public_members_must_be_capitalized.symbols = public_symbols
276 | dotnet_naming_rule.public_members_must_be_capitalized.style = first_upper
277 |
278 | # Parameters must be camelCase
279 | dotnet_naming_rule.parameters_must_be_camel_case.severity = warning
280 | dotnet_naming_rule.parameters_must_be_camel_case.symbols = parameters
281 | dotnet_naming_rule.parameters_must_be_camel_case.style = camel_case
282 |
283 | # Class, struct, enum and delegates must be PascalCase
284 | dotnet_naming_rule.non_interface_types_must_be_pascal_case.severity = warning
285 | dotnet_naming_rule.non_interface_types_must_be_pascal_case.symbols = non_interface_types
286 | dotnet_naming_rule.non_interface_types_must_be_pascal_case.style = pascal_case
287 |
288 | # Interfaces must be PascalCase and start with an 'I'
289 | dotnet_naming_rule.interface_types_must_be_prefixed_with_i.severity = warning
290 | dotnet_naming_rule.interface_types_must_be_prefixed_with_i.symbols = interface_types
291 | dotnet_naming_rule.interface_types_must_be_prefixed_with_i.style = prefix_interface_interface_with_i
292 |
293 | dotnet_naming_symbols.private_fields.applicable_kinds = field
294 | dotnet_naming_symbols.private_fields.applicable_accessibilities = private, internal
295 |
296 | # internal and private fields should be _camelCase
297 | dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
298 | dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields
299 | dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style
300 |
301 | dotnet_naming_symbols.private_internal_fields.applicable_kinds = field
302 | dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal, protected
303 |
304 | dotnet_naming_style.camel_case_underscore_style.required_prefix = _
305 | dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case
306 |
307 |
308 | [*.cs]
309 |
310 | # CA1815: Override equals and operator equals on value types
311 | dotnet_diagnostic.CA1815.severity = silent
312 |
--------------------------------------------------------------------------------
/src/TestApp/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: https://EditorConfig.org
2 | # top-most EditorConfig file
3 | root = true
4 |
5 | # Unix-style newlines with a newline ending every file
6 | [*]
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | # Matches multiple files with brace expansion notation
12 | # Set default charset
13 | [*.js]
14 | charset = utf-8
15 |
16 | # Solution Files
17 | [*.sln]
18 | indent_style = tab
19 |
20 | # XML Project Files
21 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
22 | indent_size = 2
23 |
24 | # YAML Files
25 | [*.{yml,yaml}]
26 | indent_size = 2
27 | indent_style = space
28 |
29 | # Markdown Files
30 | [*.md]
31 | trim_trailing_whitespace = false
32 |
33 | # .NET Code Style Settings
34 | [*.{cs,csx,cake,vb}]
35 |
36 | [*.cshtml.cs]
37 | dotnet_diagnostic.SA1649.severity = none
38 |
39 | # "this." and "Me." qualifiers
40 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#this_and_me
41 | dotnet_style_qualification_for_field = false:warning
42 | dotnet_style_qualification_for_property = false:warning
43 | dotnet_style_qualification_for_method = false:warning
44 | dotnet_style_qualification_for_event = false:warning
45 |
46 | # Language keywords instead of framework type names for type references
47 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#language_keywords
48 | dotnet_style_predefined_type_for_locals_parameters_members = true:warning
49 | dotnet_style_predefined_type_for_member_access = true:warning
50 |
51 | # Modifier preferences
52 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#normalize_modifiers
53 | dotnet_style_require_accessibility_modifiers = always:warning
54 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:warning
55 | dotnet_style_readonly_field = true:warning
56 |
57 | # Code-block preferences
58 | csharp_prefer_braces = true:silent
59 | csharp_prefer_simple_using_statement = true:suggestion
60 |
61 | # Expression-level preferences
62 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#expression_level
63 | dotnet_style_object_initializer = true:warning
64 | dotnet_style_collection_initializer = true:warning
65 | dotnet_style_explicit_tuple_names = true:warning
66 | dotnet_style_prefer_inferred_tuple_names = true:warning
67 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
68 | dotnet_style_prefer_auto_properties = true:warning
69 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
70 |
71 | # 'using' directive preferences
72 | csharp_using_directive_placement = outside_namespace:silent
73 |
74 | # Null-checking preferences
75 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#null_checking
76 | dotnet_style_coalesce_expression = true:warning
77 | dotnet_style_null_propagation = true:warning
78 |
79 | # Other (Undocumented)
80 | dotnet_style_prefer_conditional_expression_over_return = false
81 | dotnet_style_prefer_conditional_expression_over_assignment = false
82 |
83 | # C# Code Style Settings
84 | [*.{cs,csx,cake}]
85 | # Implicit and explicit types
86 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#implicit-and-explicit-types
87 | csharp_style_var_for_built_in_types = true:warning
88 | csharp_style_var_when_type_is_apparent = true:warning
89 | csharp_style_var_elsewhere = true:warning
90 |
91 | # Expression-bodied members
92 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#expression_bodied_members
93 | csharp_style_expression_bodied_methods = false:warning
94 | csharp_style_expression_bodied_constructors = false:warning
95 | csharp_style_expression_bodied_operators = true:warning
96 | csharp_style_expression_bodied_properties = true:warning
97 | csharp_style_expression_bodied_indexers = true:warning
98 | csharp_style_expression_bodied_accessors = true:warning
99 |
100 | # Pattern matching
101 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#pattern_matching
102 | csharp_style_pattern_matching_over_is_with_cast_check = true:warning
103 | csharp_style_pattern_matching_over_as_with_null_check = true:warning
104 |
105 | # Inlined variable declarations
106 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#inlined_variable_declarations
107 | csharp_style_inlined_variable_declaration = true:warning
108 |
109 | # Expression-level preferences
110 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#expression_level_csharp
111 | csharp_prefer_simple_default_expression = true:warning
112 | csharp_style_deconstructed_variable_declaration = true:warning
113 | csharp_style_pattern_local_over_anonymous_function = true:warning
114 |
115 | # "Null" checking preferences
116 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#null_checking_csharp
117 | csharp_style_throw_expression = true:warning
118 | csharp_style_conditional_delegate_call = true:warning
119 |
120 | # Code block preferences
121 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#code_block
122 | csharp_prefer_braces = true:warning
123 |
124 | #############################
125 | # .NET Formatting Conventions
126 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#formatting-conventions
127 | #############################
128 |
129 | # Organize usings
130 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#usings
131 | dotnet_sort_system_directives_first = true
132 | dotnet_separate_import_directive_groups = true
133 |
134 | # C# formatting settings
135 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#c-formatting-settings
136 | csharp_new_line_before_open_brace = all
137 | csharp_new_line_before_else = true:warning
138 | csharp_new_line_before_catch = true:warning
139 | csharp_new_line_before_finally = true:warning
140 | csharp_new_line_before_members_in_object_initializers = true:warning
141 | csharp_new_line_before_members_in_anonymous_types = true:warning
142 | csharp_new_line_between_query_expression_clauses = true:warning
143 |
144 | # Indentation options
145 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#indent
146 | csharp_indent_case_contents = true:warning
147 | csharp_indent_switch_labels = true:warning
148 | csharp_indent_labels = no_change:warning
149 |
150 | # Spacing options
151 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#spacing
152 | csharp_space_after_cast = false:warning
153 | csharp_space_after_keywords_in_control_flow_statements = true:warning
154 | csharp_space_between_method_declaration_parameter_list_parentheses = false:warning
155 | csharp_space_between_method_call_parameter_list_parentheses = false:warning
156 | csharp_space_between_parentheses = expressions:warning
157 | csharp_space_before_colon_in_inheritance_clause = true:warning
158 | csharp_space_after_colon_in_inheritance_clause = true:warning
159 | csharp_space_around_binary_operators = before_and_after:warning
160 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false:warning
161 | csharp_space_between_method_call_name_and_opening_parenthesis = false:warning
162 | csharp_space_between_method_call_empty_parameter_list_parentheses = false:warning
163 |
164 | # Wrapping options
165 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#wrapping
166 | csharp_preserve_single_line_statements = false:warning
167 | csharp_preserve_single_line_blocks = true:warning
168 |
169 | # More Indentation options (Undocumented)
170 | csharp_indent_block_contents = true:warning
171 | csharp_indent_braces = false:warning
172 |
173 | # Spacing Options (Undocumented)
174 | csharp_space_after_comma = true:warning
175 | csharp_space_after_dot = false:warning
176 | csharp_space_after_semicolon_in_for_statement = true:warning
177 | csharp_space_around_declaration_statements = do_not_ignore:warning
178 | csharp_space_before_comma = false:warning
179 | csharp_space_before_dot = false:warning
180 | csharp_space_before_semicolon_in_for_statement = false:warning
181 | csharp_space_before_open_square_brackets = false:warning
182 | csharp_space_between_empty_square_brackets = false:warning
183 | csharp_space_between_method_declaration_name_and_open_parenthesis = false:warning
184 | csharp_space_between_square_brackets = false:warning
185 |
186 | #########################
187 | # .NET Naming conventions
188 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-naming-conventions
189 | #########################
190 |
191 | [*.{cs,csx,cake,vb}]
192 | # Naming Symbols
193 | # constant_fields - Define constant fields
194 | dotnet_naming_symbols.constant_fields.applicable_kinds = field
195 | dotnet_naming_symbols.constant_fields.required_modifiers = const
196 |
197 | # non_private_readonly_fields - Define public, internal and protected readonly fields
198 | dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, internal, protected
199 | dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field
200 | dotnet_naming_symbols.non_private_readonly_fields.required_modifiers = readonly
201 |
202 | # static_readonly_fields - Define static and readonly fields
203 | dotnet_naming_symbols.static_readonly_fields.applicable_kinds = field
204 | dotnet_naming_symbols.static_readonly_fields.required_modifiers = static, readonly
205 |
206 | # private_readonly_fields - Define private readonly fields
207 | dotnet_naming_symbols.private_readonly_fields.applicable_accessibilities = private
208 | dotnet_naming_symbols.private_readonly_fields.applicable_kinds = field
209 | dotnet_naming_symbols.private_readonly_fields.required_modifiers = readonly
210 |
211 | # public_internal_fields - Define public and internal fields
212 | dotnet_naming_symbols.public_internal_fields.applicable_accessibilities = public, internal
213 | dotnet_naming_symbols.public_internal_fields.applicable_kinds = field
214 |
215 | # private_protected_fields - Define private and protected fields
216 | dotnet_naming_symbols.private_protected_fields.applicable_accessibilities = private, protected
217 | dotnet_naming_symbols.private_protected_fields.applicable_kinds = field
218 |
219 | # public_symbols - Define any public symbol
220 | dotnet_naming_symbols.public_symbols.applicable_accessibilities = public, internal, protected, protected_internal
221 | dotnet_naming_symbols.public_symbols.applicable_kinds = method, property, event, delegate
222 |
223 | # parameters - Defines any parameter
224 | dotnet_naming_symbols.parameters.applicable_kinds = parameter
225 |
226 | # non_interface_types - Defines class, struct, enum and delegate types
227 | dotnet_naming_symbols.non_interface_types.applicable_kinds = class, struct, enum, delegate
228 |
229 | # interface_types - Defines interfaces
230 | dotnet_naming_symbols.interface_types.applicable_kinds = interface
231 |
232 | #########################
233 | # Naming Styles
234 | #########################
235 |
236 | # camel_case - Define the camelCase style
237 | dotnet_naming_style.camel_case.capitalization = camel_case
238 |
239 | # pascal_case - Define the Pascal_case style
240 | dotnet_naming_style.pascal_case.capitalization = pascal_case
241 |
242 | # first_upper - The first character must start with an upper-case character
243 | dotnet_naming_style.first_upper.capitalization = first_word_upper
244 |
245 | # prefix_interface_interface_with_i - Interfaces must be PascalCase and the first character of an interface must be an 'I'
246 | dotnet_naming_style.prefix_interface_interface_with_i.capitalization = pascal_case
247 | dotnet_naming_style.prefix_interface_interface_with_i.required_prefix = I
248 |
249 | #########################
250 | # Naming Rules
251 | #########################
252 |
253 | # Constant fields must be PascalCase
254 | dotnet_naming_rule.constant_fields_must_be_pascal_case.severity = warning
255 | dotnet_naming_rule.constant_fields_must_be_pascal_case.symbols = constant_fields
256 | dotnet_naming_rule.constant_fields_must_be_pascal_case.style = pascal_case
257 |
258 | # Public, internal and protected readonly fields must be PascalCase
259 | dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.severity = warning
260 | dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.symbols = non_private_readonly_fields
261 | dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.style = pascal_case
262 |
263 | # Static readonly fields must be PascalCase
264 | dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.severity = warning
265 | dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.symbols = static_readonly_fields
266 | dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.style = pascal_case
267 |
268 | # Public and internal fields must be PascalCase
269 | dotnet_naming_rule.public_internal_fields_must_be_pascal_case.severity = warning
270 | dotnet_naming_rule.public_internal_fields_must_be_pascal_case.symbols = public_internal_fields
271 | dotnet_naming_rule.public_internal_fields_must_be_pascal_case.style = pascal_case
272 |
273 | # Public members must be capitalized
274 | dotnet_naming_rule.public_members_must_be_capitalized.severity = warning
275 | dotnet_naming_rule.public_members_must_be_capitalized.symbols = public_symbols
276 | dotnet_naming_rule.public_members_must_be_capitalized.style = first_upper
277 |
278 | # Parameters must be camelCase
279 | dotnet_naming_rule.parameters_must_be_camel_case.severity = warning
280 | dotnet_naming_rule.parameters_must_be_camel_case.symbols = parameters
281 | dotnet_naming_rule.parameters_must_be_camel_case.style = camel_case
282 |
283 | # Class, struct, enum and delegates must be PascalCase
284 | dotnet_naming_rule.non_interface_types_must_be_pascal_case.severity = warning
285 | dotnet_naming_rule.non_interface_types_must_be_pascal_case.symbols = non_interface_types
286 | dotnet_naming_rule.non_interface_types_must_be_pascal_case.style = pascal_case
287 |
288 | # Interfaces must be PascalCase and start with an 'I'
289 | dotnet_naming_rule.interface_types_must_be_prefixed_with_i.severity = warning
290 | dotnet_naming_rule.interface_types_must_be_prefixed_with_i.symbols = interface_types
291 | dotnet_naming_rule.interface_types_must_be_prefixed_with_i.style = prefix_interface_interface_with_i
292 |
293 | dotnet_naming_symbols.private_fields.applicable_kinds = field
294 | dotnet_naming_symbols.private_fields.applicable_accessibilities = private, internal
295 |
296 | # internal and private fields should be _camelCase
297 | dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
298 | dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields
299 | dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style
300 |
301 | dotnet_naming_symbols.private_internal_fields.applicable_kinds = field
302 | dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal, protected
303 |
304 | dotnet_naming_style.camel_case_underscore_style.required_prefix = _
305 | dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case
306 |
307 |
308 | [*.cs]
309 |
310 | # CA1815: Override equals and operator equals on value types
311 | dotnet_diagnostic.CA1815.severity = silent
312 |
--------------------------------------------------------------------------------
/test/CometD.UnitTest/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: https://EditorConfig.org
2 | # top-most EditorConfig file
3 | root = true
4 |
5 | # Unix-style newlines with a newline ending every file
6 | [*]
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | # Matches multiple files with brace expansion notation
12 | # Set default charset
13 | [*.js]
14 | charset = utf-8
15 |
16 | # Solution Files
17 | [*.sln]
18 | indent_style = tab
19 |
20 | # XML Project Files
21 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
22 | indent_size = 2
23 |
24 | # YAML Files
25 | [*.{yml,yaml}]
26 | indent_size = 2
27 | indent_style = space
28 |
29 | # Markdown Files
30 | [*.md]
31 | trim_trailing_whitespace = false
32 |
33 | # .NET Code Style Settings
34 | [*.{cs,csx,cake,vb}]
35 |
36 | [*.cshtml.cs]
37 | dotnet_diagnostic.SA1649.severity = none
38 |
39 | # "this." and "Me." qualifiers
40 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#this_and_me
41 | dotnet_style_qualification_for_field = false:warning
42 | dotnet_style_qualification_for_property = false:warning
43 | dotnet_style_qualification_for_method = false:warning
44 | dotnet_style_qualification_for_event = false:warning
45 |
46 | # Language keywords instead of framework type names for type references
47 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#language_keywords
48 | dotnet_style_predefined_type_for_locals_parameters_members = true:warning
49 | dotnet_style_predefined_type_for_member_access = true:warning
50 |
51 | # Modifier preferences
52 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#normalize_modifiers
53 | dotnet_style_require_accessibility_modifiers = always:warning
54 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:warning
55 | dotnet_style_readonly_field = true:warning
56 |
57 | # Code-block preferences
58 | csharp_prefer_braces = true:silent
59 | csharp_prefer_simple_using_statement = true:suggestion
60 |
61 | # Expression-level preferences
62 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#expression_level
63 | dotnet_style_object_initializer = true:warning
64 | dotnet_style_collection_initializer = true:warning
65 | dotnet_style_explicit_tuple_names = true:warning
66 | dotnet_style_prefer_inferred_tuple_names = true:warning
67 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
68 | dotnet_style_prefer_auto_properties = true:warning
69 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
70 |
71 | # 'using' directive preferences
72 | csharp_using_directive_placement = outside_namespace:silent
73 |
74 | # Null-checking preferences
75 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#null_checking
76 | dotnet_style_coalesce_expression = true:warning
77 | dotnet_style_null_propagation = true:warning
78 |
79 | # Other (Undocumented)
80 | dotnet_style_prefer_conditional_expression_over_return = false
81 | dotnet_style_prefer_conditional_expression_over_assignment = false
82 |
83 | # C# Code Style Settings
84 | [*.{cs,csx,cake}]
85 | # Implicit and explicit types
86 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#implicit-and-explicit-types
87 | csharp_style_var_for_built_in_types = true:warning
88 | csharp_style_var_when_type_is_apparent = true:warning
89 | csharp_style_var_elsewhere = true:warning
90 |
91 | # Expression-bodied members
92 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#expression_bodied_members
93 | csharp_style_expression_bodied_methods = false:warning
94 | csharp_style_expression_bodied_constructors = false:warning
95 | csharp_style_expression_bodied_operators = true:warning
96 | csharp_style_expression_bodied_properties = true:warning
97 | csharp_style_expression_bodied_indexers = true:warning
98 | csharp_style_expression_bodied_accessors = true:warning
99 |
100 | # Pattern matching
101 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#pattern_matching
102 | csharp_style_pattern_matching_over_is_with_cast_check = true:warning
103 | csharp_style_pattern_matching_over_as_with_null_check = true:warning
104 |
105 | # Inlined variable declarations
106 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#inlined_variable_declarations
107 | csharp_style_inlined_variable_declaration = true:warning
108 |
109 | # Expression-level preferences
110 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#expression_level_csharp
111 | csharp_prefer_simple_default_expression = true:warning
112 | csharp_style_deconstructed_variable_declaration = true:warning
113 | csharp_style_pattern_local_over_anonymous_function = true:warning
114 |
115 | # "Null" checking preferences
116 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#null_checking_csharp
117 | csharp_style_throw_expression = true:warning
118 | csharp_style_conditional_delegate_call = true:warning
119 |
120 | # Code block preferences
121 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#code_block
122 | csharp_prefer_braces = true:warning
123 |
124 | #############################
125 | # .NET Formatting Conventions
126 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#formatting-conventions
127 | #############################
128 |
129 | # Organize usings
130 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#usings
131 | dotnet_sort_system_directives_first = true
132 | dotnet_separate_import_directive_groups = true
133 |
134 | # C# formatting settings
135 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#c-formatting-settings
136 | csharp_new_line_before_open_brace = all
137 | csharp_new_line_before_else = true:warning
138 | csharp_new_line_before_catch = true:warning
139 | csharp_new_line_before_finally = true:warning
140 | csharp_new_line_before_members_in_object_initializers = true:warning
141 | csharp_new_line_before_members_in_anonymous_types = true:warning
142 | csharp_new_line_between_query_expression_clauses = true:warning
143 |
144 | # Indentation options
145 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#indent
146 | csharp_indent_case_contents = true:warning
147 | csharp_indent_switch_labels = true:warning
148 | csharp_indent_labels = no_change:warning
149 |
150 | # Spacing options
151 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#spacing
152 | csharp_space_after_cast = false:warning
153 | csharp_space_after_keywords_in_control_flow_statements = true:warning
154 | csharp_space_between_method_declaration_parameter_list_parentheses = false:warning
155 | csharp_space_between_method_call_parameter_list_parentheses = false:warning
156 | csharp_space_between_parentheses = expressions:warning
157 | csharp_space_before_colon_in_inheritance_clause = true:warning
158 | csharp_space_after_colon_in_inheritance_clause = true:warning
159 | csharp_space_around_binary_operators = before_and_after:warning
160 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false:warning
161 | csharp_space_between_method_call_name_and_opening_parenthesis = false:warning
162 | csharp_space_between_method_call_empty_parameter_list_parentheses = false:warning
163 |
164 | # Wrapping options
165 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#wrapping
166 | csharp_preserve_single_line_statements = false:warning
167 | csharp_preserve_single_line_blocks = true:warning
168 |
169 | # More Indentation options (Undocumented)
170 | csharp_indent_block_contents = true:warning
171 | csharp_indent_braces = false:warning
172 |
173 | # Spacing Options (Undocumented)
174 | csharp_space_after_comma = true:warning
175 | csharp_space_after_dot = false:warning
176 | csharp_space_after_semicolon_in_for_statement = true:warning
177 | csharp_space_around_declaration_statements = do_not_ignore:warning
178 | csharp_space_before_comma = false:warning
179 | csharp_space_before_dot = false:warning
180 | csharp_space_before_semicolon_in_for_statement = false:warning
181 | csharp_space_before_open_square_brackets = false:warning
182 | csharp_space_between_empty_square_brackets = false:warning
183 | csharp_space_between_method_declaration_name_and_open_parenthesis = false:warning
184 | csharp_space_between_square_brackets = false:warning
185 |
186 | #########################
187 | # .NET Naming conventions
188 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-naming-conventions
189 | #########################
190 |
191 | [*.{cs,csx,cake,vb}]
192 | # Naming Symbols
193 | # constant_fields - Define constant fields
194 | dotnet_naming_symbols.constant_fields.applicable_kinds = field
195 | dotnet_naming_symbols.constant_fields.required_modifiers = const
196 |
197 | # non_private_readonly_fields - Define public, internal and protected readonly fields
198 | dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, internal, protected
199 | dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field
200 | dotnet_naming_symbols.non_private_readonly_fields.required_modifiers = readonly
201 |
202 | # static_readonly_fields - Define static and readonly fields
203 | dotnet_naming_symbols.static_readonly_fields.applicable_kinds = field
204 | dotnet_naming_symbols.static_readonly_fields.required_modifiers = static, readonly
205 |
206 | # private_readonly_fields - Define private readonly fields
207 | dotnet_naming_symbols.private_readonly_fields.applicable_accessibilities = private
208 | dotnet_naming_symbols.private_readonly_fields.applicable_kinds = field
209 | dotnet_naming_symbols.private_readonly_fields.required_modifiers = readonly
210 |
211 | # public_internal_fields - Define public and internal fields
212 | dotnet_naming_symbols.public_internal_fields.applicable_accessibilities = public, internal
213 | dotnet_naming_symbols.public_internal_fields.applicable_kinds = field
214 |
215 | # private_protected_fields - Define private and protected fields
216 | dotnet_naming_symbols.private_protected_fields.applicable_accessibilities = private, protected
217 | dotnet_naming_symbols.private_protected_fields.applicable_kinds = field
218 |
219 | # public_symbols - Define any public symbol
220 | dotnet_naming_symbols.public_symbols.applicable_accessibilities = public, internal, protected, protected_internal
221 | dotnet_naming_symbols.public_symbols.applicable_kinds = method, property, event, delegate
222 |
223 | # parameters - Defines any parameter
224 | dotnet_naming_symbols.parameters.applicable_kinds = parameter
225 |
226 | # non_interface_types - Defines class, struct, enum and delegate types
227 | dotnet_naming_symbols.non_interface_types.applicable_kinds = class, struct, enum, delegate
228 |
229 | # interface_types - Defines interfaces
230 | dotnet_naming_symbols.interface_types.applicable_kinds = interface
231 |
232 | #########################
233 | # Naming Styles
234 | #########################
235 |
236 | # camel_case - Define the camelCase style
237 | dotnet_naming_style.camel_case.capitalization = camel_case
238 |
239 | # pascal_case - Define the Pascal_case style
240 | dotnet_naming_style.pascal_case.capitalization = pascal_case
241 |
242 | # first_upper - The first character must start with an upper-case character
243 | dotnet_naming_style.first_upper.capitalization = first_word_upper
244 |
245 | # prefix_interface_interface_with_i - Interfaces must be PascalCase and the first character of an interface must be an 'I'
246 | dotnet_naming_style.prefix_interface_interface_with_i.capitalization = pascal_case
247 | dotnet_naming_style.prefix_interface_interface_with_i.required_prefix = I
248 |
249 | #########################
250 | # Naming Rules
251 | #########################
252 |
253 | # Constant fields must be PascalCase
254 | dotnet_naming_rule.constant_fields_must_be_pascal_case.severity = warning
255 | dotnet_naming_rule.constant_fields_must_be_pascal_case.symbols = constant_fields
256 | dotnet_naming_rule.constant_fields_must_be_pascal_case.style = pascal_case
257 |
258 | # Public, internal and protected readonly fields must be PascalCase
259 | dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.severity = warning
260 | dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.symbols = non_private_readonly_fields
261 | dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.style = pascal_case
262 |
263 | # Static readonly fields must be PascalCase
264 | dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.severity = warning
265 | dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.symbols = static_readonly_fields
266 | dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.style = pascal_case
267 |
268 | # Public and internal fields must be PascalCase
269 | dotnet_naming_rule.public_internal_fields_must_be_pascal_case.severity = warning
270 | dotnet_naming_rule.public_internal_fields_must_be_pascal_case.symbols = public_internal_fields
271 | dotnet_naming_rule.public_internal_fields_must_be_pascal_case.style = pascal_case
272 |
273 | # Public members must be capitalized
274 | dotnet_naming_rule.public_members_must_be_capitalized.severity = warning
275 | dotnet_naming_rule.public_members_must_be_capitalized.symbols = public_symbols
276 | dotnet_naming_rule.public_members_must_be_capitalized.style = first_upper
277 |
278 | # Parameters must be camelCase
279 | dotnet_naming_rule.parameters_must_be_camel_case.severity = warning
280 | dotnet_naming_rule.parameters_must_be_camel_case.symbols = parameters
281 | dotnet_naming_rule.parameters_must_be_camel_case.style = camel_case
282 |
283 | # Class, struct, enum and delegates must be PascalCase
284 | dotnet_naming_rule.non_interface_types_must_be_pascal_case.severity = warning
285 | dotnet_naming_rule.non_interface_types_must_be_pascal_case.symbols = non_interface_types
286 | dotnet_naming_rule.non_interface_types_must_be_pascal_case.style = pascal_case
287 |
288 | # Interfaces must be PascalCase and start with an 'I'
289 | dotnet_naming_rule.interface_types_must_be_prefixed_with_i.severity = warning
290 | dotnet_naming_rule.interface_types_must_be_prefixed_with_i.symbols = interface_types
291 | dotnet_naming_rule.interface_types_must_be_prefixed_with_i.style = prefix_interface_interface_with_i
292 |
293 | dotnet_naming_symbols.private_fields.applicable_kinds = field
294 | dotnet_naming_symbols.private_fields.applicable_accessibilities = private, internal
295 |
296 | # internal and private fields should be _camelCase
297 | dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
298 | dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields
299 | dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style
300 |
301 | dotnet_naming_symbols.private_internal_fields.applicable_kinds = field
302 | dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal, protected
303 |
304 | dotnet_naming_style.camel_case_underscore_style.required_prefix = _
305 | dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case
306 |
307 |
308 | [*.cs]
309 |
310 | # CA1815: Override equals and operator equals on value types
311 | dotnet_diagnostic.CA1815.severity = silent
312 |
--------------------------------------------------------------------------------
/src/CometD.NetCore.Salesforce/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: https://EditorConfig.org
2 | # top-most EditorConfig file
3 | root = true
4 |
5 | # Unix-style newlines with a newline ending every file
6 | [*]
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | # Matches multiple files with brace expansion notation
12 | # Set default charset
13 | [*.js]
14 | charset = utf-8
15 |
16 | # Solution Files
17 | [*.sln]
18 | indent_style = tab
19 |
20 | # XML Project Files
21 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
22 | indent_size = 2
23 |
24 | # YAML Files
25 | [*.{yml,yaml}]
26 | indent_size = 2
27 | indent_style = space
28 |
29 | # Markdown Files
30 | [*.md]
31 | trim_trailing_whitespace = false
32 |
33 | # .NET Code Style Settings
34 | [*.{cs,csx,cake,vb}]
35 |
36 | [*.cshtml.cs]
37 | dotnet_diagnostic.SA1649.severity = none
38 |
39 | # "this." and "Me." qualifiers
40 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#this_and_me
41 | dotnet_style_qualification_for_field = false:warning
42 | dotnet_style_qualification_for_property = false:warning
43 | dotnet_style_qualification_for_method = false:warning
44 | dotnet_style_qualification_for_event = false:warning
45 |
46 | # Language keywords instead of framework type names for type references
47 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#language_keywords
48 | dotnet_style_predefined_type_for_locals_parameters_members = true:warning
49 | dotnet_style_predefined_type_for_member_access = true:warning
50 |
51 | # Modifier preferences
52 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#normalize_modifiers
53 | dotnet_style_require_accessibility_modifiers = always:warning
54 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:warning
55 | dotnet_style_readonly_field = true:warning
56 |
57 | # Code-block preferences
58 | csharp_prefer_braces = true:silent
59 | csharp_prefer_simple_using_statement = true:suggestion
60 |
61 | # Expression-level preferences
62 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#expression_level
63 | dotnet_style_object_initializer = true:warning
64 | dotnet_style_collection_initializer = true:warning
65 | dotnet_style_explicit_tuple_names = true:warning
66 | dotnet_style_prefer_inferred_tuple_names = true:warning
67 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
68 | dotnet_style_prefer_auto_properties = true:warning
69 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
70 |
71 | # 'using' directive preferences
72 | csharp_using_directive_placement = outside_namespace:silent
73 |
74 | # Null-checking preferences
75 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#null_checking
76 | dotnet_style_coalesce_expression = true:warning
77 | dotnet_style_null_propagation = true:warning
78 |
79 | # Other (Undocumented)
80 | dotnet_style_prefer_conditional_expression_over_return = false
81 | dotnet_style_prefer_conditional_expression_over_assignment = false
82 |
83 | # C# Code Style Settings
84 | [*.{cs,csx,cake}]
85 | # Implicit and explicit types
86 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#implicit-and-explicit-types
87 | csharp_style_var_for_built_in_types = true:warning
88 | csharp_style_var_when_type_is_apparent = true:warning
89 | csharp_style_var_elsewhere = true:warning
90 |
91 | # Expression-bodied members
92 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#expression_bodied_members
93 | csharp_style_expression_bodied_methods = false:warning
94 | csharp_style_expression_bodied_constructors = false:warning
95 | csharp_style_expression_bodied_operators = true:warning
96 | csharp_style_expression_bodied_properties = true:warning
97 | csharp_style_expression_bodied_indexers = true:warning
98 | csharp_style_expression_bodied_accessors = true:warning
99 |
100 | # Pattern matching
101 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#pattern_matching
102 | csharp_style_pattern_matching_over_is_with_cast_check = true:warning
103 | csharp_style_pattern_matching_over_as_with_null_check = true:warning
104 |
105 | # Inlined variable declarations
106 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#inlined_variable_declarations
107 | csharp_style_inlined_variable_declaration = true:warning
108 |
109 | # Expression-level preferences
110 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#expression_level_csharp
111 | csharp_prefer_simple_default_expression = true:warning
112 | csharp_style_deconstructed_variable_declaration = true:warning
113 | csharp_style_pattern_local_over_anonymous_function = true:warning
114 |
115 | # "Null" checking preferences
116 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#null_checking_csharp
117 | csharp_style_throw_expression = true:warning
118 | csharp_style_conditional_delegate_call = true:warning
119 |
120 | # Code block preferences
121 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#code_block
122 | csharp_prefer_braces = true:warning
123 |
124 | #############################
125 | # .NET Formatting Conventions
126 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#formatting-conventions
127 | #############################
128 |
129 | # Organize usings
130 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#usings
131 | dotnet_sort_system_directives_first = true
132 | dotnet_separate_import_directive_groups = true
133 |
134 | # C# formatting settings
135 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#c-formatting-settings
136 | csharp_new_line_before_open_brace = all
137 | csharp_new_line_before_else = true:warning
138 | csharp_new_line_before_catch = true:warning
139 | csharp_new_line_before_finally = true:warning
140 | csharp_new_line_before_members_in_object_initializers = true:warning
141 | csharp_new_line_before_members_in_anonymous_types = true:warning
142 | csharp_new_line_between_query_expression_clauses = true:warning
143 |
144 | # Indentation options
145 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#indent
146 | csharp_indent_case_contents = true:warning
147 | csharp_indent_switch_labels = true:warning
148 | csharp_indent_labels = no_change:warning
149 |
150 | # Spacing options
151 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#spacing
152 | csharp_space_after_cast = false:warning
153 | csharp_space_after_keywords_in_control_flow_statements = true:warning
154 | csharp_space_between_method_declaration_parameter_list_parentheses = false:warning
155 | csharp_space_between_method_call_parameter_list_parentheses = false:warning
156 | csharp_space_between_parentheses = expressions:warning
157 | csharp_space_before_colon_in_inheritance_clause = true:warning
158 | csharp_space_after_colon_in_inheritance_clause = true:warning
159 | csharp_space_around_binary_operators = before_and_after:warning
160 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false:warning
161 | csharp_space_between_method_call_name_and_opening_parenthesis = false:warning
162 | csharp_space_between_method_call_empty_parameter_list_parentheses = false:warning
163 |
164 | # Wrapping options
165 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#wrapping
166 | csharp_preserve_single_line_statements = false:warning
167 | csharp_preserve_single_line_blocks = true:warning
168 |
169 | # More Indentation options (Undocumented)
170 | csharp_indent_block_contents = true:warning
171 | csharp_indent_braces = false:warning
172 |
173 | # Spacing Options (Undocumented)
174 | csharp_space_after_comma = true:warning
175 | csharp_space_after_dot = false:warning
176 | csharp_space_after_semicolon_in_for_statement = true:warning
177 | csharp_space_around_declaration_statements = do_not_ignore:warning
178 | csharp_space_before_comma = false:warning
179 | csharp_space_before_dot = false:warning
180 | csharp_space_before_semicolon_in_for_statement = false:warning
181 | csharp_space_before_open_square_brackets = false:warning
182 | csharp_space_between_empty_square_brackets = false:warning
183 | csharp_space_between_method_declaration_name_and_open_parenthesis = false:warning
184 | csharp_space_between_square_brackets = false:warning
185 |
186 | #########################
187 | # .NET Naming conventions
188 | # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-naming-conventions
189 | #########################
190 |
191 | [*.{cs,csx,cake,vb}]
192 | # Naming Symbols
193 | # constant_fields - Define constant fields
194 | dotnet_naming_symbols.constant_fields.applicable_kinds = field
195 | dotnet_naming_symbols.constant_fields.required_modifiers = const
196 |
197 | # non_private_readonly_fields - Define public, internal and protected readonly fields
198 | dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, internal, protected
199 | dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field
200 | dotnet_naming_symbols.non_private_readonly_fields.required_modifiers = readonly
201 |
202 | # static_readonly_fields - Define static and readonly fields
203 | dotnet_naming_symbols.static_readonly_fields.applicable_kinds = field
204 | dotnet_naming_symbols.static_readonly_fields.required_modifiers = static, readonly
205 |
206 | # private_readonly_fields - Define private readonly fields
207 | dotnet_naming_symbols.private_readonly_fields.applicable_accessibilities = private
208 | dotnet_naming_symbols.private_readonly_fields.applicable_kinds = field
209 | dotnet_naming_symbols.private_readonly_fields.required_modifiers = readonly
210 |
211 | # public_internal_fields - Define public and internal fields
212 | dotnet_naming_symbols.public_internal_fields.applicable_accessibilities = public, internal
213 | dotnet_naming_symbols.public_internal_fields.applicable_kinds = field
214 |
215 | # private_protected_fields - Define private and protected fields
216 | dotnet_naming_symbols.private_protected_fields.applicable_accessibilities = private, protected
217 | dotnet_naming_symbols.private_protected_fields.applicable_kinds = field
218 |
219 | # public_symbols - Define any public symbol
220 | dotnet_naming_symbols.public_symbols.applicable_accessibilities = public, internal, protected, protected_internal
221 | dotnet_naming_symbols.public_symbols.applicable_kinds = method, property, event, delegate
222 |
223 | # parameters - Defines any parameter
224 | dotnet_naming_symbols.parameters.applicable_kinds = parameter
225 |
226 | # non_interface_types - Defines class, struct, enum and delegate types
227 | dotnet_naming_symbols.non_interface_types.applicable_kinds = class, struct, enum, delegate
228 |
229 | # interface_types - Defines interfaces
230 | dotnet_naming_symbols.interface_types.applicable_kinds = interface
231 |
232 | #########################
233 | # Naming Styles
234 | #########################
235 |
236 | # camel_case - Define the camelCase style
237 | dotnet_naming_style.camel_case.capitalization = camel_case
238 |
239 | # pascal_case - Define the Pascal_case style
240 | dotnet_naming_style.pascal_case.capitalization = pascal_case
241 |
242 | # first_upper - The first character must start with an upper-case character
243 | dotnet_naming_style.first_upper.capitalization = first_word_upper
244 |
245 | # prefix_interface_interface_with_i - Interfaces must be PascalCase and the first character of an interface must be an 'I'
246 | dotnet_naming_style.prefix_interface_interface_with_i.capitalization = pascal_case
247 | dotnet_naming_style.prefix_interface_interface_with_i.required_prefix = I
248 |
249 | #########################
250 | # Naming Rules
251 | #########################
252 |
253 | # Constant fields must be PascalCase
254 | dotnet_naming_rule.constant_fields_must_be_pascal_case.severity = warning
255 | dotnet_naming_rule.constant_fields_must_be_pascal_case.symbols = constant_fields
256 | dotnet_naming_rule.constant_fields_must_be_pascal_case.style = pascal_case
257 |
258 | # Public, internal and protected readonly fields must be PascalCase
259 | dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.severity = warning
260 | dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.symbols = non_private_readonly_fields
261 | dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.style = pascal_case
262 |
263 | # Static readonly fields must be PascalCase
264 | dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.severity = warning
265 | dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.symbols = static_readonly_fields
266 | dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.style = pascal_case
267 |
268 | # Public and internal fields must be PascalCase
269 | dotnet_naming_rule.public_internal_fields_must_be_pascal_case.severity = warning
270 | dotnet_naming_rule.public_internal_fields_must_be_pascal_case.symbols = public_internal_fields
271 | dotnet_naming_rule.public_internal_fields_must_be_pascal_case.style = pascal_case
272 |
273 | # Public members must be capitalized
274 | dotnet_naming_rule.public_members_must_be_capitalized.severity = warning
275 | dotnet_naming_rule.public_members_must_be_capitalized.symbols = public_symbols
276 | dotnet_naming_rule.public_members_must_be_capitalized.style = first_upper
277 |
278 | # Parameters must be camelCase
279 | dotnet_naming_rule.parameters_must_be_camel_case.severity = warning
280 | dotnet_naming_rule.parameters_must_be_camel_case.symbols = parameters
281 | dotnet_naming_rule.parameters_must_be_camel_case.style = camel_case
282 |
283 | # Class, struct, enum and delegates must be PascalCase
284 | dotnet_naming_rule.non_interface_types_must_be_pascal_case.severity = warning
285 | dotnet_naming_rule.non_interface_types_must_be_pascal_case.symbols = non_interface_types
286 | dotnet_naming_rule.non_interface_types_must_be_pascal_case.style = pascal_case
287 |
288 | # Interfaces must be PascalCase and start with an 'I'
289 | dotnet_naming_rule.interface_types_must_be_prefixed_with_i.severity = warning
290 | dotnet_naming_rule.interface_types_must_be_prefixed_with_i.symbols = interface_types
291 | dotnet_naming_rule.interface_types_must_be_prefixed_with_i.style = prefix_interface_interface_with_i
292 |
293 | dotnet_naming_symbols.private_fields.applicable_kinds = field
294 | dotnet_naming_symbols.private_fields.applicable_accessibilities = private, internal
295 |
296 | # internal and private fields should be _camelCase
297 | dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
298 | dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields
299 | dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style
300 |
301 | dotnet_naming_symbols.private_internal_fields.applicable_kinds = field
302 | dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal, protected
303 |
304 | dotnet_naming_style.camel_case_underscore_style.required_prefix = _
305 | dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case
306 |
307 |
308 | [*.cs]
309 |
310 | # CA1815: Override equals and operator equals on value types
311 | dotnet_diagnostic.CA1815.severity = silent
312 |
--------------------------------------------------------------------------------
/src/CometD.NetCore.Salesforce/Resilience/ResilientForceClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 |
6 | using Microsoft.Extensions.Logging;
7 | using Microsoft.Extensions.Options;
8 |
9 | using NetCoreForce.Client;
10 | using NetCoreForce.Client.Models;
11 |
12 | using Polly;
13 | using Polly.Wrap;
14 |
15 | namespace CometD.NetCore.Salesforce.Resilience
16 | {
17 | public class ResilientForceClient : IResilientForceClient
18 | {
19 | private readonly AsyncExpiringLazy _forceClient;
20 | private readonly SalesforceConfiguration _options;
21 | private readonly ILogger _logger;
22 | private readonly AsyncPolicyWrap _policy;
23 |
24 | private readonly string _policyContextMethod = nameof(_policyContextMethod);
25 |
26 | ///
27 | /// Initializes a new instance of the class.
28 | ///
29 | ///
30 | ///
31 | ///
32 | public ResilientForceClient(
33 | Func> forceClient,
34 | IOptions options,
35 | ILogger logger)
36 | {
37 | if (forceClient == null)
38 | {
39 | throw new ArgumentNullException(nameof(forceClient));
40 | }
41 |
42 | _logger = logger ?? throw new ArgumentNullException(nameof(logger));
43 |
44 | _forceClient = forceClient();
45 | _options = options.Value;
46 |
47 | _policy = Policy.WrapAsync(GetAuthenticationRetryPolicy(), GetWaitAndRetryPolicy());
48 | }
49 |
50 | ///
51 | public async Task CountQueryAsync(
52 | string queryString,
53 | bool queryAll = false,
54 | CancellationToken cancellationToken = default)
55 | {
56 | var mContext = new Context
57 | {
58 | [_policyContextMethod] = nameof(CountQueryAsync)
59 | };
60 |
61 | return await _policy.ExecuteAsync(
62 | async (context, token) =>
63 | {
64 | var client = _forceClient.Value().Result;
65 | return await client.CountQuery(queryString, queryAll);
66 | },
67 | mContext,
68 | cancellationToken);
69 | }
70 |
71 | ///
72 | public async Task CreateRecordAsync(
73 | string sObjectTypeName,
74 | T sObject,
75 | Dictionary? customHeaders = null,
76 | CancellationToken cancellationToken = default)
77 | {
78 | var mContext = new Context
79 | {
80 | [_policyContextMethod] = nameof(CreateRecordAsync)
81 | };
82 |
83 | return await _policy.ExecuteAsync(
84 | async (context, token) =>
85 | {
86 | var client = _forceClient.Value().Result;
87 | return await client.CreateRecord(
88 | sObjectTypeName,
89 | sObject,
90 | customHeaders);
91 | },
92 | mContext,
93 | cancellationToken);
94 | }
95 |
96 | ///
97 | public Task DeleteRecordAsync(
98 | string sObjectTypeName,
99 | string objectId,
100 | CancellationToken cancellationToken = default)
101 | {
102 | var mContext = new Context
103 | {
104 | [_policyContextMethod] = nameof(DeleteRecordAsync)
105 | };
106 |
107 | return _policy.ExecuteAsync(
108 | (context, token) =>
109 | {
110 | var client = _forceClient.Value().Result;
111 | return client.DeleteRecord(sObjectTypeName, objectId);
112 | },
113 | mContext,
114 | cancellationToken);
115 | }
116 |
117 | ///
118 | public async Task DescribeGlobalAsync(CancellationToken cancellationToken = default)
119 | {
120 | var mContext = new Context
121 | {
122 | [_policyContextMethod] = nameof(DescribeGlobalAsync)
123 | };
124 |
125 | return await _policy.ExecuteAsync(
126 | async (context, token) =>
127 | {
128 | var client = _forceClient.Value().Result;
129 | return await client.DescribeGlobal();
130 | },
131 | mContext,
132 | cancellationToken);
133 | }
134 |
135 | ///
136 | public async Task> GetAvailableRestApiVersionsAsync(
137 | string? currentInstanceUrl = null,
138 | CancellationToken cancellationToken = default)
139 | {
140 | var mContext = new Context
141 | {
142 | [_policyContextMethod] = nameof(GetAvailableRestApiVersionsAsync)
143 | };
144 |
145 | return await _policy.ExecuteAsync(
146 | async (context, token) =>
147 | {
148 | var client = _forceClient.Value().Result;
149 | return await client.GetAvailableRestApiVersions(currentInstanceUrl);
150 | },
151 | mContext,
152 | cancellationToken);
153 | }
154 |
155 | ///
156 | public async Task GetObjectBasicInfoAsync(
157 | string objectTypeName,
158 | CancellationToken cancellationToken = default)
159 | {
160 | var mContext = new Context
161 | {
162 | [_policyContextMethod] = nameof(GetObjectBasicInfoAsync)
163 | };
164 |
165 | return await _policy.ExecuteAsync(
166 | async (context, token) =>
167 | {
168 | var client = _forceClient.Value().Result;
169 | return await client.GetObjectBasicInfo(objectTypeName);
170 | },
171 | mContext,
172 | cancellationToken);
173 | }
174 |
175 | ///
176 | public async Task GetObjectByIdAsync(
177 | string sObjectTypeName,
178 | string objectId,
179 | List? fields = null,
180 | CancellationToken cancellationToken = default)
181 | {
182 | var mContext = new Context
183 | {
184 | [_policyContextMethod] = nameof(GetObjectByIdAsync)
185 | };
186 |
187 | return await _policy.ExecuteAsync(
188 | async (context, token) =>
189 | {
190 | var client = _forceClient.Value().Result;
191 | return await client.GetObjectById(
192 | sObjectTypeName,
193 | objectId,
194 | fields);
195 | },
196 | mContext,
197 | cancellationToken);
198 | }
199 |
200 | ///
201 | public async Task GetObjectDescribeAsync(
202 | string objectTypeName,
203 | CancellationToken cancellationToken = default)
204 | {
205 | var mContext = new Context
206 | {
207 | [_policyContextMethod] = nameof(GetObjectDescribeAsync)
208 | };
209 |
210 | return await _policy.ExecuteAsync(
211 | async (context, token) =>
212 | {
213 | var client = _forceClient.Value().Result;
214 | return await client.GetObjectDescribe(objectTypeName);
215 | },
216 | mContext,
217 | cancellationToken);
218 | }
219 |
220 | ///
221 | public async Task GetOrganizationLimitsAsync(CancellationToken cancellationToken = default)
222 | {
223 | var mContext = new Context
224 | {
225 | [_policyContextMethod] = nameof(GetOrganizationLimitsAsync)
226 | };
227 |
228 | return await _policy.ExecuteAsync(
229 | async (context, token) =>
230 | {
231 | var client = _forceClient.Value().Result;
232 | return await client.GetOrganizationLimits();
233 | },
234 | mContext,
235 | cancellationToken);
236 | }
237 |
238 | ///
239 | public async Task GetUserInfoAsync(
240 | string identityUrl,
241 | CancellationToken cancellationToken = default)
242 | {
243 | var mContext = new Context
244 | {
245 | [_policyContextMethod] = nameof(GetUserInfoAsync)
246 | };
247 |
248 | return await _policy.ExecuteAsync(
249 | async (context, token) =>
250 | {
251 | var client = _forceClient.Value().Result;
252 | return await client.GetUserInfo(identityUrl);
253 | },
254 | mContext,
255 | cancellationToken);
256 | }
257 |
258 | ///
259 | public async Task InsertOrUpdateRecordAsync(
260 | string sObjectTypeName,
261 | string fieldName,
262 | string fieldValue,
263 | T sObject,
264 | Dictionary? customHeaders = null,
265 | CancellationToken cancellationToken = default)
266 | {
267 | var mContext = new Context
268 | {
269 | [_policyContextMethod] = nameof(InsertOrUpdateRecordAsync)
270 | };
271 |
272 | return await _policy.ExecuteAsync(
273 | async (context, token) =>
274 | {
275 | var client = _forceClient.Value().Result;
276 | return await client.InsertOrUpdateRecord(
277 | sObjectTypeName,
278 | fieldName,
279 | fieldValue,
280 | sObject,
281 | customHeaders);
282 | },
283 | mContext,
284 | cancellationToken);
285 | }
286 |
287 | ///
288 | public async Task> QueryAsync(
289 | string queryString,
290 | bool queryAll = false,
291 | CancellationToken cancellationToken = default)
292 | {
293 | var mContext = new Context
294 | {
295 | [_policyContextMethod] = nameof(QueryAsync)
296 | };
297 |
298 | return await _policy.ExecuteAsync(
299 | async (context, token) =>
300 | {
301 | var client = _forceClient.Value().Result;
302 | return await client.Query(
303 | queryString,
304 | queryAll);
305 | },
306 | mContext,
307 | cancellationToken);
308 | }
309 |
310 | ///
311 | public async Task QuerySingleAsync(
312 | string queryString,
313 | bool queryAll = false,
314 | CancellationToken cancellationToken = default)
315 | {
316 | var mContext = new Context
317 | {
318 | [_policyContextMethod] = nameof(QuerySingleAsync)
319 | };
320 |
321 | return await _policy.ExecuteAsync(
322 | async (context, token) =>
323 | {
324 | var client = _forceClient.Value().Result;
325 | return await client.QuerySingle(
326 | queryString,
327 | queryAll);
328 | },
329 | mContext,
330 | cancellationToken);
331 | }
332 |
333 | ///
334 | public async Task> SearchAsync(
335 | string searchString,
336 | CancellationToken cancellationToken = default)
337 | {
338 | var mContext = new Context
339 | {
340 | [_policyContextMethod] = nameof(SearchAsync)
341 | };
342 |
343 | return await _policy.ExecuteAsync(
344 | async (context, token) =>
345 | {
346 | var client = _forceClient.Value().Result;
347 | return await client.Search(searchString);
348 | },
349 | mContext,
350 | cancellationToken);
351 | }
352 |
353 | ///
354 | public async Task TestConnectionAsync(
355 | string? currentInstanceUrl = null,
356 | CancellationToken cancellationToken = default)
357 | {
358 | var mContext = new Context
359 | {
360 | [_policyContextMethod] = nameof(TestConnectionAsync)
361 | };
362 |
363 | return await _policy.ExecuteAsync(
364 | async (context, token) =>
365 | {
366 | var client = _forceClient.Value().Result;
367 | return await Task.FromResult(client.TestConnection(currentInstanceUrl));
368 | },
369 | mContext,
370 | cancellationToken);
371 | }
372 |
373 | ///
374 | public Task UpdateRecordAsync(
375 | string sObjectTypeName,
376 | string objectId,
377 | T sObject,
378 | Dictionary? customHeaders = null,
379 | CancellationToken cancellationToken = default)
380 | {
381 | var mContext = new Context
382 | {
383 | [_policyContextMethod] = nameof(UpdateRecordAsync)
384 | };
385 |
386 | return _policy.ExecuteAsync(
387 | (context, token) =>
388 | {
389 | var client = _forceClient.Value().Result;
390 | return client.UpdateRecord(
391 | sObjectTypeName,
392 | objectId,
393 | sObject,
394 | customHeaders);
395 | },
396 | mContext,
397 | cancellationToken);
398 | }
399 |
400 | private IAsyncPolicy GetWaitAndRetryPolicy()
401 | {
402 | var jitterer = new Random();
403 |
404 | return Policy
405 | .Handle(x => !(x is ForceApiException))
406 | .WaitAndRetryAsync(
407 | retryCount: _options.Retry, // exponential back-off plus some jitter
408 | sleepDurationProvider: (retryAttempt, context) =>
409 | {
410 | return TimeSpan.FromSeconds(Math.Pow(_options.BackoffPower, retryAttempt))
411 | + TimeSpan.FromMilliseconds(jitterer.Next(0, 100));
412 | },
413 | onRetry: (ex, span, context) =>
414 | {
415 | var methodName = context[_policyContextMethod] ?? "MethodNotSpecified";
416 |
417 | _logger.LogWarning(
418 | "{Method} wait {Seconds} to execute with exception: {Message} for named policy: {Policy}",
419 | methodName,
420 | span.TotalSeconds,
421 | ex.Message,
422 | context.PolicyKey);
423 | })
424 | .WithPolicyKey($"{nameof(ResilientForceClient)}WaitAndRetryAsync");
425 | }
426 |
427 | private IAsyncPolicy GetAuthenticationRetryPolicy()
428 | {
429 | return Policy
430 | .Handle(x => x.Message.Contains("ErrorCode INVALID_SESSION_ID"))
431 | .RetryAsync(
432 | retryCount: _options.Retry,
433 | onRetryAsync: async (ex, count, context) =>
434 | {
435 | var methodName = context[_policyContextMethod] ?? "MethodNotSpecified";
436 |
437 | _logger.LogWarning(
438 | "{Method} attempting to re-authenticate Retry {Count} of {Total} for named policy {PolicyKey}, due to {Message}.",
439 | methodName,
440 | count,
441 | _options.Retry,
442 | context.PolicyKey,
443 | ex.Message);
444 |
445 | await _forceClient.Invalidate();
446 | })
447 | .WithPolicyKey($"{nameof(ResilientForceClient)}RetryAsync");
448 | }
449 | }
450 | }
451 |
--------------------------------------------------------------------------------