Skip to content

Commit 261d94e

Browse files
Add support for MS DI and client factory (#2318)
* Add support for MS DI and client factory * Add best practices guide
1 parent 4eff2fa commit 261d94e

31 files changed

+711
-161
lines changed

BEST_PRACTICES.md

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
# RestSharp – Best Practices
2+
3+
This document captures practical guidance for developing, testing, reviewing, and releasing changes in the RestSharp repository. It consolidates conventions used across the solution and provides checklists and examples to reduce regressions and ensure consistency across target frameworks.
4+
5+
6+
## 1) Daily Development Workflow
7+
8+
- Prefer small, focused pull requests with clear descriptions and test coverage.
9+
- Build and test locally before pushing. Validate all relevant target frameworks (TFMs) for your changes.
10+
- Keep commits tidy; prefer meaningful commit messages over large "fix" commits.
11+
- Follow repository code style and file organization rules (see sections below).
12+
- Use the latest C# language features (C# 14).
13+
14+
Suggested loop:
15+
- dotnet build
16+
- dotnet test
17+
18+
## 2) Multi-Targeting Guidance
19+
20+
The core library targets netstandard2.0, net471, net48, net8.0, net9.0, and net10.0. Tests target net48 (Windows only), net8.0, net9.0, and net10.0.
21+
22+
- Use conditional compilation for TFM-specific APIs or behaviors:
23+
- #if NET
24+
- #if NET for platform attributes (e.g., [UnsupportedOSPlatform("browser")])
25+
- When adding features, ensure compilation succeeds for all TFMs. If an API is missing on older TFMs, add polyfills or conditional code, or guard with feature detection.
26+
- Be mindful of System.Text.Json: it is a package dependency on older TFMs but built-in on modern TFMs.
27+
- Validate tests for each TFM impacted by your changes. If a test only applies to modern .NET, guard it with #if NET8_0_OR_GREATER.
28+
29+
30+
## 3) Build and Configuration
31+
32+
- Shared build logic lives in props/Common.props, src/Directory.Build.props, and test/Directory.Build.props. Do not duplicate MSBuild settings in individual projects unless necessary.
33+
- Language version is preview; prefer modern C# features but ensure cross-TFM compatibility.
34+
- Nullable reference types:
35+
- Enabled in /src (Nullable=enable). Treat nullable warnings as design feedback.
36+
- Disabled in /test (Nullable=disable) for test authoring ergonomics.
37+
- Assemblies are strong-named via RestSharp.snk. Do not remove signing.
38+
- Package versions are centrally managed in Directory.Packages.props; do not pin versions locally unless justified by TFM constraints.
39+
40+
41+
## 4) Source Generators
42+
43+
Custom incremental generators live in gen/SourceGenerator and are referenced as analyzers by src/RestSharp.
44+
45+
- Use [GenerateImmutable] to produce immutable wrappers of mutable classes.
46+
- Exclude properties with [Exclude] when not needed in the immutable type.
47+
- Generated files are emitted to obj/<Configuration>/<TFM>/generated/SourceGenerator/.
48+
- Use [GenerateClone(BaseType=..., Name=...)] to create static factory methods that upcast/copy base properties into derived types.
49+
- When editing generators:
50+
- Keep the generator targets netstandard2.0.
51+
- Ensure EmitCompilerGeneratedFiles is enabled when debugging locally.
52+
- Validate output by building and inspecting generated files under obj/...
53+
- Keep generator helpers cohesive (Extensions.cs) and prefer small, composable utilities.
54+
55+
56+
## 5) Testing Strategy
57+
58+
- Frameworks and libraries: xUnit + FluentAssertions + AutoFixture.
59+
- Organization:
60+
- Unit tests in test/RestSharp.Tests.
61+
- Integration tests (HTTP/WireMock) in test/RestSharp.Tests.Integrated.
62+
- Serializer-specific tests in dedicated projects.
63+
- Shared helpers in test/RestSharp.Tests.Shared.
64+
- Best practices:
65+
- Co-locate tests with feature areas; use partial classes to split large suites.
66+
- Prefer WireMockServer over live endpoints for HTTP scenarios.
67+
- Avoid flaky tests: don’t depend on timing, locale, or network conditions.
68+
- Use descriptive assertions, e.g., result.Should().Be(expected).
69+
- Scope tests by TFM when API availability differs.
70+
- Useful commands:
71+
- dotnet test RestSharp.sln -c Debug
72+
- dotnet test test/RestSharp.Tests/RestSharp.Tests.csproj -f net8.0
73+
- dotnet test test/RestSharp.Tests/RestSharp.Tests.csproj --filter "FullyQualifiedName=Namespace.Class.Method" -f net8.0
74+
- Test results are written to test-results/<TFM>/<ProjectName>.trx.
75+
76+
77+
## 6) Code Coverage
78+
79+
- Use coverlet.collector for data-collector-based coverage.
80+
- Example command (Cobertura output):
81+
- dotnet test test/RestSharp.Tests/RestSharp.Tests.csproj -f net10.0 --collect:"XPlat Code Coverage" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura
82+
- Coverage artifacts are placed alongside test results in test-results.
83+
84+
85+
## 7) Dependency Injection and Extensions
86+
87+
- DI extensions live in src/RestSharp.Extensions.DependencyInjection.
88+
- When updating DI helpers:
89+
- Keep APIs minimal and idiomatic for Microsoft.Extensions.DependencyInjection.
90+
- Maintain backward compatibility where feasible; add overloads rather than breaking changes.
91+
- Add focused tests under test/RestSharp.Tests.DependencyInjection.
92+
93+
94+
## 8) Serialization Extensions
95+
96+
- JSON (Newtonsoft.Json), XML, and CSV serializers are separate packages within src/RestSharp.Serializers.*.
97+
- Keep serializer-specific behavior isolated. Avoid coupling core library to serializers.
98+
- Each serializer project has its own tests; ensure behavior parity across TFMs.
99+
100+
101+
## 9) Performance and Benchmarks
102+
103+
- Use benchmarks/RestSharp.Benchmarks for perf investigations.
104+
- Before merging performance-related changes:
105+
- Validate allocations and throughput with BenchmarkDotNet where practical.
106+
- Check for regression across TFMs if the code path is shared.
107+
108+
109+
## 10) Versioning and Packaging
110+
111+
- Versioning is handled by MinVer via Git tags and history. Ensure CI uses full history (unshallow fetch).
112+
- Do not hardcode versions; rely on MinVer for assembly and file versions (CustomVersion target aligns versions post-MinVer).
113+
- Packaging notes:
114+
- dotnet pack src/RestSharp/RestSharp.csproj -c Release -o nuget -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg
115+
- Symbol packages must be .snupkg; SourceLink is enabled for debugging.
116+
- NuGet metadata is defined in src/Directory.Build.props; keep it accurate.
117+
118+
119+
## 11) Continuous Integration
120+
121+
- PR workflow executes matrix tests on Windows (net48, net8.0, net9.0, net10.0) and Linux (net8.0, net9.0, net10.0).
122+
- Build/Deploy workflow packages and pushes to NuGet on dev branch and tags using OIDC-based auth.
123+
- To simulate CI locally:
124+
- dotnet test -c Debug -f net8.0
125+
- Optionally also run -f net9.0 and -f net10.0. On Windows, include net48.
126+
- Uploading artifacts and publishing test results are CI-responsibilities; keep local output organized in test-results for quick inspection.
127+
128+
129+
## 12) Code Organization and Style
130+
131+
- File organization:
132+
- Use partial classes to split large types by responsibility (e.g., RestClient.*, PropertyCache.*). Link related files via <DependentUpon> in csproj when appropriate.
133+
- License header:
134+
- All source files under /src must include the standard repository license header.
135+
- Test files do not require the header.
136+
- EditorConfig and rules:
137+
- Follow .editorconfig for formatting and analyzer rules.
138+
- In /src, suppress XML doc warnings via NoWarn=1591; in tests suppress xUnit1033 and CS8002 as configured.
139+
- Nullable:
140+
- Treat nullable annotations and warnings as design signals; prefer explicit nullability and defensive checks at public boundaries.
141+
- Public API surface:
142+
- Avoid breaking changes. If unavoidable, document in docs and changelog, and add clear migration notes.
143+
144+
145+
## 13) PR Readiness Checklist
146+
147+
Use this quick checklist before requesting review:
148+
- Builds cleanly across all targeted TFMs for affected projects.
149+
- Unit/integration tests added or updated; all pass locally for relevant TFMs.
150+
- No analyzer warnings introduced in src (beyond allowed suppressions).
151+
- License header present in new /src files.
152+
- Source generator changes validated (if applicable) by inspecting obj/.../generated output.
153+
- Public API changes reviewed, documented, and tested.
154+
- Central package versions unchanged unless intentionally updated with justification.
155+
- Commit messages and PR description explain the why and the how.
156+
157+
158+
## 14) Troubleshooting Guide
159+
160+
- Tests fail only on a specific TFM
161+
- Run with -f <TFM> to isolate. Look for conditional compilation and API availability differences.
162+
- Generator output missing
163+
- Ensure EmitCompilerGeneratedFiles is true in the project under test; clean and rebuild; inspect obj/.../generated.
164+
- net48 failures on non-Windows
165+
- Expected. Use net8.0 or higher on Linux/macOS. Run net48 only on Windows.
166+
- MinVer version incorrect
167+
- Ensure CI or local clone has full git history (git fetch --prune --unshallow).
168+
- Platform-specific failures
169+
- Use [UnsupportedOSPlatform] and conditional compilation to guard APIs not available on Browser, etc.
170+
171+
172+
## 15) Useful Commands (Quick Reference)
173+
174+
- Build solution (Release):
175+
- dotnet build RestSharp.sln -c Release
176+
- Run tests for a single TFM:
177+
- dotnet test test/RestSharp.Tests/RestSharp.Tests.csproj -f net8.0
178+
- Run a single test by fully-qualified name:
179+
- dotnet test test/RestSharp.Tests/RestSharp.Tests.csproj --filter "FullyQualifiedName=RestSharp.Tests.ObjectParserTests.ShouldUseRequestProperty" -f net8.0
180+
- Pack locally with symbols:
181+
- dotnet pack src/RestSharp/RestSharp.csproj -c Release -o nuget -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg
182+
- View generated source files after build:
183+
- find src/RestSharp/obj/Debug -name "*.g.cs" -o -name "ReadOnly*.cs"
184+
- Clean all build artifacts:
185+
- dotnet clean RestSharp.sln
186+
- rm -rf src/*/bin src/*/obj test/*/bin test/*/obj gen/*/bin gen/*/obj
187+
188+
189+
## 16) Documentation and References
190+
191+
- Main docs: https://restsharp.dev
192+
- Repository: https://github.com/restsharp/RestSharp
193+
- NuGet Packages:
194+
- RestSharp
195+
- RestSharp.Serializers.NewtonsoftJson
196+
- RestSharp.Serializers.Xml
197+
- RestSharp.Serializers.CsvHelper
198+
- License: Apache-2.0
199+
200+
Keep this document up-to-date when build properties, TFMs, CI workflows, or repository conventions change.

Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
<SystemTextJsonVer>9.0.10</SystemTextJsonVer>
1717
</PropertyGroup>
1818
<ItemGroup Label="Runtime dependencies">
19+
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.0" />
1920
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
2021
<PackageVersion Include="CsvHelper" Version="33.0.1" />
2122
<PackageVersion Include="System.Text.Json" Version="$(SystemTextJsonVer)" />

RestSharp.sln

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SourceGen", "SourceGen", "{
3737
EndProject
3838
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceGenerator", "gen\SourceGenerator\SourceGenerator.csproj", "{FE778406-ADCF-45A1-B775-A054B55BFC50}"
3939
EndProject
40+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RestSharp.Extensions.DependencyInjection", "src\RestSharp.Extensions.DependencyInjection\RestSharp.Extensions.DependencyInjection.csproj", "{92A6F3CA-100F-4D9D-9742-B62267D445B6}"
41+
EndProject
42+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RestSharp.Tests.DependencyInjection", "test\RestSharp.Tests.DependencyInjection\RestSharp.Tests.DependencyInjection.csproj", "{602FF788-E926-4404-B3A2-D6B778A5FFB7}"
43+
EndProject
4044
Global
4145
GlobalSection(SolutionConfigurationPlatforms) = preSolution
4246
Debug.Appveyor|Any CPU = Debug.Appveyor|Any CPU
@@ -446,6 +450,66 @@ Global
446450
{FE778406-ADCF-45A1-B775-A054B55BFC50}.Release|x64.Build.0 = Release|Any CPU
447451
{FE778406-ADCF-45A1-B775-A054B55BFC50}.Release|x86.ActiveCfg = Release|Any CPU
448452
{FE778406-ADCF-45A1-B775-A054B55BFC50}.Release|x86.Build.0 = Release|Any CPU
453+
{92A6F3CA-100F-4D9D-9742-B62267D445B6}.Debug.Appveyor|Any CPU.ActiveCfg = Debug|Any CPU
454+
{92A6F3CA-100F-4D9D-9742-B62267D445B6}.Debug.Appveyor|Any CPU.Build.0 = Debug|Any CPU
455+
{92A6F3CA-100F-4D9D-9742-B62267D445B6}.Debug.Appveyor|ARM.ActiveCfg = Debug|Any CPU
456+
{92A6F3CA-100F-4D9D-9742-B62267D445B6}.Debug.Appveyor|ARM.Build.0 = Debug|Any CPU
457+
{92A6F3CA-100F-4D9D-9742-B62267D445B6}.Debug.Appveyor|Mixed Platforms.ActiveCfg = Debug|Any CPU
458+
{92A6F3CA-100F-4D9D-9742-B62267D445B6}.Debug.Appveyor|Mixed Platforms.Build.0 = Debug|Any CPU
459+
{92A6F3CA-100F-4D9D-9742-B62267D445B6}.Debug.Appveyor|x64.ActiveCfg = Debug|Any CPU
460+
{92A6F3CA-100F-4D9D-9742-B62267D445B6}.Debug.Appveyor|x64.Build.0 = Debug|Any CPU
461+
{92A6F3CA-100F-4D9D-9742-B62267D445B6}.Debug.Appveyor|x86.ActiveCfg = Debug|Any CPU
462+
{92A6F3CA-100F-4D9D-9742-B62267D445B6}.Debug.Appveyor|x86.Build.0 = Debug|Any CPU
463+
{92A6F3CA-100F-4D9D-9742-B62267D445B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
464+
{92A6F3CA-100F-4D9D-9742-B62267D445B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
465+
{92A6F3CA-100F-4D9D-9742-B62267D445B6}.Debug|ARM.ActiveCfg = Debug|Any CPU
466+
{92A6F3CA-100F-4D9D-9742-B62267D445B6}.Debug|ARM.Build.0 = Debug|Any CPU
467+
{92A6F3CA-100F-4D9D-9742-B62267D445B6}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
468+
{92A6F3CA-100F-4D9D-9742-B62267D445B6}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
469+
{92A6F3CA-100F-4D9D-9742-B62267D445B6}.Debug|x64.ActiveCfg = Debug|Any CPU
470+
{92A6F3CA-100F-4D9D-9742-B62267D445B6}.Debug|x64.Build.0 = Debug|Any CPU
471+
{92A6F3CA-100F-4D9D-9742-B62267D445B6}.Debug|x86.ActiveCfg = Debug|Any CPU
472+
{92A6F3CA-100F-4D9D-9742-B62267D445B6}.Debug|x86.Build.0 = Debug|Any CPU
473+
{92A6F3CA-100F-4D9D-9742-B62267D445B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
474+
{92A6F3CA-100F-4D9D-9742-B62267D445B6}.Release|Any CPU.Build.0 = Release|Any CPU
475+
{92A6F3CA-100F-4D9D-9742-B62267D445B6}.Release|ARM.ActiveCfg = Release|Any CPU
476+
{92A6F3CA-100F-4D9D-9742-B62267D445B6}.Release|ARM.Build.0 = Release|Any CPU
477+
{92A6F3CA-100F-4D9D-9742-B62267D445B6}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
478+
{92A6F3CA-100F-4D9D-9742-B62267D445B6}.Release|Mixed Platforms.Build.0 = Release|Any CPU
479+
{92A6F3CA-100F-4D9D-9742-B62267D445B6}.Release|x64.ActiveCfg = Release|Any CPU
480+
{92A6F3CA-100F-4D9D-9742-B62267D445B6}.Release|x64.Build.0 = Release|Any CPU
481+
{92A6F3CA-100F-4D9D-9742-B62267D445B6}.Release|x86.ActiveCfg = Release|Any CPU
482+
{92A6F3CA-100F-4D9D-9742-B62267D445B6}.Release|x86.Build.0 = Release|Any CPU
483+
{602FF788-E926-4404-B3A2-D6B778A5FFB7}.Debug.Appveyor|Any CPU.ActiveCfg = Debug|Any CPU
484+
{602FF788-E926-4404-B3A2-D6B778A5FFB7}.Debug.Appveyor|Any CPU.Build.0 = Debug|Any CPU
485+
{602FF788-E926-4404-B3A2-D6B778A5FFB7}.Debug.Appveyor|ARM.ActiveCfg = Debug|Any CPU
486+
{602FF788-E926-4404-B3A2-D6B778A5FFB7}.Debug.Appveyor|ARM.Build.0 = Debug|Any CPU
487+
{602FF788-E926-4404-B3A2-D6B778A5FFB7}.Debug.Appveyor|Mixed Platforms.ActiveCfg = Debug|Any CPU
488+
{602FF788-E926-4404-B3A2-D6B778A5FFB7}.Debug.Appveyor|Mixed Platforms.Build.0 = Debug|Any CPU
489+
{602FF788-E926-4404-B3A2-D6B778A5FFB7}.Debug.Appveyor|x64.ActiveCfg = Debug|Any CPU
490+
{602FF788-E926-4404-B3A2-D6B778A5FFB7}.Debug.Appveyor|x64.Build.0 = Debug|Any CPU
491+
{602FF788-E926-4404-B3A2-D6B778A5FFB7}.Debug.Appveyor|x86.ActiveCfg = Debug|Any CPU
492+
{602FF788-E926-4404-B3A2-D6B778A5FFB7}.Debug.Appveyor|x86.Build.0 = Debug|Any CPU
493+
{602FF788-E926-4404-B3A2-D6B778A5FFB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
494+
{602FF788-E926-4404-B3A2-D6B778A5FFB7}.Debug|Any CPU.Build.0 = Debug|Any CPU
495+
{602FF788-E926-4404-B3A2-D6B778A5FFB7}.Debug|ARM.ActiveCfg = Debug|Any CPU
496+
{602FF788-E926-4404-B3A2-D6B778A5FFB7}.Debug|ARM.Build.0 = Debug|Any CPU
497+
{602FF788-E926-4404-B3A2-D6B778A5FFB7}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
498+
{602FF788-E926-4404-B3A2-D6B778A5FFB7}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
499+
{602FF788-E926-4404-B3A2-D6B778A5FFB7}.Debug|x64.ActiveCfg = Debug|Any CPU
500+
{602FF788-E926-4404-B3A2-D6B778A5FFB7}.Debug|x64.Build.0 = Debug|Any CPU
501+
{602FF788-E926-4404-B3A2-D6B778A5FFB7}.Debug|x86.ActiveCfg = Debug|Any CPU
502+
{602FF788-E926-4404-B3A2-D6B778A5FFB7}.Debug|x86.Build.0 = Debug|Any CPU
503+
{602FF788-E926-4404-B3A2-D6B778A5FFB7}.Release|Any CPU.ActiveCfg = Release|Any CPU
504+
{602FF788-E926-4404-B3A2-D6B778A5FFB7}.Release|Any CPU.Build.0 = Release|Any CPU
505+
{602FF788-E926-4404-B3A2-D6B778A5FFB7}.Release|ARM.ActiveCfg = Release|Any CPU
506+
{602FF788-E926-4404-B3A2-D6B778A5FFB7}.Release|ARM.Build.0 = Release|Any CPU
507+
{602FF788-E926-4404-B3A2-D6B778A5FFB7}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
508+
{602FF788-E926-4404-B3A2-D6B778A5FFB7}.Release|Mixed Platforms.Build.0 = Release|Any CPU
509+
{602FF788-E926-4404-B3A2-D6B778A5FFB7}.Release|x64.ActiveCfg = Release|Any CPU
510+
{602FF788-E926-4404-B3A2-D6B778A5FFB7}.Release|x64.Build.0 = Release|Any CPU
511+
{602FF788-E926-4404-B3A2-D6B778A5FFB7}.Release|x86.ActiveCfg = Release|Any CPU
512+
{602FF788-E926-4404-B3A2-D6B778A5FFB7}.Release|x86.Build.0 = Release|Any CPU
449513
EndGlobalSection
450514
GlobalSection(SolutionProperties) = preSolution
451515
HideSolutionNode = FALSE
@@ -463,6 +527,7 @@ Global
463527
{2150E333-8FDC-42A3-9474-1A3956D46DE8} = {8C7B43EB-2F93-483C-B433-E28F9386AD67}
464528
{E6D94FFD-7811-40BE-ABC4-6D6AB41F0060} = {9051DDA0-E563-45D5-9504-085EBAACF469}
465529
{FE778406-ADCF-45A1-B775-A054B55BFC50} = {55B8F371-B2BA-4DEE-AB98-5BAB8A21B1C2}
530+
{602FF788-E926-4404-B3A2-D6B778A5FFB7} = {9051DDA0-E563-45D5-9504-085EBAACF469}
466531
EndGlobalSection
467532
GlobalSection(ExtensibilityGlobals) = postSolution
468533
SolutionGuid = {77FF357B-03FA-4FA5-A68F-BFBE5800FEBA}

agents.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ The build system uses a hierarchical props structure:
9999

100100
**Modern .NET (8/9/10):**
101101
- Native support for most features
102-
- Conditional compilation using `#if NET8_0_OR_GREATER`
102+
- Conditional compilation using `#if NET`
103103
- Platform-specific attributes like `[UnsupportedOSPlatform("browser")]`
104104

105105
### Assembly Signing

0 commit comments

Comments
 (0)