From 1dae2e5f5915ea4b8877d51a7ce2734ea67af965 Mon Sep 17 00:00:00 2001 From: Brendan Kowitz Date: Sun, 17 Dec 2023 19:01:03 -0800 Subject: [PATCH] Upgrades from .NET7 to 8 (keeps 6 as current LTS) (#3617) * Upgrades from .NET7 to 8 (keeps 6 as current LTS) * Adds RoleClaimType mapping for .net8 +semver: major --- CustomAnalysisRules.ruleset | 6 +- Directory.Build.props | 2 +- Directory.Packages.props | 51 ++++++------- ...s-PRInternalChecks-azureBuild-pipeline.yml | 2 +- build/build-variables.yml | 2 +- build/ci-pipeline.yml | 2 +- build/docker/Dockerfile | 6 +- build/dotnet6-compat/global.json | 2 +- build/jobs/analyze.yml | 2 +- build/pr-pipeline.yml | 2 +- global.json | 2 +- .../ApiNotificationMiddleware.cs | 46 ++++++------ .../Features/Audit/AuditHelper.cs | 4 +- ...ictionaryExpansionConfigurationProvider.cs | 2 +- .../Headers/HeaderDictionaryExtensions.cs | 8 +- .../Headers/ResourceActionResultExtensions.cs | 4 +- .../Throttling/ThrottlingMiddleware.cs | 3 +- .../FhirServerApplicationBuilderExtensions.cs | 5 +- ...nymizationConfigurationArtifactProvider.cs | 33 ++++----- .../AzureAccessTokenClientInitializerV2.cs | 2 +- .../Export/CancelExportRequestHandlerTests.cs | 6 +- .../Operations/Export/ExportJobTaskTests.cs | 74 ++++++++++--------- .../SearchParameterStatusManagerTests.cs | 1 + ...icrosoft.Health.Fhir.Core.UnitTests.csproj | 3 + .../CancellationTokenSourceExtensions.cs | 20 +++++ .../Extensions/Clock.cs | 27 +++++++ .../Extensions/ClockResolver.cs | 17 +++++ .../Extensions/SearchServiceExtensions.cs | 2 +- .../Conformance/CapabilityStatementBuilder.cs | 6 +- .../Models/DefaultOptionHashSet.cs | 1 - .../Conformance/SystemConformanceProvider.cs | 2 +- .../SearchParameterDefinitionBuilder.cs | 4 +- .../ContainerRegistryTemplateProvider.cs | 18 ++--- .../ConvertData/DefaultTemplateProvider.cs | 11 +-- .../Export/CancelExportRequestHandler.cs | 1 + .../Export/CreateExportRequestHandler.cs | 4 +- .../Operations/Export/ExportFileManager.cs | 5 +- .../Operations/Export/ExportJobTask.cs | 1 + .../Export/Models/ExportJobRecord.cs | 1 + .../Operations/Import/ImportErrorStore.cs | 3 +- .../Operations/Import/ImportResourceLoader.cs | 7 +- .../Reindex/CancelReindexRequestHandler.cs | 1 + .../Reindex/Models/ReindexJobRecord.cs | 1 + .../Operations/Reindex/ReindexJobTask.cs | 9 ++- .../Features/Persistence/ResourceWrapper.cs | 1 + .../Search/Access/ExpressionAccessControl.cs | 2 +- ...eferenceToReferenceSearchValueConverter.cs | 2 +- .../UriToReferenceSearchValueConverter.cs | 2 +- .../Search/Expressions/IncludeExpression.cs | 2 +- .../Expressions/Parsers/ExpressionParser.cs | 2 +- .../SearchValueExpressionBuilderHelper.cs | 1 + .../Filters/MissingDataFilterCriteria.cs | 12 +-- ...FilebasedSearchParameterStatusDataStore.cs | 5 +- .../Registry/SearchParameterStatusManager.cs | 1 + .../Features/Search/SearchService.cs | 1 + .../Features/Search/StringExtensions.cs | 4 +- .../Search/TypedElementSearchIndexer.cs | 2 +- .../Narratives/NarrativeHtmlSanitizer.cs | 6 +- .../Storage/CosmosFhirDataStoreTests.cs | 5 +- ...DbSearchParameterStatusInitializerTests.cs | 1 + .../Storage/Search/ResourceWrapperTests.cs | 5 +- ...soft.Health.Fhir.CosmosDb.UnitTests.csproj | 3 + .../ReindexJobCosmosThrottleController.cs | 1 + ...CosmosDbCollectionPhysicalPartitionInfo.cs | 7 +- .../Storage/CosmosDbDistributedLock.cs | 4 +- .../Features/ActionResults/FhirResultTests.cs | 6 +- ...RouteDataPopulatingFilterAttributeTests.cs | 4 +- ...teBulkImportRequestFilterAttributeTests.cs | 32 ++++---- ...ValidateContentTypeFilterAttributeTests.cs | 38 +++++----- ...lidateExportRequestFilterAttributeTests.cs | 32 ++++---- .../Headers/FhirResultExtensionsTests.cs | 6 +- .../RequestHandlerCheckAccessTests.cs | 3 +- .../Controllers/BulkDeleteController.cs | 2 +- .../Controllers/EverythingController.cs | 2 +- .../Controllers/FhirController.cs | 4 +- ...perationOutcomeExceptionFilterAttribute.cs | 4 +- .../Formatters/FormatterExtensions.cs | 2 +- .../Formatters/HtmlOutputFormatter.cs | 7 +- .../Formatters/HttpContextExtensions.cs | 8 +- .../Features/Headers/FhirResultExtensions.cs | 10 ++- .../Operations/ParametersExtensions.cs | 4 +- .../Resources/Bundle/BundleHandler.cs | 10 +-- .../Bundle/BundleHandlerParallelOperations.cs | 3 +- .../Resources/ProvenanceHeaderBehavior.cs | 4 +- .../Resources/ResourceHandlerTests.cs | 5 +- .../Features/Search/BundleFactoryTests.cs | 7 +- .../SearchValueExpressionBuilderTests.cs | 5 +- .../MemberMatch/MemberMatchService.cs | 10 +-- ...tIdentityProviderRegistrationExtensions.cs | 10 +-- .../Startup.cs | 4 +- .../Import/ImportOrchestratorJob.cs | 6 +- .../Operations/Import/ImportProcessingJob.cs | 5 +- .../Features/Operations/Import/SqlImporter.cs | 5 +- .../Features/Search/CustomQueries.cs | 3 +- .../Expressions/Visitors/IncludeRewriter.cs | 2 + .../Visitors/SqlRootExpressionRewriter.cs | 2 +- .../Search/HashingSqlQueryParameterManager.cs | 4 + .../Features/Search/SqlCommandSimplifier.cs | 7 +- .../Storage/SqlServerFhirDataStore.cs | 2 +- .../Features/Watchdogs/DefragWatchdog.cs | 3 +- .../BatchExtensions.cs | 2 +- .../JobHosting.cs | 8 ++ ...ft.Health.Fhir.R4.Tests.Integration.csproj | 3 + ...t.Health.Fhir.R4B.Tests.Integration.csproj | 3 + ...ft.Health.Fhir.R5.Tests.Integration.csproj | 3 + .../Persistence/FhirStorageTests.cs | 5 +- tools/Importer/Importer.cs | 6 +- tools/IndexRebuilder/IndexRebuilder.cs | 2 +- ...MinimalSearchParameterDefinitionBuilder.cs | 2 +- .../ResourceWrapperParser.cs | 2 +- .../RegisterAndMonitorImport.cs | 2 +- 111 files changed, 449 insertions(+), 330 deletions(-) create mode 100644 src/Microsoft.Health.Fhir.Core/Extensions/CancellationTokenSourceExtensions.cs create mode 100644 src/Microsoft.Health.Fhir.Core/Extensions/Clock.cs create mode 100644 src/Microsoft.Health.Fhir.Core/Extensions/ClockResolver.cs diff --git a/CustomAnalysisRules.ruleset b/CustomAnalysisRules.ruleset index af324a764a..342d390ef1 100644 --- a/CustomAnalysisRules.ruleset +++ b/CustomAnalysisRules.ruleset @@ -1,5 +1,5 @@  - + @@ -16,6 +16,8 @@ + + @@ -48,4 +50,4 @@ - + \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index baa783685b..174c7a2c62 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -18,7 +18,7 @@ true https://github.com/microsoft/fhir-server/ $(MSBuildThisFileDirectory)\CodeCoverage.runsettings - net7.0;net6.0 + net8.0;net6.0 true true diff --git a/Directory.Packages.props b/Directory.Packages.props index 91375b060f..297d59d0eb 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,16 +1,16 @@ - 7.0.29 + 7.1.5 4.3.0 - + - + - + @@ -18,9 +18,9 @@ - + - 7.0.12 + 8.0.0 @@ -32,7 +32,7 @@ - + @@ -61,21 +61,22 @@ - - - - - - - - - - - + + + + + + + + + + + - - - + + + + @@ -108,9 +109,9 @@ - + - + @@ -119,7 +120,7 @@ - + - \ No newline at end of file + diff --git a/build/.vsts-PRInternalChecks-azureBuild-pipeline.yml b/build/.vsts-PRInternalChecks-azureBuild-pipeline.yml index 24bd7d438b..92961612b2 100644 --- a/build/.vsts-PRInternalChecks-azureBuild-pipeline.yml +++ b/build/.vsts-PRInternalChecks-azureBuild-pipeline.yml @@ -32,7 +32,7 @@ jobs: displayName: dotnet build inputs: projects: '**/*.sln' - arguments: --configuration ${{ parameters.BuildConfiguration }} --version-suffix $(build.buildnumber) /warnaserror -f net7.0 + arguments: --configuration ${{ parameters.BuildConfiguration }} --version-suffix $(build.buildnumber) /warnaserror -f net8.0 - task: AutoApplicability@1 displayName: Run AutoApplicability continueOnError: True diff --git a/build/build-variables.yml b/build/build-variables.yml index f5b4d150f5..7f28b89720 100644 --- a/build/build-variables.yml +++ b/build/build-variables.yml @@ -3,7 +3,7 @@ variables: buildConfiguration: 'Release' - defaultBuildFramework: 'net7.0' + defaultBuildFramework: 'net8.0' azureSubscriptionEndpoint: 'docker-build' azureContainerRegistryName: 'healthplatformregistry' azureContainerRegistry: '$(azureContainerRegistryName).azurecr.io' diff --git a/build/ci-pipeline.yml b/build/ci-pipeline.yml index 66b76f9255..de6f125dd8 100644 --- a/build/ci-pipeline.yml +++ b/build/ci-pipeline.yml @@ -65,7 +65,7 @@ stages: majorMinorPatch: $[stageDependencies.UpdateVersion.Semver.outputs['SetVariablesFromGitVersion.majorMinorPatch']] nuGetVersion: $[stageDependencies.UpdateVersion.Semver.outputs['SetVariablesFromGitVersion.nuGetVersion']] jobs: - - job: Windows_dotnet7 + - job: Windows_dotnet8 pool: name: '$(DefaultWindowsPool)' steps: diff --git a/build/docker/Dockerfile b/build/docker/Dockerfile index e99e09a7e0..35aa37800b 100644 --- a/build/docker/Dockerfile +++ b/build/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:7.0.402-cbl-mariner2.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:8.0.100-1-cbl-mariner2.0 AS build ARG FHIR_VERSION ARG ASSEMBLY_VER @@ -67,9 +67,9 @@ RUN dotnet restore ./src/Microsoft.Health.Fhir.${FHIR_VERSION}.Web/Microsoft.Hea COPY . . -RUN dotnet publish /repo/src/Microsoft.Health.Fhir.${FHIR_VERSION}.Web/Microsoft.Health.Fhir.${FHIR_VERSION}.Web.csproj -c Release -o "/build" --no-restore -p:AssemblyVersion="${ASSEMBLY_VER}" -p:FileVersion="${ASSEMBLY_VER}" -p:Version="${ASSEMBLY_VER}" -f net7.0 +RUN dotnet publish /repo/src/Microsoft.Health.Fhir.${FHIR_VERSION}.Web/Microsoft.Health.Fhir.${FHIR_VERSION}.Web.csproj -c Release -o "/build" --no-restore -p:AssemblyVersion="${ASSEMBLY_VER}" -p:FileVersion="${ASSEMBLY_VER}" -p:Version="${ASSEMBLY_VER}" -f net8.0 -FROM mcr.microsoft.com/dotnet/aspnet:7.0.12-cbl-mariner2.0 AS runtime +FROM mcr.microsoft.com/dotnet/aspnet:8.0.0-cbl-mariner2.0 AS runtime ARG FHIR_VERSION diff --git a/build/dotnet6-compat/global.json b/build/dotnet6-compat/global.json index abde2ccf72..0c1333116f 100644 --- a/build/dotnet6-compat/global.json +++ b/build/dotnet6-compat/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "6.0.415" + "version": "6.0.417" } } diff --git a/build/jobs/analyze.yml b/build/jobs/analyze.yml index cfc7ee368e..5ad0110a60 100644 --- a/build/jobs/analyze.yml +++ b/build/jobs/analyze.yml @@ -108,7 +108,7 @@ steps: inputs: userProvideBuildInfo: 'msBuildInfo' msBuildArchitecture: 'DotNetCore' - msBuildCommandline: 'dotnet build $(Build.SourcesDirectory)/Microsoft.Health.Fhir.sln --configuration $(buildConfiguration) -p:ContinuousIntegrationBuild=true -f net7.0' + msBuildCommandline: 'dotnet build $(Build.SourcesDirectory)/Microsoft.Health.Fhir.sln --configuration $(buildConfiguration) -p:ContinuousIntegrationBuild=true -f net8.0' - task: BinSkim@4 inputs: diff --git a/build/pr-pipeline.yml b/build/pr-pipeline.yml index 43dd31a89c..d42658a04e 100644 --- a/build/pr-pipeline.yml +++ b/build/pr-pipeline.yml @@ -48,7 +48,7 @@ stages: majorMinorPatch: $[stageDependencies.UpdateVersion.Semver.outputs['SetVariablesFromGitVersion.majorMinorPatch']] nuGetVersion: $[stageDependencies.UpdateVersion.Semver.outputs['SetVariablesFromGitVersion.nuGetVersion']] jobs: - - job: Windows_dotnet7 + - job: Windows_dotnet8 pool: name: '$(DefaultWindowsPool)' steps: diff --git a/global.json b/global.json index 34a2bc8ad8..48e1c84489 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "7.0.402" + "version": "8.0.100" } } diff --git a/src/Microsoft.Health.Fhir.Api/Features/ApiNotifications/ApiNotificationMiddleware.cs b/src/Microsoft.Health.Fhir.Api/Features/ApiNotifications/ApiNotificationMiddleware.cs index cd7a2d2a3c..2fd4f8efba 100644 --- a/src/Microsoft.Health.Fhir.Api/Features/ApiNotifications/ApiNotificationMiddleware.cs +++ b/src/Microsoft.Health.Fhir.Api/Features/ApiNotifications/ApiNotificationMiddleware.cs @@ -57,38 +57,36 @@ protected virtual async Task PublishNotificationAsync(HttpContext context, Reque { var apiNotification = new ApiResponseNotification(); - using (var timer = _logger.BeginTimedScope(nameof(ApiNotificationMiddleware)) as ActionTimer) + using ActionTimer timer = _logger.BeginTimedScope(nameof(ApiNotificationMiddleware)); + try { + await next(context); + } + finally + { + apiNotification.Latency = timer.ElapsedTime; + try { - await next(context); - } - finally - { - apiNotification.Latency = timer.ElapsedTime; + IFhirRequestContext fhirRequestContext = _fhirRequestContextAccessor.RequestContext; - try + // For now, we will only emit metrics for audited actions (e.g., metadata will not emit metrics). + if (fhirRequestContext?.AuditEventType != null) { - IFhirRequestContext fhirRequestContext = _fhirRequestContextAccessor.RequestContext; + apiNotification.Authentication = fhirRequestContext.Principal?.Identity.AuthenticationType; + apiNotification.FhirOperation = fhirRequestContext.AuditEventType; + apiNotification.Protocol = context.Request.Scheme; + apiNotification.ResourceType = fhirRequestContext.ResourceType; + apiNotification.StatusCode = (HttpStatusCode)context.Response.StatusCode; - // For now, we will only emit metrics for audited actions (e.g., metadata will not emit metrics). - if (fhirRequestContext?.AuditEventType != null) - { - apiNotification.Authentication = fhirRequestContext.Principal?.Identity.AuthenticationType; - apiNotification.FhirOperation = fhirRequestContext.AuditEventType; - apiNotification.Protocol = context.Request.Scheme; - apiNotification.ResourceType = fhirRequestContext.ResourceType; - apiNotification.StatusCode = (HttpStatusCode)context.Response.StatusCode; - - await _mediator.Publish(apiNotification, CancellationToken.None); - } - } - catch (Exception e) - { - // Failures in publishing API notifications should not cause the API to return an error. - _logger.LogCritical(e, "Failure while publishing API notification."); + await _mediator.Publish(apiNotification, CancellationToken.None); } } + catch (Exception e) + { + // Failures in publishing API notifications should not cause the API to return an error. + _logger.LogCritical(e, "Failure while publishing API notification."); + } } } } diff --git a/src/Microsoft.Health.Fhir.Api/Features/Audit/AuditHelper.cs b/src/Microsoft.Health.Fhir.Api/Features/Audit/AuditHelper.cs index 4a0f2ce931..a2e37db190 100644 --- a/src/Microsoft.Health.Fhir.Api/Features/Audit/AuditHelper.cs +++ b/src/Microsoft.Health.Fhir.Api/Features/Audit/AuditHelper.cs @@ -146,9 +146,9 @@ private void Log(AuditAction auditAction, HttpStatusCode? statusCode, HttpContex /// Return all the values of constants of the specified type /// /// List of constant values - private static IList GetAnonymousOperations() + private static List GetAnonymousOperations() { - IList anonymousOperations = new List(); + List anonymousOperations = new List(); FieldInfo[] fieldInfos = typeof(FhirAnonymousOperationType).GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy); // Go through the list and only pick out the constants diff --git a/src/Microsoft.Health.Fhir.Api/Features/Binders/DictionaryExpansionConfigurationProvider.cs b/src/Microsoft.Health.Fhir.Api/Features/Binders/DictionaryExpansionConfigurationProvider.cs index 72164af0dd..e203040b32 100644 --- a/src/Microsoft.Health.Fhir.Api/Features/Binders/DictionaryExpansionConfigurationProvider.cs +++ b/src/Microsoft.Health.Fhir.Api/Features/Binders/DictionaryExpansionConfigurationProvider.cs @@ -70,7 +70,7 @@ public static bool TryParseDictionaryJson(string value, out Dictionary SetContentLocationHeader(th var url = urlResolver.ResolveOperationResultUrl(operationName, id); - result.Headers.Add(HeaderNames.ContentLocation, url.ToString()); + result.Headers[HeaderNames.ContentLocation] = url.ToString(); return result; } @@ -31,7 +31,7 @@ public static ResourceActionResult SetContentTypeHeader(this R EnsureArg.IsNotNull(result, nameof(result)); EnsureArg.IsNotNullOrWhiteSpace(contentTypeValue, nameof(contentTypeValue)); - result.Headers.Add(HeaderNames.ContentType, contentTypeValue); + result.Headers[HeaderNames.ContentType] = contentTypeValue; return result; } } diff --git a/src/Microsoft.Health.Fhir.Api/Features/Throttling/ThrottlingMiddleware.cs b/src/Microsoft.Health.Fhir.Api/Features/Throttling/ThrottlingMiddleware.cs index 6732d908d1..947f39ff9d 100644 --- a/src/Microsoft.Health.Fhir.Api/Features/Throttling/ThrottlingMiddleware.cs +++ b/src/Microsoft.Health.Fhir.Api/Features/Throttling/ThrottlingMiddleware.cs @@ -17,6 +17,7 @@ using Microsoft.Health.Fhir.Api.Configs; using Microsoft.Health.Fhir.Api.Features.Headers; using Microsoft.Health.Fhir.Core.Configs; +using Microsoft.Health.Fhir.Core.Extensions; using Microsoft.Health.Fhir.Core.Features; using Microsoft.Health.Fhir.Core.Features.Operations; @@ -315,7 +316,7 @@ private async Task Return429FromRequestRateExceededException(RequestRateExceeded public async ValueTask DisposeAsync() { - _cancellationTokenSource.Cancel(); + await _cancellationTokenSource.CancelAsync(); await _samplingLoopTask; _cancellationTokenSource.Dispose(); _samplingLoopTask.Dispose(); diff --git a/src/Microsoft.Health.Fhir.Api/Registration/FhirServerApplicationBuilderExtensions.cs b/src/Microsoft.Health.Fhir.Api/Registration/FhirServerApplicationBuilderExtensions.cs index f336bdccfb..da761c230e 100644 --- a/src/Microsoft.Health.Fhir.Api/Registration/FhirServerApplicationBuilderExtensions.cs +++ b/src/Microsoft.Health.Fhir.Api/Registration/FhirServerApplicationBuilderExtensions.cs @@ -61,10 +61,7 @@ public PathBaseMiddleware(RequestDelegate next, PathString pathBase) public async Task Invoke(HttpContext context) { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } + ArgumentNullException.ThrowIfNull(context, nameof(context)); var originalPathBase = context.Request.PathBase; context.Request.PathBase = originalPathBase.Add(_pathBase); diff --git a/src/Microsoft.Health.Fhir.Azure/AnonymizationConfigurationArtifactProvider.cs b/src/Microsoft.Health.Fhir.Azure/AnonymizationConfigurationArtifactProvider.cs index 185774c3cb..7a4ddb91c5 100644 --- a/src/Microsoft.Health.Fhir.Azure/AnonymizationConfigurationArtifactProvider.cs +++ b/src/Microsoft.Health.Fhir.Azure/AnonymizationConfigurationArtifactProvider.cs @@ -4,6 +4,7 @@ // ------------------------------------------------------------------------------------------------- using System; +using System.Collections.Generic; using System.Globalization; using System.IdentityModel.Tokens.Jwt; using System.IO; @@ -39,7 +40,7 @@ public class AnonymizationConfigurationArtifactProvider : IArtifactProvider, IDi private readonly ILogger _logger; private readonly IExportClientInitializer _exportClientInitializer; private readonly ExportJobConfiguration _exportJobConfiguration; - private IOciArtifactProvider _anonymizationConfigurationCollectionProvider; + private OciArtifactProvider _anonymizationConfigurationCollectionProvider; private IContainerRegistryTokenProvider _containerRegistryTokenProvider; private BlobServiceClient _blobClient; @@ -113,26 +114,24 @@ async Task TokenEntryFactory(ICacheEntry entry) throw new AnonymizationConfigurationFetchException(Resources.AnonymizationConfigurationCollectionTooLarge); } - using (var str = new MemoryStream(acrImage.Blobs[i].Content)) + using var str = new MemoryStream(acrImage.Blobs[i].Content); + Dictionary blobsDict = StreamUtility.DecompressFromTarGz(str); + if (!blobsDict.TryGetValue(configName, out byte[] value)) { - var blobsDict = StreamUtility.DecompressFromTarGz(str); - if (!blobsDict.ContainsKey(configName)) + continue; + } + else + { + configFound = true; + if (CheckConfigurationIsTooLarge(value.LongLength)) { - continue; + throw new AnonymizationConfigurationFetchException(Resources.AnonymizationConfigurationTooLarge); } - else + + using (var config = new MemoryStream(value)) { - configFound = true; - if (CheckConfigurationIsTooLarge(blobsDict[configName].LongLength)) - { - throw new AnonymizationConfigurationFetchException(Resources.AnonymizationConfigurationTooLarge); - } - - using (var config = new MemoryStream(blobsDict[configName])) - { - await config.CopyToAsync(targetStream, cancellationToken); - break; - } + await config.CopyToAsync(targetStream, cancellationToken); + break; } } } diff --git a/src/Microsoft.Health.Fhir.Azure/IntegrationDataStore/AzureAccessTokenClientInitializerV2.cs b/src/Microsoft.Health.Fhir.Azure/IntegrationDataStore/AzureAccessTokenClientInitializerV2.cs index 01e34f1c59..ee92f52a03 100644 --- a/src/Microsoft.Health.Fhir.Azure/IntegrationDataStore/AzureAccessTokenClientInitializerV2.cs +++ b/src/Microsoft.Health.Fhir.Azure/IntegrationDataStore/AzureAccessTokenClientInitializerV2.cs @@ -87,7 +87,7 @@ public Task GetAuthorizedClientAsync(IntegrationDataStoreConf } } - private static TokenCredential CreateDefaultTokenCredential() + private static DefaultAzureCredential CreateDefaultTokenCredential() { return new DefaultAzureCredential(); } diff --git a/src/Microsoft.Health.Fhir.Core.UnitTests/Features/Operations/Export/CancelExportRequestHandlerTests.cs b/src/Microsoft.Health.Fhir.Core.UnitTests/Features/Operations/Export/CancelExportRequestHandlerTests.cs index 0b5d3a577b..bcc1e40aa4 100644 --- a/src/Microsoft.Health.Fhir.Core.UnitTests/Features/Operations/Export/CancelExportRequestHandlerTests.cs +++ b/src/Microsoft.Health.Fhir.Core.UnitTests/Features/Operations/Export/CancelExportRequestHandlerTests.cs @@ -10,7 +10,6 @@ using MediatR; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Health.Core.Internal; using Microsoft.Health.Extensions.DependencyInjection; using Microsoft.Health.Fhir.Core.Extensions; using Microsoft.Health.Fhir.Core.Features.Operations; @@ -70,6 +69,7 @@ public async Task GivenAFhirMediator_WhenCancelingExistingExportJobThatHasAlread Assert.Null(outcome.JobRecord.CanceledTime); } +#if NET8_0_OR_GREATER [Theory] [InlineData(OperationStatus.Queued)] [InlineData(OperationStatus.Running)] @@ -79,7 +79,8 @@ public async Task GivenAFhirMediator_WhenCancelingExistingExportJobThatHasNotCom var instant = new DateTimeOffset(2019, 5, 3, 22, 45, 15, TimeSpan.FromMinutes(-60)); - using (Mock.Property(() => ClockResolver.UtcNowFunc, () => instant)) + Microsoft.Extensions.Time.Testing.FakeTimeProvider timeProvider = new(instant); + using (Mock.Property(() => ClockResolver.TimeProvider, timeProvider)) { outcome = await SetupAndExecuteCancelExportAsync(operationStatus, HttpStatusCode.Accepted); } @@ -90,6 +91,7 @@ public async Task GivenAFhirMediator_WhenCancelingExistingExportJobThatHasNotCom await _fhirOperationDataStore.Received(1).UpdateExportJobAsync(outcome.JobRecord, outcome.ETag, _cancellationToken); } +#endif [Fact] public async Task GivenAFhirMediator_WhenCancelingExistingExportJobEncountersJobConflictException_ThenItWillBeRetried() diff --git a/src/Microsoft.Health.Fhir.Core.UnitTests/Features/Operations/Export/ExportJobTaskTests.cs b/src/Microsoft.Health.Fhir.Core.UnitTests/Features/Operations/Export/ExportJobTaskTests.cs index 156afbbfe8..ac8351455c 100644 --- a/src/Microsoft.Health.Fhir.Core.UnitTests/Features/Operations/Export/ExportJobTaskTests.cs +++ b/src/Microsoft.Health.Fhir.Core.UnitTests/Features/Operations/Export/ExportJobTaskTests.cs @@ -17,10 +17,10 @@ using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Microsoft.Health.Core.Features.Context; -using Microsoft.Health.Core.Internal; using Microsoft.Health.Extensions.DependencyInjection; using Microsoft.Health.Fhir.Core.Configs; using Microsoft.Health.Fhir.Core.Exceptions; +using Microsoft.Health.Fhir.Core.Extensions; using Microsoft.Health.Fhir.Core.Features; using Microsoft.Health.Fhir.Core.Features.Context; using Microsoft.Health.Fhir.Core.Features.Operations; @@ -391,6 +391,40 @@ private Expression>>> CreateQueryP arg.Any(x => x.Item1 == "ct" && x.Item2 == continuationToken); } +#if NET8_0_OR_GREATER + [Fact] + public async Task GivenStorageAccountConnectionDidNotChange_WhenExecuted_ThenJobShouldBeCompleted() + { + ExportJobConfiguration exportJobConfiguration = new ExportJobConfiguration(); + exportJobConfiguration.StorageAccountConnection = "connection"; + exportJobConfiguration.StorageAccountUri = string.Empty; + + var exportJobRecordWithConnection = CreateExportJobRecord( + exportJobType: ExportJobType.Patient, + storageAccountConnectionHash: Microsoft.Health.Core.Extensions.StringExtensions.ComputeHash(exportJobConfiguration.StorageAccountConnection)); + SetupExportJobRecordAndOperationDataStore(exportJobRecordWithConnection); + + var exportJobTask = CreateExportJobTask(exportJobConfiguration); + + _searchService.SearchAsync( + Arg.Any(), + Arg.Any>>(), + _cancellationToken, + true) + .Returns(x => CreateSearchResult()); + + DateTimeOffset endTimestamp = DateTimeOffset.UtcNow; + + using (Mock.Property(() => ClockResolver.TimeProvider, new Microsoft.Extensions.Time.Testing.FakeTimeProvider(endTimestamp))) + { + await exportJobTask.ExecuteAsync(_exportJobRecord, _weakETag, _cancellationToken); + } + + Assert.NotNull(_lastExportJobOutcome); + Assert.Equal(OperationStatus.Completed, _lastExportJobOutcome.JobRecord.Status); + Assert.Equal(endTimestamp, _lastExportJobOutcome.JobRecord.EndTime); + } + [Fact] public async Task GivenSearchSucceeds_WhenExecuted_ThenJobStatusShouldBeUpdatedToCompleted() { @@ -403,7 +437,7 @@ public async Task GivenSearchSucceeds_WhenExecuted_ThenJobStatusShouldBeUpdatedT DateTimeOffset endTimestamp = DateTimeOffset.UtcNow; - using (Mock.Property(() => ClockResolver.UtcNowFunc, () => endTimestamp)) + using (Mock.Property(() => ClockResolver.TimeProvider, new Microsoft.Extensions.Time.Testing.FakeTimeProvider(endTimestamp))) { await _exportJobTask.ExecuteAsync(_exportJobRecord, _weakETag, _cancellationToken); } @@ -428,7 +462,7 @@ public async Task GivenSearchFailed_WhenExecuted_ThenJobStatusShouldBeUpdatedToF DateTimeOffset endTimestamp = DateTimeOffset.UtcNow; - using (Mock.Property(() => ClockResolver.UtcNowFunc, () => endTimestamp)) + using (Mock.Property(() => ClockResolver.TimeProvider, new Microsoft.Extensions.Time.Testing.FakeTimeProvider(endTimestamp))) { await _exportJobTask.ExecuteAsync(_exportJobRecord, _weakETag, _cancellationToken); } @@ -438,6 +472,7 @@ public async Task GivenSearchFailed_WhenExecuted_ThenJobStatusShouldBeUpdatedToF Assert.Equal(endTimestamp, _lastExportJobOutcome.JobRecord.EndTime); Assert.False(string.IsNullOrWhiteSpace(_lastExportJobOutcome.JobRecord.FailureDetails.FailureReason)); } +#endif [Fact] public async Task GivenSearchHadIssues_WhenExecuted_ThenIssuesAreRecorded() @@ -524,39 +559,6 @@ public async Task GivenConnectingToDestinationFails_WhenExecuted_ThenJobStatusSh Assert.Equal(HttpStatusCode.BadRequest, _lastExportJobOutcome.JobRecord.FailureDetails.FailureStatusCode); } - [Fact] - public async Task GivenStorageAccountConnectionDidNotChange_WhenExecuted_ThenJobShouldBeCompleted() - { - ExportJobConfiguration exportJobConfiguration = new ExportJobConfiguration(); - exportJobConfiguration.StorageAccountConnection = "connection"; - exportJobConfiguration.StorageAccountUri = string.Empty; - - var exportJobRecordWithConnection = CreateExportJobRecord( - exportJobType: ExportJobType.Patient, - storageAccountConnectionHash: Microsoft.Health.Core.Extensions.StringExtensions.ComputeHash(exportJobConfiguration.StorageAccountConnection)); - SetupExportJobRecordAndOperationDataStore(exportJobRecordWithConnection); - - var exportJobTask = CreateExportJobTask(exportJobConfiguration); - - _searchService.SearchAsync( - Arg.Any(), - Arg.Any>>(), - _cancellationToken, - true) - .Returns(x => CreateSearchResult()); - - DateTimeOffset endTimestamp = DateTimeOffset.UtcNow; - - using (Mock.Property(() => ClockResolver.UtcNowFunc, () => endTimestamp)) - { - await exportJobTask.ExecuteAsync(_exportJobRecord, _weakETag, _cancellationToken); - } - - Assert.NotNull(_lastExportJobOutcome); - Assert.Equal(OperationStatus.Completed, _lastExportJobOutcome.JobRecord.Status); - Assert.Equal(endTimestamp, _lastExportJobOutcome.JobRecord.EndTime); - } - [Fact] public async Task GivenStorageAccountConnectionChanged_WhenExecuted_ThenJobStatusShouldBeUpdatedToFailed() { diff --git a/src/Microsoft.Health.Fhir.Core.UnitTests/Features/Search/Registry/SearchParameterStatusManagerTests.cs b/src/Microsoft.Health.Fhir.Core.UnitTests/Features/Search/Registry/SearchParameterStatusManagerTests.cs index a53f201877..6bdf7a6849 100644 --- a/src/Microsoft.Health.Fhir.Core.UnitTests/Features/Search/Registry/SearchParameterStatusManagerTests.cs +++ b/src/Microsoft.Health.Fhir.Core.UnitTests/Features/Search/Registry/SearchParameterStatusManagerTests.cs @@ -11,6 +11,7 @@ using MediatR; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Health.Core; +using Microsoft.Health.Fhir.Core.Extensions; using Microsoft.Health.Fhir.Core.Features.Definition; using Microsoft.Health.Fhir.Core.Features.Search.Parameters; using Microsoft.Health.Fhir.Core.Features.Search.Registry; diff --git a/src/Microsoft.Health.Fhir.Core.UnitTests/Microsoft.Health.Fhir.Core.UnitTests.csproj b/src/Microsoft.Health.Fhir.Core.UnitTests/Microsoft.Health.Fhir.Core.UnitTests.csproj index 4e86ac736a..dec942973d 100644 --- a/src/Microsoft.Health.Fhir.Core.UnitTests/Microsoft.Health.Fhir.Core.UnitTests.csproj +++ b/src/Microsoft.Health.Fhir.Core.UnitTests/Microsoft.Health.Fhir.Core.UnitTests.csproj @@ -20,6 +20,9 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/src/Microsoft.Health.Fhir.Core/Extensions/CancellationTokenSourceExtensions.cs b/src/Microsoft.Health.Fhir.Core/Extensions/CancellationTokenSourceExtensions.cs new file mode 100644 index 0000000000..844564b7ec --- /dev/null +++ b/src/Microsoft.Health.Fhir.Core/Extensions/CancellationTokenSourceExtensions.cs @@ -0,0 +1,20 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Health.Fhir.Core.Extensions; + +#if NET6_0 +public static class CancellationTokenSourceExtensions +{ + public static Task CancelAsync(this CancellationTokenSource cancellationTokenSource) + { + cancellationTokenSource.Cancel(); + return Task.CompletedTask; + } +} +#endif diff --git a/src/Microsoft.Health.Fhir.Core/Extensions/Clock.cs b/src/Microsoft.Health.Fhir.Core/Extensions/Clock.cs new file mode 100644 index 0000000000..c3b53fd111 --- /dev/null +++ b/src/Microsoft.Health.Fhir.Core/Extensions/Clock.cs @@ -0,0 +1,27 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +using System; + +namespace Microsoft.Health.Fhir.Core.Extensions; + +#if NET8_0_OR_GREATER + +/// +/// Clock has been removed in Shared Components in favor of .NET8 TimeProvider. +/// This class provides a wrapper to the TimeProvider to maintain the same interface while we continue to support .net6 +/// +public static class Clock +{ + public static DateTimeOffset UtcNow + { + get + { + return ClockResolver.TimeProvider.GetUtcNow(); + } + } +} + +#endif diff --git a/src/Microsoft.Health.Fhir.Core/Extensions/ClockResolver.cs b/src/Microsoft.Health.Fhir.Core/Extensions/ClockResolver.cs new file mode 100644 index 0000000000..48c12554a4 --- /dev/null +++ b/src/Microsoft.Health.Fhir.Core/Extensions/ClockResolver.cs @@ -0,0 +1,17 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +using System; + +namespace Microsoft.Health.Fhir.Core.Extensions; + +#if NET8_0_OR_GREATER + +public static class ClockResolver +{ + public static TimeProvider TimeProvider { get; set; } = TimeProvider.System; +} + +#endif diff --git a/src/Microsoft.Health.Fhir.Core/Extensions/SearchServiceExtensions.cs b/src/Microsoft.Health.Fhir.Core/Extensions/SearchServiceExtensions.cs index 5eba435a71..d9f5b8c9a2 100644 --- a/src/Microsoft.Health.Fhir.Core/Extensions/SearchServiceExtensions.cs +++ b/src/Microsoft.Health.Fhir.Core/Extensions/SearchServiceExtensions.cs @@ -53,7 +53,7 @@ public static class SearchServiceExtensions Microsoft.Extensions.Logging.ILogger logger = null) { // Filters search parameters that can limit the number of results (e.g. _count=1) - IList> filteredParameters = conditionalParameters + List> filteredParameters = conditionalParameters .Where(x => !_excludedParameters.Contains(x.Item1, StringComparer.OrdinalIgnoreCase)) .ToList(); diff --git a/src/Microsoft.Health.Fhir.Core/Features/Conformance/CapabilityStatementBuilder.cs b/src/Microsoft.Health.Fhir.Core/Features/Conformance/CapabilityStatementBuilder.cs index b857e4b9c7..a82e3370f3 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Conformance/CapabilityStatementBuilder.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Conformance/CapabilityStatementBuilder.cs @@ -133,7 +133,7 @@ public ICapabilityStatementBuilder ApplyToResource(string resourceType, Action { @@ -177,7 +177,7 @@ public ICapabilityStatementBuilder AddGlobalSearchParameters() return this; } - private ICapabilityStatementBuilder SyncSearchParamsAsync(string resourceType) + private CapabilityStatementBuilder SyncSearchParamsAsync(string resourceType) { EnsureArg.IsNotNullOrEmpty(resourceType, nameof(resourceType)); EnsureArg.IsTrue(_modelInfoProvider.IsKnownResource(resourceType), nameof(resourceType), x => GenerateTypeErrorMessage(x, resourceType)); @@ -243,7 +243,7 @@ private ICapabilityStatementBuilder SyncSearchParamsAsync(string resourceType) return this; } - private ICapabilityStatementBuilder SyncProfile(string resourceType, bool disableCacheRefresh) + private CapabilityStatementBuilder SyncProfile(string resourceType, bool disableCacheRefresh) { EnsureArg.IsNotNullOrEmpty(resourceType, nameof(resourceType)); EnsureArg.IsTrue(_modelInfoProvider.IsKnownResource(resourceType), nameof(resourceType), x => GenerateTypeErrorMessage(x, resourceType)); diff --git a/src/Microsoft.Health.Fhir.Core/Features/Conformance/Models/DefaultOptionHashSet.cs b/src/Microsoft.Health.Fhir.Core/Features/Conformance/Models/DefaultOptionHashSet.cs index 5cf2b4a548..528e33d838 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Conformance/Models/DefaultOptionHashSet.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Conformance/Models/DefaultOptionHashSet.cs @@ -7,7 +7,6 @@ namespace Microsoft.Health.Fhir.Core.Features.Conformance.Models { - [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1710:Identifiers should have correct suffix", Justification = "Should be consistent with base type.")] internal class DefaultOptionHashSet : HashSet, IDefaultOption { public DefaultOptionHashSet(T defaultOption) diff --git a/src/Microsoft.Health.Fhir.Core/Features/Conformance/SystemConformanceProvider.cs b/src/Microsoft.Health.Fhir.Core/Features/Conformance/SystemConformanceProvider.cs index a2fef4a867..db5b5a0533 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Conformance/SystemConformanceProvider.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Conformance/SystemConformanceProvider.cs @@ -264,7 +264,7 @@ public async ValueTask DisposeAsync() if (!_cancellationTokenSource.IsCancellationRequested) { - _cancellationTokenSource.Cancel(); + await _cancellationTokenSource.CancelAsync(); } if (_rebuilder != null) diff --git a/src/Microsoft.Health.Fhir.Core/Features/Definition/SearchParameterDefinitionBuilder.cs b/src/Microsoft.Health.Fhir.Core/Features/Definition/SearchParameterDefinitionBuilder.cs index 4916427848..6f18bded37 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Definition/SearchParameterDefinitionBuilder.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Definition/SearchParameterDefinitionBuilder.cs @@ -27,7 +27,7 @@ namespace Microsoft.Health.Fhir.Core.Features.Definition { internal static class SearchParameterDefinitionBuilder { - private static readonly ISet _missingExpressionsInR5 = new HashSet + private static readonly HashSet _missingExpressionsInR5 = new HashSet { new("http://hl7.org/fhir/SearchParameter/EvidenceVariable-topic"), new("http://hl7.org/fhir/SearchParameter/ImagingStudy-reason"), @@ -99,7 +99,7 @@ internal static BundleWrapper ReadEmbeddedSearchParameters( { using Stream stream = modelInfoProvider.OpenVersionedFileStream(embeddedResourceName, embeddedResourceNamespace, assembly); - using TextReader reader = new StreamReader(stream); + using var reader = new StreamReader(stream); var data = reader.ReadToEnd(); var rawResource = new RawResource(data, FhirResourceFormat.Json, true); diff --git a/src/Microsoft.Health.Fhir.Core/Features/Operations/ConvertData/ContainerRegistryTemplateProvider.cs b/src/Microsoft.Health.Fhir.Core/Features/Operations/ConvertData/ContainerRegistryTemplateProvider.cs index 3f3c84ec5f..14df25f4da 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Operations/ConvertData/ContainerRegistryTemplateProvider.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Operations/ConvertData/ContainerRegistryTemplateProvider.cs @@ -26,8 +26,7 @@ public class ContainerRegistryTemplateProvider : IConvertDataTemplateProvider, I { private bool _disposed = false; private readonly IContainerRegistryTokenProvider _containerRegistryTokenProvider; - private readonly ITemplateCollectionProviderFactory _templateCollectionProviderFactory; - private readonly ConvertDataConfiguration _convertDataConfig; + private readonly TemplateCollectionProviderFactory _templateCollectionProviderFactory; private readonly MemoryCache _cache; private readonly MemoryCache _templateProviderCache; private readonly SemaphoreSlim _templateProviderFactorySemaphore; @@ -39,19 +38,18 @@ public ContainerRegistryTemplateProvider( ILogger logger) { EnsureArg.IsNotNull(containerRegistryTokenProvider, nameof(containerRegistryTokenProvider)); - EnsureArg.IsNotNull(convertDataConfig, nameof(convertDataConfig)); + EnsureArg.IsNotNull(convertDataConfig?.Value, nameof(convertDataConfig)); EnsureArg.IsNotNull(logger, nameof(logger)); _containerRegistryTokenProvider = containerRegistryTokenProvider; - _convertDataConfig = convertDataConfig.Value; _logger = logger; // Initialize cache and template collection provider factory _cache = new MemoryCache(new MemoryCacheOptions { - SizeLimit = _convertDataConfig.CacheSizeLimit, + SizeLimit = convertDataConfig.Value.CacheSizeLimit, }); - _templateCollectionProviderFactory = new TemplateCollectionProviderFactory(_cache, Options.Create(_convertDataConfig.TemplateCollectionOptions)); + _templateCollectionProviderFactory = new TemplateCollectionProviderFactory(_cache, Options.Create(convertDataConfig.Value.TemplateCollectionOptions)); _templateProviderCache = new MemoryCache(new MemoryCacheOptions()); _templateProviderFactorySemaphore = new SemaphoreSlim(1, 1); @@ -111,22 +109,22 @@ First try to get the template provider from the cache. // Remove token from cache when authentication failed. _cache.Remove(GetCacheKey(request.RegistryServer)); - _logger.LogWarning(authEx, "Failed to access container registry."); + _logger.LogWarning(authEx, "Failed to access container registry"); throw new ContainerRegistryNotAuthorizedException(string.Format(Core.Resources.ContainerRegistryNotAuthorized, request.RegistryServer), authEx); } catch (ImageFetchException fetchEx) { - _logger.LogWarning(fetchEx, "Failed to fetch template image."); + _logger.LogWarning(fetchEx, "Failed to fetch template image"); throw new FetchTemplateCollectionFailedException(string.Format(Core.Resources.FetchTemplateCollectionFailed, fetchEx.Message), fetchEx); } catch (TemplateManagementException templateEx) { - _logger.LogWarning(templateEx, "Template collection is invalid."); + _logger.LogWarning(templateEx, "Template collection is invalid"); throw new TemplateCollectionErrorException(string.Format(Core.Resources.FetchTemplateCollectionFailed, templateEx.Message), templateEx); } catch (Exception unhandledEx) { - _logger.LogError(unhandledEx, "Unhandled exception: failed to get template collection."); + _logger.LogError(unhandledEx, "Unhandled exception: failed to get template collection"); throw new FetchTemplateCollectionFailedException(string.Format(Core.Resources.FetchTemplateCollectionFailed, unhandledEx.Message), unhandledEx); } } diff --git a/src/Microsoft.Health.Fhir.Core/Features/Operations/ConvertData/DefaultTemplateProvider.cs b/src/Microsoft.Health.Fhir.Core/Features/Operations/ConvertData/DefaultTemplateProvider.cs index 05b4a8e55f..19dbaab203 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Operations/ConvertData/DefaultTemplateProvider.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Operations/ConvertData/DefaultTemplateProvider.cs @@ -25,12 +25,12 @@ public class DefaultTemplateProvider : IConvertDataTemplateProvider, IDisposable private bool _disposed = false; private readonly ILogger _logger; private readonly MemoryCache _cache; - private readonly ITemplateCollectionProviderFactory _templateCollectionProviderFactory; + private readonly TemplateCollectionProviderFactory _templateCollectionProviderFactory; private readonly ConvertDataConfiguration _convertDataConfig; public DefaultTemplateProvider( IOptions convertDataConfig, - ILogger logger) + ILogger logger) { EnsureArg.IsNotNull(convertDataConfig, nameof(convertDataConfig)); EnsureArg.IsNotNull(logger, nameof(logger)); @@ -44,6 +44,7 @@ public DefaultTemplateProvider( { SizeLimit = _convertDataConfig.CacheSizeLimit, }); + _templateCollectionProviderFactory = new TemplateCollectionProviderFactory(_cache, Options.Create(_convertDataConfig.TemplateCollectionOptions)); } @@ -57,7 +58,7 @@ public async Task>> GetTemplateCollectionAsync { var accessToken = string.Empty; - _logger.LogInformation("Using the default template collection for data conversion."); + _logger.LogInformation("Using the default template collection for data conversion"); try { @@ -66,12 +67,12 @@ public async Task>> GetTemplateCollectionAsync } catch (TemplateManagementException templateEx) { - _logger.LogWarning(templateEx, "Template collection is invalid."); + _logger.LogWarning(templateEx, "Template collection is invalid"); throw new TemplateCollectionErrorException(string.Format(Core.Resources.FetchTemplateCollectionFailed, templateEx.Message), templateEx); } catch (Exception unhandledEx) { - _logger.LogError(unhandledEx, "Unhandled exception: failed to get template collection."); + _logger.LogError(unhandledEx, "Unhandled exception: failed to get template collection"); throw new FetchTemplateCollectionFailedException(string.Format(Core.Resources.FetchTemplateCollectionFailed, unhandledEx.Message), unhandledEx); } } diff --git a/src/Microsoft.Health.Fhir.Core/Features/Operations/Export/CancelExportRequestHandler.cs b/src/Microsoft.Health.Fhir.Core/Features/Operations/Export/CancelExportRequestHandler.cs index 5e6417062a..dde615e2b5 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Operations/Export/CancelExportRequestHandler.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Operations/Export/CancelExportRequestHandler.cs @@ -13,6 +13,7 @@ using Microsoft.Health.Core; using Microsoft.Health.Core.Features.Security.Authorization; using Microsoft.Health.Fhir.Core.Exceptions; +using Microsoft.Health.Fhir.Core.Extensions; using Microsoft.Health.Fhir.Core.Features.Operations.Export.Models; using Microsoft.Health.Fhir.Core.Features.Security; using Microsoft.Health.Fhir.Core.Messages.Export; diff --git a/src/Microsoft.Health.Fhir.Core/Features/Operations/Export/CreateExportRequestHandler.cs b/src/Microsoft.Health.Fhir.Core/Features/Operations/Export/CreateExportRequestHandler.cs index 77f4c506c6..8bbb210cb3 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Operations/Export/CreateExportRequestHandler.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Operations/Export/CreateExportRequestHandler.cs @@ -112,7 +112,7 @@ public async Task Handle(CreateExportRequest request, Canc /// /// The _typeFilter parameter input. /// A list of - private static IList ParseFilter(string filterString) + private static List ParseFilter(string filterString) { var filters = new List(); @@ -121,7 +121,7 @@ private static IList ParseFilter(string filterString) var filterArray = filterString.Split(","); foreach (string filter in filterArray) { - var parameterIndex = filter.IndexOf("?", StringComparison.Ordinal); + var parameterIndex = filter.IndexOf('?', StringComparison.Ordinal); if (parameterIndex < 0 || parameterIndex == filter.Length - 1) { diff --git a/src/Microsoft.Health.Fhir.Core/Features/Operations/Export/ExportFileManager.cs b/src/Microsoft.Health.Fhir.Core/Features/Operations/Export/ExportFileManager.cs index 6ec7c20f16..57e683aa35 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Operations/Export/ExportFileManager.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Operations/Export/ExportFileManager.cs @@ -21,7 +21,7 @@ internal class ExportFileManager { private readonly ExportJobRecord _exportJobRecord; private readonly IExportDestinationClient _exportDestinationClient; - private readonly IDictionary _resourceTypeToFileInfoMapping; + private readonly Dictionary _resourceTypeToFileInfoMapping; private readonly uint _approxMaxFileSizeInBytes; private bool _isInitialized = false; private Dictionary _resourceCommited = new Dictionary(); @@ -162,14 +162,13 @@ private ExportFileInfo CreateNewFileAndUpdateMappings(string resourceType, int f } // Update internal mapping with new file for the resource type. - if (_resourceTypeToFileInfoMapping.ContainsKey(resourceType)) + if (!_resourceTypeToFileInfoMapping.TryAdd(resourceType, newFile)) { _resourceTypeToFileInfoMapping[resourceType] = newFile; _resourceCommited[resourceType] = false; } else { - _resourceTypeToFileInfoMapping.Add(resourceType, newFile); _resourceCommited.Add(resourceType, false); } diff --git a/src/Microsoft.Health.Fhir.Core/Features/Operations/Export/ExportJobTask.cs b/src/Microsoft.Health.Fhir.Core/Features/Operations/Export/ExportJobTask.cs index 773edaf223..6dc0c046e9 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Operations/Export/ExportJobTask.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Operations/Export/ExportJobTask.cs @@ -22,6 +22,7 @@ using Microsoft.Health.Extensions.DependencyInjection; using Microsoft.Health.Fhir.Core.Configs; using Microsoft.Health.Fhir.Core.Exceptions; +using Microsoft.Health.Fhir.Core.Extensions; using Microsoft.Health.Fhir.Core.Features.Context; using Microsoft.Health.Fhir.Core.Features.Operations.Export.ExportDestinationClient; using Microsoft.Health.Fhir.Core.Features.Operations.Export.Models; diff --git a/src/Microsoft.Health.Fhir.Core/Features/Operations/Export/Models/ExportJobRecord.cs b/src/Microsoft.Health.Fhir.Core/Features/Operations/Export/Models/ExportJobRecord.cs index 18ff1e12f4..21b81dda35 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Operations/Export/Models/ExportJobRecord.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Operations/Export/Models/ExportJobRecord.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using EnsureThat; using Microsoft.Health.Core; +using Microsoft.Health.Fhir.Core.Extensions; using Microsoft.Health.Fhir.Core.Models; using Microsoft.Health.JobManagement; using Newtonsoft.Json; diff --git a/src/Microsoft.Health.Fhir.Core/Features/Operations/Import/ImportErrorStore.cs b/src/Microsoft.Health.Fhir.Core/Features/Operations/Import/ImportErrorStore.cs index dc8df249bd..48bec833c3 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Operations/Import/ImportErrorStore.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Operations/Import/ImportErrorStore.cs @@ -41,6 +41,7 @@ public ImportErrorStore(IIntegrationDataStoreClient integrationDataStoreClient, /// /// New import errors /// Cancellaltion Token + [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2016:Forward the 'CancellationToken' parameter to methods", Justification = ".NET 6/8 compat")] public async Task UploadErrorsAsync(string[] importErrors, CancellationToken cancellationToken) { if (importErrors == null || importErrors.Length == 0) @@ -67,7 +68,7 @@ public async Task UploadErrorsAsync(string[] importErrors, CancellationToken can } catch (Exception ex) { - _logger.LogWarning("Failed to upload import error log.", ex); + _logger.LogWarning(ex, "Failed to upload import error log."); throw new RetriableJobException(ex.Message, ex); } } diff --git a/src/Microsoft.Health.Fhir.Core/Features/Operations/Import/ImportResourceLoader.cs b/src/Microsoft.Health.Fhir.Core/Features/Operations/Import/ImportResourceLoader.cs index be49d170bf..50d4776385 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Operations/Import/ImportResourceLoader.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Operations/Import/ImportResourceLoader.cs @@ -77,10 +77,7 @@ private async Task LoadResourcesInternalAsync(Channel outputChan while ((currentBytesRead <= bytesToRead) && !string.IsNullOrEmpty(content = await reader.ReadLineAsync())) #pragma warning restore CA2016 { - if (cancellationToken.IsCancellationRequested) - { - throw new OperationCanceledException(); - } + cancellationToken.ThrowIfCancellationRequested(); if (offset > 0 && skipFirstLine) // skip first line { @@ -126,7 +123,7 @@ private async Task LoadResourcesInternalAsync(Channel outputChan } } - private async Task> ParseImportRawContentAsync(string resourceType, IList<(string content, long index, int length)> rawContents, long offset, ImportMode importMode) + private async Task> ParseImportRawContentAsync(string resourceType, List<(string content, long index, int length)> rawContents, long offset, ImportMode importMode) { return await Task.Run(() => { diff --git a/src/Microsoft.Health.Fhir.Core/Features/Operations/Reindex/CancelReindexRequestHandler.cs b/src/Microsoft.Health.Fhir.Core/Features/Operations/Reindex/CancelReindexRequestHandler.cs index ad539fd972..66cdd65776 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Operations/Reindex/CancelReindexRequestHandler.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Operations/Reindex/CancelReindexRequestHandler.cs @@ -12,6 +12,7 @@ using Microsoft.Health.Core; using Microsoft.Health.Core.Features.Security.Authorization; using Microsoft.Health.Fhir.Core.Exceptions; +using Microsoft.Health.Fhir.Core.Extensions; using Microsoft.Health.Fhir.Core.Features.Operations.Reindex.Models; using Microsoft.Health.Fhir.Core.Features.Security; using Microsoft.Health.Fhir.Core.Messages.Reindex; diff --git a/src/Microsoft.Health.Fhir.Core/Features/Operations/Reindex/Models/ReindexJobRecord.cs b/src/Microsoft.Health.Fhir.Core/Features/Operations/Reindex/Models/ReindexJobRecord.cs index e470c72d6c..5ecf7b33b9 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Operations/Reindex/Models/ReindexJobRecord.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Operations/Reindex/Models/ReindexJobRecord.cs @@ -9,6 +9,7 @@ using System.Linq; using EnsureThat; using Microsoft.Health.Core; +using Microsoft.Health.Fhir.Core.Extensions; using Microsoft.Health.Fhir.Core.Features.Persistence; using Microsoft.Health.Fhir.Core.Features.Search; using Microsoft.Health.Fhir.Core.Models; diff --git a/src/Microsoft.Health.Fhir.Core/Features/Operations/Reindex/ReindexJobTask.cs b/src/Microsoft.Health.Fhir.Core/Features/Operations/Reindex/ReindexJobTask.cs index 37e7078855..3085cf3a62 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Operations/Reindex/ReindexJobTask.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Operations/Reindex/ReindexJobTask.cs @@ -19,6 +19,7 @@ using Microsoft.Health.Extensions.DependencyInjection; using Microsoft.Health.Fhir.Core.Configs; using Microsoft.Health.Fhir.Core.Exceptions; +using Microsoft.Health.Fhir.Core.Extensions; using Microsoft.Health.Fhir.Core.Features.Context; using Microsoft.Health.Fhir.Core.Features.Definition; using Microsoft.Health.Fhir.Core.Features.Operations.Reindex.Models; @@ -197,7 +198,7 @@ private async Task TryPopulateNewJobFields(CancellationToken cancellationT var resourceList = new HashSet(); // filter list of SearchParameters by the target resource types - if (_reindexJobRecord.TargetResourceTypes.Any()) + if (_reindexJobRecord.TargetResourceTypes.Count > 0) { foreach (var searchParam in possibleNotYetIndexedParams) { @@ -251,7 +252,7 @@ private async Task TryPopulateNewJobFields(CancellationToken cancellationT } // if there are not any parameters which are supported but not yet indexed, then we have nothing to do - if (!notYetIndexedParams.Any() && resourceList.Count == 0) + if (notYetIndexedParams.Count == 0 && resourceList.Count == 0) { _reindexJobRecord.Error.Add(new OperationOutcomeIssue( OperationOutcomeConstants.IssueSeverity.Information, @@ -312,7 +313,7 @@ private async Task HandleException(Exception ex) _reindexJobRecord.FailureCount++; - _logger.LogError(ex, "Encountered an unhandled exception. The job failure count increased to {FailureCount}, id: {Id}.", _reindexJobRecord.FailureCount); + _logger.LogError(ex, "Encountered an unhandled exception. The job failure count increased to {FailureCount}.", _reindexJobRecord.FailureCount); if (_reindexJobRecord.FailureCount >= _reindexJobConfiguration.ConsecutiveFailuresThreshold) { @@ -332,6 +333,7 @@ private async Task HandleException(Exception ex) } } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1849:Call async methods when in an async method", Justification = "tokenSource.CancelAsync(false) doesn't exist.")] private async Task ProcessJob() { var queryTasks = new List>(); @@ -879,6 +881,7 @@ private async Task UpdateParametersAndCompleteJob(CancellationToken cancellation } } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1859:Use concrete types when possible for improved performance", Justification = "Collection defined on model")] private ICollection GetDerivedResourceTypes(IReadOnlyCollection resourceTypes) { var completeResourceList = new HashSet(resourceTypes); diff --git a/src/Microsoft.Health.Fhir.Core/Features/Persistence/ResourceWrapper.cs b/src/Microsoft.Health.Fhir.Core/Features/Persistence/ResourceWrapper.cs index 5eec7f570c..bbabd83f3a 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Persistence/ResourceWrapper.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Persistence/ResourceWrapper.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using EnsureThat; using Microsoft.Health.Core; +using Microsoft.Health.Fhir.Core.Extensions; using Microsoft.Health.Fhir.Core.Features.Search; using Microsoft.Health.Fhir.Core.Models; using Newtonsoft.Json; diff --git a/src/Microsoft.Health.Fhir.Core/Features/Search/Access/ExpressionAccessControl.cs b/src/Microsoft.Health.Fhir.Core/Features/Search/Access/ExpressionAccessControl.cs index c848ecb8d0..d7ba5f809a 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Search/Access/ExpressionAccessControl.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Search/Access/ExpressionAccessControl.cs @@ -66,7 +66,7 @@ public void CheckAndRaiseAccessExceptions(Expression expression) } } - private static bool ResourceTypeAllowedByClinicalScopes(ICollection validResourceTypes, string resourceType) + private static bool ResourceTypeAllowedByClinicalScopes(HashSet validResourceTypes, string resourceType) { if (validResourceTypes != null && (validResourceTypes.Contains(KnownResourceTypes.All) || validResourceTypes.Contains(resourceType))) { diff --git a/src/Microsoft.Health.Fhir.Core/Features/Search/Converters/ResourceReferenceToReferenceSearchValueConverter.cs b/src/Microsoft.Health.Fhir.Core/Features/Search/Converters/ResourceReferenceToReferenceSearchValueConverter.cs index 3b79061eeb..741ab3d268 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Search/Converters/ResourceReferenceToReferenceSearchValueConverter.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Search/Converters/ResourceReferenceToReferenceSearchValueConverter.cs @@ -37,7 +37,7 @@ protected override IEnumerable Convert(ITypedElement value) } // Contained resources will not be searchable. - if (reference.StartsWith("#", StringComparison.Ordinal) + if (reference.StartsWith('#') || reference.StartsWith("urn:", StringComparison.Ordinal)) { yield break; diff --git a/src/Microsoft.Health.Fhir.Core/Features/Search/Converters/UriToReferenceSearchValueConverter.cs b/src/Microsoft.Health.Fhir.Core/Features/Search/Converters/UriToReferenceSearchValueConverter.cs index 455c758148..118810c8d9 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Search/Converters/UriToReferenceSearchValueConverter.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Search/Converters/UriToReferenceSearchValueConverter.cs @@ -36,7 +36,7 @@ protected override IEnumerable Convert(ITypedElement value) } // Contained resources will not be searchable. - if (uri.StartsWith("#", StringComparison.Ordinal) + if (uri.StartsWith('#') || uri.StartsWith("urn:", StringComparison.Ordinal)) { yield break; diff --git a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/IncludeExpression.cs b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/IncludeExpression.cs index d3dd5abc57..c333de2e4b 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/IncludeExpression.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/IncludeExpression.cs @@ -170,7 +170,7 @@ private IReadOnlyCollection GetRequiredResources() } } - private IReadOnlyCollection GetProducedResources() + private List GetProducedResources() { var producedResources = new List(); diff --git a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/Parsers/ExpressionParser.cs b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/Parsers/ExpressionParser.cs index de285b2e49..730e3255ca 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/Parsers/ExpressionParser.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/Parsers/ExpressionParser.cs @@ -221,7 +221,7 @@ private Expression ParseImpl(string[] resourceTypes, ReadOnlySpan key, str return ParseSearchValueExpression(searchParameter, modifier.ToString(), value); } - private Expression ParseChainedExpression(string[] resourceTypes, SearchParameterInfo searchParameter, string[] targetResourceTypes, ReadOnlySpan remainingKey, string value, bool reversed) + private ChainedExpression ParseChainedExpression(string[] resourceTypes, SearchParameterInfo searchParameter, string[] targetResourceTypes, ReadOnlySpan remainingKey, string value, bool reversed) { // We have more paths after this so this is a chained expression. // Since this is chained expression, the expression must be a reference type. diff --git a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/Parsers/SearchValueExpressionBuilderHelper.cs b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/Parsers/SearchValueExpressionBuilderHelper.cs index 124dd8406a..fe666ee6cc 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/Parsers/SearchValueExpressionBuilderHelper.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Search/Expressions/Parsers/SearchValueExpressionBuilderHelper.cs @@ -9,6 +9,7 @@ using EnsureThat; using Microsoft.Health.Core; using Microsoft.Health.Core.Extensions; +using Microsoft.Health.Fhir.Core.Extensions; using Microsoft.Health.Fhir.Core.Features.Search.SearchValues; using Microsoft.Health.Fhir.ValueSets; diff --git a/src/Microsoft.Health.Fhir.Core/Features/Search/Filters/MissingDataFilterCriteria.cs b/src/Microsoft.Health.Fhir.Core/Features/Search/Filters/MissingDataFilterCriteria.cs index ece2a32f34..ed4d6f5d1b 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Search/Filters/MissingDataFilterCriteria.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Search/Filters/MissingDataFilterCriteria.cs @@ -26,7 +26,7 @@ namespace Microsoft.Health.Fhir.Core.Features.Search.Filters /// public sealed class MissingDataFilterCriteria : IFilterCriteria { - private static readonly IDictionary _requiredStatusElementsByResourceType = new Dictionary() + private static readonly Dictionary _requiredStatusElementsByResourceType = new() { { "AllergyIntolerance", "clinicalStatus" }, { "Condition", "clinicalStatus" }, @@ -51,7 +51,7 @@ private MissingDataFilterCriteria(bool isCriteriaEnabled, bool isSmartRequest) _isSmartRequest = isSmartRequest; } - public static MissingDataFilterCriteria Default => new MissingDataFilterCriteria(isCriteriaEnabled: false, isSmartRequest: false); + public static MissingDataFilterCriteria Default => new(isCriteriaEnabled: false, isSmartRequest: false); public SearchResult Apply(SearchResult searchResult) { @@ -62,8 +62,8 @@ public SearchResult Apply(SearchResult searchResult) return searchResult; } - List finalResults = new List(); - List searchIssues = new List(); + var finalResults = new List(); + var searchIssues = new List(); foreach (SearchResultEntry resultEntry in searchResult.Results) { @@ -156,7 +156,7 @@ private static bool ContainStatusElement(ResourceWrapper resourceWrapper, string private static bool ContainsXmlStatusElement(ResourceWrapper resourceWrapper, string requiredStatusElementName) { - XDocument doc = XDocument.Parse(resourceWrapper.RawResource.Data); + var doc = XDocument.Parse(resourceWrapper.RawResource.Data); List elementsByName = doc.Root.Elements().Where(x => string.Equals(x.Name.LocalName, requiredStatusElementName, StringComparison.OrdinalIgnoreCase)).ToList(); @@ -179,7 +179,7 @@ private static bool ContainsXmlStatusElement(ResourceWrapper resourceWrapper, st private static bool ContainsJsonStatusElement(ResourceWrapper resourceWrapper, string requiredStatusElementName) { - JObject jsonResource = JObject.Parse(resourceWrapper.RawResource.Data); + var jsonResource = JObject.Parse(resourceWrapper.RawResource.Data); if (!jsonResource.ContainsKey(requiredStatusElementName)) { return false; diff --git a/src/Microsoft.Health.Fhir.Core/Features/Search/Registry/FilebasedSearchParameterStatusDataStore.cs b/src/Microsoft.Health.Fhir.Core/Features/Search/Registry/FilebasedSearchParameterStatusDataStore.cs index cc8a059671..30e68ff4db 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Search/Registry/FilebasedSearchParameterStatusDataStore.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Search/Registry/FilebasedSearchParameterStatusDataStore.cs @@ -11,6 +11,7 @@ using EnsureThat; using Microsoft.Health.Core; using Microsoft.Health.Fhir.Core.Data; +using Microsoft.Health.Fhir.Core.Extensions; using Microsoft.Health.Fhir.Core.Features.Definition; using Microsoft.Health.Fhir.Core.Models; using Newtonsoft.Json; @@ -39,8 +40,8 @@ public async Task> GetSearchP { if (_statusResults == null) { - using Stream stream = _modelInfoProvider.OpenVersionedFileStream("unsupported-search-parameters.json"); - using TextReader reader = new StreamReader(stream); + await using Stream stream = _modelInfoProvider.OpenVersionedFileStream("unsupported-search-parameters.json"); + using var reader = new StreamReader(stream); #pragma warning disable CA2016 var content = await reader.ReadToEndAsync(); #pragma warning restore CA2016 diff --git a/src/Microsoft.Health.Fhir.Core/Features/Search/Registry/SearchParameterStatusManager.cs b/src/Microsoft.Health.Fhir.Core/Features/Search/Registry/SearchParameterStatusManager.cs index 4f9b473acf..44f407689f 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Search/Registry/SearchParameterStatusManager.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Search/Registry/SearchParameterStatusManager.cs @@ -12,6 +12,7 @@ using MediatR; using Microsoft.Extensions.Logging; using Microsoft.Health.Core; +using Microsoft.Health.Fhir.Core.Extensions; using Microsoft.Health.Fhir.Core.Features.Definition; using Microsoft.Health.Fhir.Core.Features.Search.Parameters; using Microsoft.Health.Fhir.Core.Messages.Search; diff --git a/src/Microsoft.Health.Fhir.Core/Features/Search/SearchService.cs b/src/Microsoft.Health.Fhir.Core/Features/Search/SearchService.cs index 5903179883..8446d3310b 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Search/SearchService.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Search/SearchService.cs @@ -12,6 +12,7 @@ using EnsureThat; using Microsoft.Health.Core; using Microsoft.Health.Fhir.Core.Exceptions; +using Microsoft.Health.Fhir.Core.Extensions; using Microsoft.Health.Fhir.Core.Features.Persistence; using Microsoft.Health.Fhir.Core.Models; diff --git a/src/Microsoft.Health.Fhir.Core/Features/Search/StringExtensions.cs b/src/Microsoft.Health.Fhir.Core/Features/Search/StringExtensions.cs index c92ff1733e..ea99f2d738 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Search/StringExtensions.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Search/StringExtensions.cs @@ -150,11 +150,11 @@ public static string UnescapeSearchParameterValue(this string s) return s; } - private static IReadOnlyList Split(string s, char separator) + private static List Split(string s, char separator) { EnsureArg.IsNotNull(s, nameof(s)); - List results = new List(); + var results = new List(); bool isEscaping = false; diff --git a/src/Microsoft.Health.Fhir.Core/Features/Search/TypedElementSearchIndexer.cs b/src/Microsoft.Health.Fhir.Core/Features/Search/TypedElementSearchIndexer.cs index 8bb9a7c8c6..0ac194cc46 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Search/TypedElementSearchIndexer.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Search/TypedElementSearchIndexer.cs @@ -189,7 +189,7 @@ private IEnumerable ProcessNonCompositeSearchParameter(SearchP } } - private IReadOnlyList ExtractSearchValues( + private List ExtractSearchValues( string searchParameterDefinitionUrl, SearchParamType? searchParameterType, IReadOnlyList allowedReferenceResourceTypes, diff --git a/src/Microsoft.Health.Fhir.Core/Features/Validation/Narratives/NarrativeHtmlSanitizer.cs b/src/Microsoft.Health.Fhir.Core/Features/Validation/Narratives/NarrativeHtmlSanitizer.cs index ac234a1039..e86efc1318 100644 --- a/src/Microsoft.Health.Fhir.Core/Features/Validation/Narratives/NarrativeHtmlSanitizer.cs +++ b/src/Microsoft.Health.Fhir.Core/Features/Validation/Narratives/NarrativeHtmlSanitizer.cs @@ -22,7 +22,7 @@ public class NarrativeHtmlSanitizer : INarrativeHtmlSanitizer { private readonly ILogger _logger; - private static readonly ISet AllowedElements = new HashSet(StringComparer.OrdinalIgnoreCase) + private static readonly HashSet AllowedElements = new(StringComparer.OrdinalIgnoreCase) { // https://www.hl7.org/fhir/narrative-definitions.html#Narrative.div "a", @@ -75,7 +75,7 @@ public class NarrativeHtmlSanitizer : INarrativeHtmlSanitizer "var", }; - private static readonly ISet AllowedAttributes = new HashSet(StringComparer.OrdinalIgnoreCase) + private static readonly HashSet AllowedAttributes = new(StringComparer.OrdinalIgnoreCase) { // https://www.hl7.org/fhir/narrative-definitions.html#Narrative.div "abbr", @@ -140,7 +140,7 @@ public class NarrativeHtmlSanitizer : INarrativeHtmlSanitizer }; // Obvious invalid structural parsing errors to report - private static readonly ISet RaiseErrorTypes = new HashSet + private static readonly HashSet RaiseErrorTypes = new HashSet { HtmlParseError.AmbiguousOpenTag, HtmlParseError.BogusComment, diff --git a/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Storage/CosmosFhirDataStoreTests.cs b/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Storage/CosmosFhirDataStoreTests.cs index 8458f0f55b..4da108a6e6 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Storage/CosmosFhirDataStoreTests.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Storage/CosmosFhirDataStoreTests.cs @@ -19,7 +19,6 @@ using Microsoft.Extensions.Options; using Microsoft.Health.Abstractions.Exceptions; using Microsoft.Health.Core.Features.Context; -using Microsoft.Health.Core.Internal; using Microsoft.Health.Extensions.DependencyInjection; using Microsoft.Health.Fhir.Core.Configs; using Microsoft.Health.Fhir.Core.Extensions; @@ -119,6 +118,7 @@ public async Task GivenAQuery_WhenFetchingSubsequentPagesYieldsA429_ReturnsExist Assert.Equal("token", continuationToken); } +#if NET8_0_OR_GREATER [Fact] public async Task GivenAQuery_WhenFetchingSubsequentPagesTimesOut_ReturnsExistingResults() { @@ -134,7 +134,7 @@ public async Task GivenAQuery_WhenFetchingSubsequentPagesTimesOut_ReturnsExistin _cosmosDataStoreConfiguration.SearchEnumerationTimeoutInSeconds = 0; // lock the time - using (Mock.Property(() => ClockResolver.UtcNowFunc, () => time)) + using (Mock.Property(() => ClockResolver.TimeProvider, new Microsoft.Extensions.Time.Testing.FakeTimeProvider(time))) { (IReadOnlyList results, string continuationToken) = await _dataStore.ExecuteDocumentQueryAsync( @@ -145,6 +145,7 @@ await _dataStore.ExecuteDocumentQueryAsync( Assert.Equal("token", continuationToken); } } +#endif [Fact] public async Task GivenAQueryWhereItemCountCanBeExceeded_WhenExecuted_FetchesSubsequentPages() diff --git a/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Storage/Registry/CosmosDbSearchParameterStatusInitializerTests.cs b/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Storage/Registry/CosmosDbSearchParameterStatusInitializerTests.cs index cac99a769f..df694110f7 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Storage/Registry/CosmosDbSearchParameterStatusInitializerTests.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Storage/Registry/CosmosDbSearchParameterStatusInitializerTests.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Microsoft.Azure.Cosmos; using Microsoft.Health.Core; +using Microsoft.Health.Fhir.Core.Extensions; using Microsoft.Health.Fhir.Core.Features.Search.Registry; using Microsoft.Health.Fhir.CosmosDb.Configs; using Microsoft.Health.Fhir.CosmosDb.Features.Queries; diff --git a/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Storage/Search/ResourceWrapperTests.cs b/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Storage/Search/ResourceWrapperTests.cs index b44ecc4d10..d7e31ba025 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Storage/Search/ResourceWrapperTests.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Features/Storage/Search/ResourceWrapperTests.cs @@ -9,7 +9,6 @@ using System.Net.Http; using Hl7.Fhir.Model; using Hl7.Fhir.Serialization; -using Microsoft.Health.Core.Internal; using Microsoft.Health.Fhir.Core.Extensions; using Microsoft.Health.Fhir.Core.Features.Persistence; using Microsoft.Health.Fhir.Core.Models; @@ -65,6 +64,7 @@ public void GivenAResourceWrapper_WhenConvertingToAHistoryObject_ThenTheCorrectP Assert.Equal("version1", historyRecord.Version); } +#if NET8_0_OR_GREATER [Fact] public void GivenAResource_WhenCreatingAResourceWrapper_ThenMetaPropertiesAreCorrect() { @@ -74,7 +74,7 @@ public void GivenAResource_WhenCreatingAResourceWrapper_ThenMetaPropertiesAreCor observation.Meta.Profile = new List { "test" }; var lastModified = new DateTimeOffset(2017, 1, 1, 1, 1, 1, TimeSpan.Zero); - using (Mock.Property(() => ClockResolver.UtcNowFunc, () => lastModified)) + using (Mock.Property(() => ClockResolver.TimeProvider, new Microsoft.Extensions.Time.Testing.FakeTimeProvider(lastModified))) { ResourceElement typedElement = observation.ToResourceElement(); @@ -88,5 +88,6 @@ public void GivenAResource_WhenCreatingAResourceWrapper_ThenMetaPropertiesAreCor Assert.Equal("test", poco.Meta.Profile.First()); } } +#endif } } diff --git a/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Microsoft.Health.Fhir.CosmosDb.UnitTests.csproj b/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Microsoft.Health.Fhir.CosmosDb.UnitTests.csproj index 36c08d6ab3..e2af38051a 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Microsoft.Health.Fhir.CosmosDb.UnitTests.csproj +++ b/src/Microsoft.Health.Fhir.CosmosDb.UnitTests/Microsoft.Health.Fhir.CosmosDb.UnitTests.csproj @@ -6,6 +6,9 @@ + + + diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Operations/Reindex/ReindexJobCosmosThrottleController.cs b/src/Microsoft.Health.Fhir.CosmosDb/Features/Operations/Reindex/ReindexJobCosmosThrottleController.cs index fca670e9ae..cae209b843 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Operations/Reindex/ReindexJobCosmosThrottleController.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Features/Operations/Reindex/ReindexJobCosmosThrottleController.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.Primitives; using Microsoft.Health.Core; using Microsoft.Health.Core.Features.Context; +using Microsoft.Health.Fhir.Core.Extensions; using Microsoft.Health.Fhir.Core.Features.Context; using Microsoft.Health.Fhir.Core.Features.Operations; using Microsoft.Health.Fhir.Core.Features.Operations.Reindex; diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosDbCollectionPhysicalPartitionInfo.cs b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosDbCollectionPhysicalPartitionInfo.cs index ff54542b2a..2c7d315501 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosDbCollectionPhysicalPartitionInfo.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosDbCollectionPhysicalPartitionInfo.cs @@ -17,6 +17,7 @@ using Microsoft.Extensions.Options; using Microsoft.Health.Abstractions.Exceptions; using Microsoft.Health.Extensions.DependencyInjection; +using Microsoft.Health.Fhir.Core.Extensions; using Microsoft.Health.Fhir.CosmosDb.Configs; namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage @@ -70,7 +71,7 @@ private async Task BackgroundLoop(CancellationToken cancellationToken) if (newPartitionCount != PhysicalPartitionCount) { - _logger.LogInformation("Physical partition count changed from {OldPhysicalPartitionCount} to {NewPhysicalPartitionCount}.", PhysicalPartitionCount, newPartitionCount); + _logger.LogInformation("Physical partition count changed from {OldPhysicalPartitionCount} to {NewPhysicalPartitionCount}", PhysicalPartitionCount, newPartitionCount); } PhysicalPartitionCount = newPartitionCount; @@ -81,7 +82,7 @@ private async Task BackgroundLoop(CancellationToken cancellationToken) } catch (Exception e) { - _logger.LogError("Unable to get physical partition count.", e); + _logger.LogError(e, "Unable to get physical partition count"); } } } @@ -162,7 +163,7 @@ public async ValueTask DisposeAsync() { try { - _backgroundLoopCancellationTokenSource.Cancel(); + await _backgroundLoopCancellationTokenSource.CancelAsync(); await _backgroundLoopTask; _backgroundLoopCancellationTokenSource.Dispose(); _backgroundLoopTask.Dispose(); diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosDbDistributedLock.cs b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosDbDistributedLock.cs index 1509ccc908..99433011ab 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosDbDistributedLock.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosDbDistributedLock.cs @@ -12,6 +12,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Health.Abstractions.Exceptions; using Microsoft.Health.Extensions.DependencyInjection; +using Microsoft.Health.Fhir.Core.Extensions; using Newtonsoft.Json; namespace Microsoft.Health.Fhir.CosmosDb.Features.Storage @@ -140,7 +141,8 @@ public async Task ReleaseLock() try { - _keepAliveCancellationSource.Cancel(); + await _keepAliveCancellationSource.CancelAsync(); + try { await _keepAliveTask; diff --git a/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/ActionResults/FhirResultTests.cs b/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/ActionResults/FhirResultTests.cs index 2eb406b831..ca353bd728 100644 --- a/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/ActionResults/FhirResultTests.cs +++ b/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/ActionResults/FhirResultTests.cs @@ -90,9 +90,9 @@ public void GivenAFhirResult_WhenHeadersThatAlreadyExistsInResponseArePassed_The ServiceProvider provider = collection.BuildServiceProvider(); context.HttpContext.RequestServices = provider; - result.Headers.Add("testKey1", "3"); - result.Headers.Add("testKey2", "2"); - context.HttpContext.Response.Headers.Add("testKey2", "1"); + result.Headers["testKey1"] = "3"; + result.Headers["testKey2"] = "2"; + context.HttpContext.Response.Headers["testKey2"] = "1"; result.ExecuteResultAsync(context); diff --git a/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Filters/FhirRequestContextRouteDataPopulatingFilterAttributeTests.cs b/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Filters/FhirRequestContextRouteDataPopulatingFilterAttributeTests.cs index 662af60b7f..45c5d2c73e 100644 --- a/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Filters/FhirRequestContextRouteDataPopulatingFilterAttributeTests.cs +++ b/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Filters/FhirRequestContextRouteDataPopulatingFilterAttributeTests.cs @@ -123,9 +123,7 @@ public void GivenANonResourceActionResult_WhenExecutedAnAction_ThenResourceTypeS [Fact] public void GivenPartialIndexHeader_WhenSearchReqeust_ThenFhirContextPropertySet() { - _httpContext.Request.Headers.Add( - KnownHeaders.PartiallyIndexedParamsHeaderName, - new Microsoft.Extensions.Primitives.StringValues(new string[] { "true" })); + _httpContext.Request.Headers[KnownHeaders.PartiallyIndexedParamsHeaderName] = new Microsoft.Extensions.Primitives.StringValues(new string[] { "true" }); _filterAttribute.OnActionExecuting(_actionExecutingContext); diff --git a/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Filters/ValidateBulkImportRequestFilterAttributeTests.cs b/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Filters/ValidateBulkImportRequestFilterAttributeTests.cs index 33938c55ca..59e53b33e7 100644 --- a/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Filters/ValidateBulkImportRequestFilterAttributeTests.cs +++ b/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Filters/ValidateBulkImportRequestFilterAttributeTests.cs @@ -43,8 +43,8 @@ public void GiveARequestWithInvalidPreferHeader_WhenGettingABulkImportOperationR { var context = CreateContext(); context.HttpContext.Request.Method = "GET"; - context.HttpContext.Request.Headers.Add(PreferHeaderName, preferHeader); - context.HttpContext.Request.Headers.Add(HeaderNames.ContentType, CorrectContentTypeHeaderValue); + context.HttpContext.Request.Headers[PreferHeaderName] = preferHeader; + context.HttpContext.Request.Headers[HeaderNames.ContentType] = CorrectContentTypeHeaderValue; Assert.Throws(() => _filter.OnActionExecuting(context)); } @@ -57,8 +57,8 @@ public void GiveARequestWithInvalidPreferHeader_WhenCreatingABulkImportRequest_T { var context = CreateContext(); context.HttpContext.Request.Method = "POST"; - context.HttpContext.Request.Headers.Add(PreferHeaderName, preferHeader); - context.HttpContext.Request.Headers.Add(HeaderNames.ContentType, CorrectContentTypeHeaderValue); + context.HttpContext.Request.Headers[PreferHeaderName] = preferHeader; + context.HttpContext.Request.Headers[HeaderNames.ContentType] = CorrectContentTypeHeaderValue; Assert.Throws(() => _filter.OnActionExecuting(context)); } @@ -71,8 +71,8 @@ public void GiveARequestWithInvalidPreferHeader_WhenCancelABulkImportRequest_The { var context = CreateContext(); context.HttpContext.Request.Method = "DELETE"; - context.HttpContext.Request.Headers.Add(PreferHeaderName, preferHeader); - context.HttpContext.Request.Headers.Add(HeaderNames.ContentType, CorrectContentTypeHeaderValue); + context.HttpContext.Request.Headers[PreferHeaderName] = preferHeader; + context.HttpContext.Request.Headers[HeaderNames.ContentType] = CorrectContentTypeHeaderValue; Assert.Throws(() => _filter.OnActionExecuting(context)); } @@ -82,7 +82,7 @@ public void GivenARequestWithNoPreferHeader_WhenGettingABulkImportOperationReque { var context = CreateContext(); context.HttpContext.Request.Method = "GET"; - context.HttpContext.Request.Headers.Add(HeaderNames.ContentType, CorrectContentTypeHeaderValue); + context.HttpContext.Request.Headers[HeaderNames.ContentType] = CorrectContentTypeHeaderValue; Assert.Throws(() => _filter.OnActionExecuting(context)); } @@ -92,7 +92,7 @@ public void GivenARequestWithNoPreferHeader_WhenCreatingABulkImportRequest_ThenA { var context = CreateContext(); context.HttpContext.Request.Method = "POST"; - context.HttpContext.Request.Headers.Add(HeaderNames.ContentType, CorrectContentTypeHeaderValue); + context.HttpContext.Request.Headers[HeaderNames.ContentType] = CorrectContentTypeHeaderValue; Assert.Throws(() => _filter.OnActionExecuting(context)); } @@ -102,7 +102,7 @@ public void GivenARequestWithNoPreferHeader_WhenCancelABulkImportRequest_ThenARe { var context = CreateContext(); context.HttpContext.Request.Method = "DELETE"; - context.HttpContext.Request.Headers.Add(HeaderNames.ContentType, CorrectContentTypeHeaderValue); + context.HttpContext.Request.Headers[HeaderNames.ContentType] = CorrectContentTypeHeaderValue; Assert.Throws(() => _filter.OnActionExecuting(context)); } @@ -119,8 +119,8 @@ public void GiveARequestWithInvalidContentTypeHeader_WhenCreatingABulkImportRequ { var context = CreateContext(); context.HttpContext.Request.Method = "POST"; - context.HttpContext.Request.Headers.Add(PreferHeaderName, CorrectPreferHeaderValue); - context.HttpContext.Request.Headers.Add(HeaderNames.ContentType, contentTypeHeader); + context.HttpContext.Request.Headers[PreferHeaderName] = CorrectPreferHeaderValue; + context.HttpContext.Request.Headers[HeaderNames.ContentType] = contentTypeHeader; Assert.Throws(() => _filter.OnActionExecuting(context)); } @@ -130,7 +130,7 @@ public void GivenARequestWithNoContentTypeHeader_WhenCreatingABulkImportRequest_ { var context = CreateContext(); context.HttpContext.Request.Method = "POST"; - context.HttpContext.Request.Headers.Add(PreferHeaderName, CorrectPreferHeaderValue); + context.HttpContext.Request.Headers[PreferHeaderName] = CorrectPreferHeaderValue; Assert.Throws(() => _filter.OnActionExecuting(context)); } @@ -140,7 +140,7 @@ public void GivenARequestWithNoContentTypeHeader_WhenGetABulkImportRequest_ThenT { var context = CreateContext(); context.HttpContext.Request.Method = "GET"; - context.HttpContext.Request.Headers.Add(PreferHeaderName, CorrectPreferHeaderValue); + context.HttpContext.Request.Headers[PreferHeaderName] = CorrectPreferHeaderValue; _filter.OnActionExecuting(context); } @@ -150,7 +150,7 @@ public void GivenARequestWithNoContentTypeHeader_WhenCancelABulkImportRequest_Th { var context = CreateContext(); context.HttpContext.Request.Method = "DELETE"; - context.HttpContext.Request.Headers.Add(PreferHeaderName, CorrectPreferHeaderValue); + context.HttpContext.Request.Headers[PreferHeaderName] = CorrectPreferHeaderValue; _filter.OnActionExecuting(context); } @@ -160,8 +160,8 @@ public void GivenARequestWithCorrectHeader_WhenCreatingABulkImportRequest_ThenTh { var context = CreateContext(); context.HttpContext.Request.Method = "POST"; - context.HttpContext.Request.Headers.Add(PreferHeaderName, CorrectPreferHeaderValue); - context.HttpContext.Request.Headers.Add(HeaderNames.ContentType, CorrectContentTypeHeaderValue); + context.HttpContext.Request.Headers[PreferHeaderName] = CorrectPreferHeaderValue; + context.HttpContext.Request.Headers[HeaderNames.ContentType] = CorrectContentTypeHeaderValue; _filter.OnActionExecuting(context); } diff --git a/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Filters/ValidateContentTypeFilterAttributeTests.cs b/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Filters/ValidateContentTypeFilterAttributeTests.cs index fe84ff4d7f..2e2dde8674 100644 --- a/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Filters/ValidateContentTypeFilterAttributeTests.cs +++ b/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Filters/ValidateContentTypeFilterAttributeTests.cs @@ -75,7 +75,7 @@ public async Task GivenARequestWithAValidFormatQueryStringAndAnEmptyAcceptHeader var actionExecutedDelegate = CreateActionExecutedDelegate(context); context.HttpContext.Request.QueryString = new QueryString($"?_format=json"); - context.HttpContext.Request.Headers.Add("Accept", string.Empty); + context.HttpContext.Request.Headers["Accept"] = string.Empty; await filter.OnActionExecutionAsync(context, actionExecutedDelegate); } @@ -92,7 +92,7 @@ public async Task GivenARequestWithAValidFormatQueryStringAndAValidAcceptHeader_ var actionExecutedDelegate = CreateActionExecutedDelegate(context); context.HttpContext.Request.QueryString = new QueryString($"?_format={requestFormat}"); - context.HttpContext.Request.Headers.Add("Accept", "application/json"); + context.HttpContext.Request.Headers["Accept"] = "application/json"; await filter.OnActionExecutionAsync(context, actionExecutedDelegate); } @@ -129,8 +129,8 @@ public async Task GivenARequestWithAValidFormatQueryStringAndAnInvalidContentTyp context.HttpContext.Request.QueryString = new QueryString($"?_format={requestFormat}"); context.HttpContext.Request.Method = HttpMethod.Post.ToString(); - context.HttpContext.Request.Headers.Add("Content-Type", contentTypeHeader); - context.HttpContext.Request.Headers.Add("Accept", contentTypeHeader); + context.HttpContext.Request.Headers["Content-Type"] = contentTypeHeader; + context.HttpContext.Request.Headers["Accept"] = contentTypeHeader; await Assert.ThrowsAsync(async () => await filter.OnActionExecutionAsync(context, actionExecutedDelegate)); } @@ -153,7 +153,7 @@ public async Task GivenARequestWithAnInvalidFormatQueryStringAndAnInvalidContent context.HttpContext.Request.QueryString = new QueryString($"?_format={requestFormat}"); context.HttpContext.Request.Method = HttpMethod.Post.ToString(); - context.HttpContext.Request.Headers.Add("Content-Type", "application/fhir+xml"); + context.HttpContext.Request.Headers["Content-Type"] = "application/fhir+xml"; await Assert.ThrowsAsync(async () => await filter.OnActionExecutionAsync(context, actionExecutedDelegate)); } @@ -171,7 +171,7 @@ public async Task GivenARequestWithAValidAcceptHeader_WhenValidatingTheContentTy var context = CreateContext(Guid.NewGuid().ToString()); var actionExecutedDelegate = CreateActionExecutedDelegate(context); - context.HttpContext.Request.Headers.Add("Accept", acceptHeader); + context.HttpContext.Request.Headers["Accept"] = acceptHeader; await filter.OnActionExecutionAsync(context, actionExecutedDelegate); } @@ -189,7 +189,7 @@ public async Task GivenARequestWithAnInvalidAcceptHeader_WhenValidatingTheConten var context = CreateContext(Guid.NewGuid().ToString()); var actionExecutedDelegate = CreateActionExecutedDelegate(context); - context.HttpContext.Request.Headers.Add("Accept", acceptHeader); + context.HttpContext.Request.Headers["Accept"] = acceptHeader; await filter.OnActionExecutionAsync(context, actionExecutedDelegate); } @@ -206,7 +206,7 @@ public async Task GivenARequestWithAnInvalidApplicationAcceptHeader_WhenValidati var context = CreateContext(Guid.NewGuid().ToString()); var actionExecutedDelegate = CreateActionExecutedDelegate(context); - context.HttpContext.Request.Headers.Add("Accept", acceptHeader); + context.HttpContext.Request.Headers["Accept"] = acceptHeader; await Assert.ThrowsAsync(async () => await filter.OnActionExecutionAsync(context, actionExecutedDelegate)); } @@ -229,10 +229,10 @@ public async Task GivenARequestWithAnInvalidAcceptHeaderAndAnInvalidContentTypeH var context = CreateContext(Guid.NewGuid().ToString()); var actionExecutedDelegate = CreateActionExecutedDelegate(context); - context.HttpContext.Request.Headers.Add("Accept", acceptHeader); + context.HttpContext.Request.Headers["Accept"] = acceptHeader; context.HttpContext.Request.Method = HttpMethod.Post.ToString(); - context.HttpContext.Request.Headers.Add("Content-Type", contentTypeHeader); + context.HttpContext.Request.Headers["Content-Type"] = contentTypeHeader; // The fact that the Accept header is invalid is ignored, but an exception related to the invalid Content Type header is still thrown. await Assert.ThrowsAsync(async () => await filter.OnActionExecutionAsync(context, actionExecutedDelegate)); @@ -253,10 +253,10 @@ public async Task GivenARequestWithAnInvalidApplicationAcceptHeaderAndAnInvalidC var context = CreateContext(Guid.NewGuid().ToString()); var actionExecutedDelegate = CreateActionExecutedDelegate(context); - context.HttpContext.Request.Headers.Add("Accept", acceptHeader); + context.HttpContext.Request.Headers["Accept"] = acceptHeader; context.HttpContext.Request.Method = HttpMethod.Post.ToString(); - context.HttpContext.Request.Headers.Add("Content-Type", contentTypeHeader); + context.HttpContext.Request.Headers["Content-Type"] = contentTypeHeader; // The Accept header is invalid and has the format "application/", so an exception for that is thrown. await Assert.ThrowsAsync(async () => await filter.OnActionExecutionAsync(context, actionExecutedDelegate)); @@ -274,7 +274,7 @@ public async Task GivenARequestWithNoAcceptHeaderAndAnInvalidContentTypeHeader_W var actionExecutedDelegate = CreateActionExecutedDelegate(context); context.HttpContext.Request.Method = HttpMethod.Post.ToString(); - context.HttpContext.Request.Headers.Add("Content-Type", contentTypeHeader); + context.HttpContext.Request.Headers["Content-Type"] = contentTypeHeader; await Assert.ThrowsAsync(async () => await filter.OnActionExecutionAsync(context, actionExecutedDelegate)); } @@ -291,8 +291,8 @@ public async Task GivenARequestWithAValidAcceptHeaderAndAValidContentTypeHeader_ var actionExecutedDelegate = CreateActionExecutedDelegate(context); context.HttpContext.Request.Method = HttpMethod.Post.ToString(); - context.HttpContext.Request.Headers.Add("Content-Type", contentTypeHeader); - context.HttpContext.Request.Headers.Add("Accept", contentTypeHeader); + context.HttpContext.Request.Headers["Content-Type"] = contentTypeHeader; + context.HttpContext.Request.Headers["Accept"] = contentTypeHeader; await filter.OnActionExecutionAsync(context, actionExecutedDelegate); } @@ -309,10 +309,10 @@ public async Task GivenARequestWithAValidAcceptHeaderAndAnInvalidContentTypeHead var context = CreateContext(Guid.NewGuid().ToString()); var actionExecutedDelegate = CreateActionExecutedDelegate(context); - context.HttpContext.Request.Headers.Add("Accept", "application/json"); + context.HttpContext.Request.Headers["Accept"] = "application/json"; context.HttpContext.Request.Method = HttpMethod.Post.ToString(); - context.HttpContext.Request.Headers.Add("Content-Type", contentTypeHeader); + context.HttpContext.Request.Headers["Content-Type"] = contentTypeHeader; await Assert.ThrowsAsync(async () => await filter.OnActionExecutionAsync(context, actionExecutedDelegate)); } @@ -344,7 +344,7 @@ public async Task GivenACapabilityStatementWithMultipleFormats_WhenValidatingThe var actionExecutedDelegate = CreateActionExecutedDelegate(context); context.HttpContext.Request.QueryString = new QueryString($"?_format={formatQuerystring}"); - context.HttpContext.Request.Headers.Add("Accept", formats[1]); + context.HttpContext.Request.Headers["Accept"] = formats[1]; await filter.OnActionExecutionAsync(context, actionExecutedDelegate); @@ -370,7 +370,7 @@ public async Task GivenARequestWithAValidAcceptHeaderAndFormatOverride_WhenSetti var context = CreateContext(Guid.NewGuid().ToString()); var actionExecutedDelegate = CreateActionExecutedDelegate(context); - context.HttpContext.Request.Headers.Add("Accept", acceptHeader); + context.HttpContext.Request.Headers["Accept"] = acceptHeader; context.HttpContext.Request.Query = new QueryCollection(new Dictionary { { KnownQueryParameterNames.Format, "xml" }, diff --git a/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Filters/ValidateExportRequestFilterAttributeTests.cs b/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Filters/ValidateExportRequestFilterAttributeTests.cs index e53a4333eb..eda913ef64 100644 --- a/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Filters/ValidateExportRequestFilterAttributeTests.cs +++ b/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Filters/ValidateExportRequestFilterAttributeTests.cs @@ -48,7 +48,7 @@ public void GivenARequestWithInvalidAcceptHeader_WhenGettingAnExportOperationReq { var context = CreateContext(); - context.HttpContext.Request.Headers.Add(HeaderNames.Accept, acceptHeader); + context.HttpContext.Request.Headers[HeaderNames.Accept] = acceptHeader; Assert.Throws(() => _filter.OnActionExecuting(context)); } @@ -58,7 +58,7 @@ public void GivenARequestWithNoAcceptHeader_WhenGettingAnExportOperationRequest_ { var context = CreateContext(); - context.HttpContext.Request.Headers.Add(PreferHeaderName, CorrectPreferHeaderValue); + context.HttpContext.Request.Headers[PreferHeaderName] = CorrectPreferHeaderValue; Assert.Throws(() => _filter.OnActionExecuting(context)); } @@ -71,7 +71,7 @@ public void GiveARequestWithInvalidPreferHeader_WhenGettingAnExportOperationRequ { var context = CreateContext(); - context.HttpContext.Request.Headers.Add(PreferHeaderName, preferHeader); + context.HttpContext.Request.Headers[PreferHeaderName] = preferHeader; Assert.Throws(() => _filter.OnActionExecuting(context)); } @@ -81,7 +81,7 @@ public void GivenARequestWithNoPreferHeader_WhenGettingAnExportOperationRequest_ { var context = CreateContext(); - context.HttpContext.Request.Headers.Add(HeaderNames.Accept, CorrectAcceptHeaderValue); + context.HttpContext.Request.Headers[HeaderNames.Accept] = CorrectAcceptHeaderValue; Assert.Throws(() => _filter.OnActionExecuting(context)); } @@ -93,8 +93,8 @@ public void GivenARequestWithNoPreferHeader_WhenGettingAnExportOperationRequest_ public void GivenARequestWithCorrectHeadersAndUnsupportedQueryParam_WhenGettingAnExportOperationRequest_ThenARequestNotValidExceptionShouldBeThrown(string queryParamName) { var context = CreateContext(); - context.HttpContext.Request.Headers.Add(HeaderNames.Accept, CorrectAcceptHeaderValue); - context.HttpContext.Request.Headers.Add(PreferHeaderName, CorrectPreferHeaderValue); + context.HttpContext.Request.Headers[HeaderNames.Accept] = CorrectAcceptHeaderValue; + context.HttpContext.Request.Headers[PreferHeaderName] = CorrectPreferHeaderValue; var queryParams = new Dictionary() { @@ -111,8 +111,8 @@ public void GivenARequestWithCorrectHeadersAndUnsupportedQueryParam_WhenGettingA public void GivenARequestWithAnonymizedExportQueryParam_WhenGettingAnDefaultExportOperationRequest_ThenTheResultIsSuccessful(params string[] queryParamNames) { var context = CreateContext(); - context.HttpContext.Request.Headers.Add(HeaderNames.Accept, CorrectAcceptHeaderValue); - context.HttpContext.Request.Headers.Add(PreferHeaderName, CorrectPreferHeaderValue); + context.HttpContext.Request.Headers[HeaderNames.Accept] = CorrectAcceptHeaderValue; + context.HttpContext.Request.Headers[PreferHeaderName] = CorrectPreferHeaderValue; var queryParams = new Dictionary(); foreach (string queryParamName in queryParamNames) @@ -133,8 +133,8 @@ public void GivenARequestWithAnonymizedExportQueryParam_WhenGettingAnDefaultExpo public void GivenARequestWithCorrectHeaderAndSupportedQueryParam_WhenGettingAnExportOperationRequest_ThenTheResultIsSuccessful(params string[] queryParamNames) { var context = CreateContext(); - context.HttpContext.Request.Headers.Add(HeaderNames.Accept, CorrectAcceptHeaderValue); - context.HttpContext.Request.Headers.Add(PreferHeaderName, CorrectPreferHeaderValue); + context.HttpContext.Request.Headers[HeaderNames.Accept] = CorrectAcceptHeaderValue; + context.HttpContext.Request.Headers[PreferHeaderName] = CorrectPreferHeaderValue; var queryParams = new Dictionary(); foreach (string queryParamName in queryParamNames) @@ -154,8 +154,8 @@ public void GivenARequestWithCorrectHeaderAndSupportedQueryParam_WhenGettingAnEx public void GivenARequestWithCorrectHeaderAndSupportedOutputFormatQueryParam_WhenGettingAnExportOperationRequest_ThenTheResultIsSuccessful(string outputFormat) { var context = CreateContext(); - context.HttpContext.Request.Headers.Add(HeaderNames.Accept, CorrectAcceptHeaderValue); - context.HttpContext.Request.Headers.Add(PreferHeaderName, CorrectPreferHeaderValue); + context.HttpContext.Request.Headers[HeaderNames.Accept] = CorrectAcceptHeaderValue; + context.HttpContext.Request.Headers[PreferHeaderName] = CorrectPreferHeaderValue; var queryParams = new Dictionary(); queryParams.Add(KnownQueryParameterNames.OutputFormat, outputFormat); @@ -169,8 +169,8 @@ public void GivenARequestWithCorrectHeaderAndSupportedOutputFormatQueryParam_Whe public void GivenARequestWithCorrectHeaderAndUnsupportedOutputFormatQueryParam_WhenGettingAnExportOperationRequest_ThenARequestNotValidExceptionShouldBeThrown() { var context = CreateContext(); - context.HttpContext.Request.Headers.Add(HeaderNames.Accept, CorrectAcceptHeaderValue); - context.HttpContext.Request.Headers.Add(PreferHeaderName, CorrectPreferHeaderValue); + context.HttpContext.Request.Headers[HeaderNames.Accept] = CorrectAcceptHeaderValue; + context.HttpContext.Request.Headers[PreferHeaderName] = CorrectPreferHeaderValue; var queryParams = new Dictionary(); queryParams.Add(KnownQueryParameterNames.OutputFormat, "invalid"); @@ -184,8 +184,8 @@ public void GivenARequestWithCorrectHeaderAndUnsupportedOutputFormatQueryParam_W public void GivenARequestWithCorrectHeaderAndNoQueryParams_WhenGettingAnExportOperationRequest_ThenTheResultIsSuccessful() { var context = CreateContext(); - context.HttpContext.Request.Headers.Add(HeaderNames.Accept, CorrectAcceptHeaderValue); - context.HttpContext.Request.Headers.Add(PreferHeaderName, CorrectPreferHeaderValue); + context.HttpContext.Request.Headers[HeaderNames.Accept] = CorrectAcceptHeaderValue; + context.HttpContext.Request.Headers[PreferHeaderName] = CorrectPreferHeaderValue; _filter.OnActionExecuting(context); } diff --git a/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Headers/FhirResultExtensionsTests.cs b/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Headers/FhirResultExtensionsTests.cs index caeb8484a6..14138e3077 100644 --- a/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Headers/FhirResultExtensionsTests.cs +++ b/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Headers/FhirResultExtensionsTests.cs @@ -93,9 +93,11 @@ public void WhenAddingTwoHeaders_ThenFhirResultHasAtLeastTwoHeaders() [Fact] public void WhenAddingSameHeaderTwice_ThenOnlyOneHeaderIsPresent() { - Assert.Throws(() => FhirResult.Create(_mockResource) + var result = FhirResult.Create(_mockResource) .SetLastModifiedHeader() - .SetLastModifiedHeader()); + .SetLastModifiedHeader(); + + Assert.Single(result.Headers); } [Fact] diff --git a/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Security/RequestHandlerCheckAccessTests.cs b/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Security/RequestHandlerCheckAccessTests.cs index c182587f5a..23dfcf1412 100644 --- a/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Security/RequestHandlerCheckAccessTests.cs +++ b/src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Security/RequestHandlerCheckAccessTests.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; @@ -107,7 +108,7 @@ static IEnumerable GetFieldsIncludingFromBaseTypes(Type t) static object CreateObject(Type type) { - return FormatterServices.GetSafeUninitializedObject(type); + return RuntimeHelpers.GetUninitializedObject(type); } } } diff --git a/src/Microsoft.Health.Fhir.Shared.Api/Controllers/BulkDeleteController.cs b/src/Microsoft.Health.Fhir.Shared.Api/Controllers/BulkDeleteController.cs index 0c7f7d88aa..88b94327b8 100644 --- a/src/Microsoft.Health.Fhir.Shared.Api/Controllers/BulkDeleteController.cs +++ b/src/Microsoft.Health.Fhir.Shared.Api/Controllers/BulkDeleteController.cs @@ -76,7 +76,7 @@ public async Task GetBulkDeleteStatusById(long idParameter) var actionResult = JobResult.FromResults(result.Results, result.Issues, result.HttpStatusCode); if (result.HttpStatusCode == System.Net.HttpStatusCode.Accepted) { - actionResult.Headers.Add(KnownHeaders.Progress, Resources.InProgress); + actionResult.Headers[KnownHeaders.Progress] = Resources.InProgress; } return actionResult; diff --git a/src/Microsoft.Health.Fhir.Shared.Api/Controllers/EverythingController.cs b/src/Microsoft.Health.Fhir.Shared.Api/Controllers/EverythingController.cs index 7ca2214e34..3e8bfcc179 100644 --- a/src/Microsoft.Health.Fhir.Shared.Api/Controllers/EverythingController.cs +++ b/src/Microsoft.Health.Fhir.Shared.Api/Controllers/EverythingController.cs @@ -82,7 +82,7 @@ public async Task PatientEverythingById( return FhirResult.Create(result.Bundle); } - private IReadOnlyList> ReadUnsupportedParameters() + private List> ReadUnsupportedParameters() { IReadOnlyList> parameters = Request.Query .SelectMany(query => query.Value, (query, value) => Tuple.Create(query.Key, value)) diff --git a/src/Microsoft.Health.Fhir.Shared.Api/Controllers/FhirController.cs b/src/Microsoft.Health.Fhir.Shared.Api/Controllers/FhirController.cs index 6453762017..5f18b6dab9 100644 --- a/src/Microsoft.Health.Fhir.Shared.Api/Controllers/FhirController.cs +++ b/src/Microsoft.Health.Fhir.Shared.Api/Controllers/FhirController.cs @@ -242,7 +242,7 @@ public async Task ConditionalUpdate([FromBody] Resource resource) return ToSaveOutcomeResult(saveOutcome); } - private IActionResult ToSaveOutcomeResult(SaveOutcome saveOutcome) + private FhirResult ToSaveOutcomeResult(SaveOutcome saveOutcome) { switch (saveOutcome.Outcome) { @@ -444,7 +444,7 @@ public async Task ConditionalDelete(string typeParameter, [FromQu if (maxDeleteCount.HasValue) { - Response.Headers.Add(KnownHeaders.ItemsDeleted, (response?.ResourcesDeleted ?? 0).ToString(CultureInfo.InvariantCulture)); + Response.Headers[KnownHeaders.ItemsDeleted] = (response?.ResourcesDeleted ?? 0).ToString(CultureInfo.InvariantCulture); } return FhirResult.NoContent().SetETagHeader(response?.WeakETag); diff --git a/src/Microsoft.Health.Fhir.Shared.Api/Features/Filters/OperationOutcomeExceptionFilterAttribute.cs b/src/Microsoft.Health.Fhir.Shared.Api/Features/Filters/OperationOutcomeExceptionFilterAttribute.cs index 966f7e27d7..40a74d52f1 100644 --- a/src/Microsoft.Health.Fhir.Shared.Api/Features/Filters/OperationOutcomeExceptionFilterAttribute.cs +++ b/src/Microsoft.Health.Fhir.Shared.Api/Features/Filters/OperationOutcomeExceptionFilterAttribute.cs @@ -82,7 +82,7 @@ public override void OnActionExecuted(ActionExecutedContext context) operationOutcomeResult.StatusCode = HttpStatusCode.Gone; if (!string.IsNullOrEmpty(resourceGoneException.DeletedResource?.VersionId)) { - operationOutcomeResult.Headers.Add(HeaderNames.ETag, WeakETag.FromVersionId(resourceGoneException.DeletedResource.VersionId).ToString()); + operationOutcomeResult.Headers[HeaderNames.ETag] = WeakETag.FromVersionId(resourceGoneException.DeletedResource.VersionId).ToString(); } break; @@ -167,7 +167,7 @@ public override void OnActionExecuted(ActionExecutedContext context) if (!string.IsNullOrEmpty(everythingOperationException.ContentLocationHeaderValue)) { - operationOutcomeResult.Headers.Add(HeaderNames.ContentLocation, everythingOperationException.ContentLocationHeaderValue); + operationOutcomeResult.Headers[HeaderNames.ContentLocation] = everythingOperationException.ContentLocationHeaderValue; } break; diff --git a/src/Microsoft.Health.Fhir.Shared.Api/Features/Formatters/FormatterExtensions.cs b/src/Microsoft.Health.Fhir.Shared.Api/Features/Formatters/FormatterExtensions.cs index 110c78bde7..c6e6280141 100644 --- a/src/Microsoft.Health.Fhir.Shared.Api/Features/Formatters/FormatterExtensions.cs +++ b/src/Microsoft.Health.Fhir.Shared.Api/Features/Formatters/FormatterExtensions.cs @@ -15,7 +15,7 @@ namespace Microsoft.Health.Fhir.Api.Features.Formatters { public static class FormatterExtensions { - private static readonly IDictionary ResourceFormatContentType = new Dictionary + private static readonly Dictionary ResourceFormatContentType = new Dictionary { { ResourceFormat.Json, KnownContentTypes.JsonContentType }, { ResourceFormat.Xml, KnownContentTypes.XmlContentType }, diff --git a/src/Microsoft.Health.Fhir.Shared.Api/Features/Formatters/HtmlOutputFormatter.cs b/src/Microsoft.Health.Fhir.Shared.Api/Features/Formatters/HtmlOutputFormatter.cs index ff63dba370..ef7fc191db 100644 --- a/src/Microsoft.Health.Fhir.Shared.Api/Features/Formatters/HtmlOutputFormatter.cs +++ b/src/Microsoft.Health.Fhir.Shared.Api/Features/Formatters/HtmlOutputFormatter.cs @@ -6,6 +6,7 @@ using System; using System.Buffers; using System.IO; +using System.Linq; using System.Text; using EnsureThat; using Hl7.Fhir.Model; @@ -99,7 +100,11 @@ public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext co jsonTextWriter.ArrayPool = _charPool; jsonTextWriter.Formatting = Formatting.Indented; - await _fhirJsonSerializer.SerializeAsync(resourceInstance, jsonTextWriter, context.HttpContext.GetSummaryTypeOrDefault(), context.HttpContext.GetElementsOrDefault()); + await _fhirJsonSerializer.SerializeAsync( + resourceInstance, + jsonTextWriter, + context.HttpContext.GetSummaryTypeOrDefault(), + context.HttpContext.GetElementsOrDefault().ToArray()); } var viewDictionary = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()) diff --git a/src/Microsoft.Health.Fhir.Shared.Api/Features/Formatters/HttpContextExtensions.cs b/src/Microsoft.Health.Fhir.Shared.Api/Features/Formatters/HttpContextExtensions.cs index 4e3871f3aa..597dc85b95 100644 --- a/src/Microsoft.Health.Fhir.Shared.Api/Features/Formatters/HttpContextExtensions.cs +++ b/src/Microsoft.Health.Fhir.Shared.Api/Features/Formatters/HttpContextExtensions.cs @@ -4,12 +4,14 @@ // ------------------------------------------------------------------------------------------------- using System; +using System.Collections.Generic; using System.Linq; using System.Net; using Hl7.Fhir.Rest; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.Health.Fhir.Core.Features; +using Microsoft.Health.Fhir.Core.Features.Search; namespace Microsoft.Health.Fhir.Api.Features.Formatters { @@ -39,14 +41,14 @@ public static SummaryType GetSummaryTypeOrDefault(this HttpContext context) return SummaryType.False; } - public static string[] GetElementsOrDefault(this HttpContext context) + public static IReadOnlyList GetElementsOrDefault(this HttpContext context) { var query = context.Request.Query[KnownQueryParameterNames.Elements].FirstOrDefault(); if (!string.IsNullOrWhiteSpace(query) && (context.Response.StatusCode == (int)HttpStatusCode.OK || context.Response.StatusCode == (int)HttpStatusCode.Created)) { - var elements = query.Split(new char[1] { ',' }); + IReadOnlyList elements = query.SplitByOrSeparator(); return elements; } @@ -72,7 +74,7 @@ public static bool GetPrettyOrDefault(this HttpContext context) public static void AllowSynchronousIO(this HttpContext context) { - var bodyControlFeature = context.Features.Get(); + IHttpBodyControlFeature bodyControlFeature = context.Features.Get(); if (bodyControlFeature != null) { bodyControlFeature.AllowSynchronousIO = true; diff --git a/src/Microsoft.Health.Fhir.Shared.Api/Features/Headers/FhirResultExtensions.cs b/src/Microsoft.Health.Fhir.Shared.Api/Features/Headers/FhirResultExtensions.cs index d8dd8cd28a..c0b012eda1 100644 --- a/src/Microsoft.Health.Fhir.Shared.Api/Features/Headers/FhirResultExtensions.cs +++ b/src/Microsoft.Health.Fhir.Shared.Api/Features/Headers/FhirResultExtensions.cs @@ -3,6 +3,7 @@ // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. // ------------------------------------------------------------------------------------------------- +using System; using System.Globalization; using Microsoft.Health.Fhir.Api.Features.ActionResults; using Microsoft.Health.Fhir.Core.Features.Persistence; @@ -24,7 +25,7 @@ public static FhirResult SetLocationHeader(this FhirResult fhirResult, IUrlResol if (url.IsAbsoluteUri) { - fhirResult.Headers.Add(HeaderNames.Location, url.AbsoluteUri); + fhirResult.Headers[HeaderNames.Location] = url.AbsoluteUri; } } @@ -46,7 +47,7 @@ public static FhirResult SetETagHeader(this FhirResult fhirResult, WeakETag weak { if (weakETag != null) { - fhirResult.Headers.Add(HeaderNames.ETag, weakETag.ToString()); + fhirResult.Headers[HeaderNames.ETag] = weakETag.ToString(); } return fhirResult; @@ -56,10 +57,11 @@ public static FhirResult SetLastModifiedHeader(this FhirResult fhirResult) { IResourceElement resource = fhirResult.Result; - var lastUpdated = resource?.LastUpdated; + DateTimeOffset? lastUpdated = resource?.LastUpdated; + if (lastUpdated != null) { - fhirResult.Headers.Add(HeaderNames.LastModified, lastUpdated.Value.ToString("r", CultureInfo.InvariantCulture)); + fhirResult.Headers[HeaderNames.LastModified] = lastUpdated.Value.ToString("r", CultureInfo.InvariantCulture); } return fhirResult; diff --git a/src/Microsoft.Health.Fhir.Shared.Api/Features/Operations/ParametersExtensions.cs b/src/Microsoft.Health.Fhir.Shared.Api/Features/Operations/ParametersExtensions.cs index bc60e2460c..0d7a23078c 100644 --- a/src/Microsoft.Health.Fhir.Shared.Api/Features/Operations/ParametersExtensions.cs +++ b/src/Microsoft.Health.Fhir.Shared.Api/Features/Operations/ParametersExtensions.cs @@ -33,14 +33,14 @@ public static bool TryGetStringValue(this Parameters.ParameterComponent paramCom public static bool TryGetBooleanValue(this Parameters.ParameterComponent paramComponent, out bool boolValue) { - Element booleanElement = paramComponent?.Value; + DataType booleanElement = paramComponent?.Value; return bool.TryParse(booleanElement?.ToString(), out boolValue); } public static bool TryGetUriValue(this Parameters.ParameterComponent paramComponent, out Uri uriValue) { - Element uriElement = paramComponent?.Value; + DataType uriElement = paramComponent?.Value; return Uri.TryCreate(uriElement?.ToString(), UriKind.RelativeOrAbsolute, out uriValue); } diff --git a/src/Microsoft.Health.Fhir.Shared.Api/Features/Resources/Bundle/BundleHandler.cs b/src/Microsoft.Health.Fhir.Shared.Api/Features/Resources/Bundle/BundleHandler.cs index 594d6a72dd..d94e4fa43b 100644 --- a/src/Microsoft.Health.Fhir.Shared.Api/Features/Resources/Bundle/BundleHandler.cs +++ b/src/Microsoft.Health.Fhir.Shared.Api/Features/Resources/Bundle/BundleHandler.cs @@ -523,7 +523,7 @@ private async Task GenerateRequest(EntryComponent entry, int order, Cancellation if (requestMethod == HTTPVerb.POST || requestMethod == HTTPVerb.PUT) { - httpContext.Request.Headers.Add(HeaderNames.ContentType, new StringValues(KnownContentTypes.JsonContentType)); + httpContext.Request.Headers[HeaderNames.ContentType] = new StringValues(KnownContentTypes.JsonContentType); if (entry.Resource != null) { @@ -540,7 +540,7 @@ private async Task GenerateRequest(EntryComponent entry, int order, Cancellation string.Equals(KnownResourceTypes.Parameters, entry.Resource?.TypeName, StringComparison.Ordinal) && entry.Resource is Parameters parametersResource) { - httpContext.Request.Headers.Add(HeaderNames.ContentType, new StringValues(KnownContentTypes.JsonContentType)); + httpContext.Request.Headers[HeaderNames.ContentType] = new StringValues(KnownContentTypes.JsonContentType); var memoryStream = new MemoryStream(await _fhirJsonSerializer.SerializeToBytesAsync(parametersResource)); memoryStream.Seek(0, SeekOrigin.Begin); httpContext.Request.Body = memoryStream; @@ -552,7 +552,7 @@ private async Task GenerateRequest(EntryComponent entry, int order, Cancellation string.Equals(KnownResourceTypes.Binary, entry.Resource?.TypeName, StringComparison.Ordinal) && entry.Resource is Binary binaryResource && string.Equals(KnownMediaTypeHeaderValues.ApplicationJsonPatch.ToString(), binaryResource.ContentType, StringComparison.OrdinalIgnoreCase)) { - httpContext.Request.Headers.Add(HeaderNames.ContentType, new StringValues(binaryResource.ContentType)); + httpContext.Request.Headers[HeaderNames.ContentType] = new StringValues(binaryResource.ContentType); var memoryStream = new MemoryStream(binaryResource.Data); memoryStream.Seek(0, SeekOrigin.Begin); httpContext.Request.Body = memoryStream; @@ -575,7 +575,7 @@ private static void AddHeaderIfNeeded(string headerKey, string headerValue, Http { if (!string.IsNullOrWhiteSpace(headerValue)) { - httpContext.Request.Headers.Add(headerKey, new StringValues(headerValue)); + httpContext.Request.Headers[headerKey] = new StringValues(headerValue); } } @@ -773,7 +773,7 @@ private void SetupContexts(RouteContext request, HttpContext httpContext) } } - private void PopulateReferenceIdDictionary(IEnumerable bundleEntries, IDictionary idDictionary) + private void PopulateReferenceIdDictionary(IEnumerable bundleEntries, Dictionary idDictionary) { foreach (EntryComponent entry in bundleEntries) { diff --git a/src/Microsoft.Health.Fhir.Shared.Api/Features/Resources/Bundle/BundleHandlerParallelOperations.cs b/src/Microsoft.Health.Fhir.Shared.Api/Features/Resources/Bundle/BundleHandlerParallelOperations.cs index f0081a9839..eba0a55b1d 100644 --- a/src/Microsoft.Health.Fhir.Shared.Api/Features/Resources/Bundle/BundleHandlerParallelOperations.cs +++ b/src/Microsoft.Health.Fhir.Shared.Api/Features/Resources/Bundle/BundleHandlerParallelOperations.cs @@ -22,6 +22,7 @@ using Microsoft.Health.Core.Features.Context; using Microsoft.Health.Fhir.Api.Features.Bundle; using Microsoft.Health.Fhir.Api.Features.Routing; +using Microsoft.Health.Fhir.Core.Extensions; using Microsoft.Health.Fhir.Core.Features.Context; using Microsoft.Health.Fhir.Core.Features.Persistence; using Microsoft.Health.Fhir.Core.Features.Persistence.Orchestration; @@ -132,7 +133,7 @@ private async Task ExecuteRequestsInParallelAsync( // In case of a FhirTransactionFailedException, the entire Bundle Operation should be canceled. bundleOperation.Cancel($"Failed transaction. Resource at position {resourceExecutionContext.Index}. Status Code: {ex.ResponseStatusCode}. Message: {ex.Message}"); - requestCancellationToken.Cancel(); + await requestCancellationToken.CancelAsync(); throw; } diff --git a/src/Microsoft.Health.Fhir.Shared.Api/Features/Resources/ProvenanceHeaderBehavior.cs b/src/Microsoft.Health.Fhir.Shared.Api/Features/Resources/ProvenanceHeaderBehavior.cs index 30dd29ae7e..3a18ff8376 100644 --- a/src/Microsoft.Health.Fhir.Shared.Api/Features/Resources/ProvenanceHeaderBehavior.cs +++ b/src/Microsoft.Health.Fhir.Shared.Api/Features/Resources/ProvenanceHeaderBehavior.cs @@ -86,7 +86,7 @@ private async Task GenericHandle(RequestHandlerDelegate< private Provenance GetProvenanceFromHeader() { - if (!_httpContextAccessor.HttpContext.Request.Headers.ContainsKey(KnownHeaders.ProvenanceHeader)) + if (!_httpContextAccessor.HttpContext.Request.Headers.TryGetValue(KnownHeaders.ProvenanceHeader, out Microsoft.Extensions.Primitives.StringValues value)) { return null; } @@ -94,7 +94,7 @@ private Provenance GetProvenanceFromHeader() Provenance provenance; try { - provenance = _fhirJsonParser.Parse(_httpContextAccessor.HttpContext.Request.Headers[KnownHeaders.ProvenanceHeader]); + provenance = _fhirJsonParser.Parse(value); } catch { diff --git a/src/Microsoft.Health.Fhir.Shared.Core.UnitTests/Features/Resources/ResourceHandlerTests.cs b/src/Microsoft.Health.Fhir.Shared.Core.UnitTests/Features/Resources/ResourceHandlerTests.cs index 9f588589fd..7daf3abaf4 100644 --- a/src/Microsoft.Health.Fhir.Shared.Core.UnitTests/Features/Resources/ResourceHandlerTests.cs +++ b/src/Microsoft.Health.Fhir.Shared.Core.UnitTests/Features/Resources/ResourceHandlerTests.cs @@ -16,7 +16,6 @@ using Microsoft.Extensions.Logging; using Microsoft.Health.Core.Features.Context; using Microsoft.Health.Core.Features.Security.Authorization; -using Microsoft.Health.Core.Internal; using Microsoft.Health.Extensions.DependencyInjection; using Microsoft.Health.Fhir.Core.Exceptions; using Microsoft.Health.Fhir.Core.Extensions; @@ -182,6 +181,7 @@ public async Task GivenAFhirMediator_WhenCreatingAResourceAndResourceIdProviderH Assert.Equal("id2", deserializedResource.Id); } +#if NET8_0_OR_GREATER [Fact] public async Task GivenAFhirMediator_WhenSavingAResource_ThenLastUpdatedShouldBeSet() { @@ -189,7 +189,7 @@ public async Task GivenAFhirMediator_WhenSavingAResource_ThenLastUpdatedShouldBe DateTime baseDate = DateTimeOffset.Now.Date; var instant = new DateTimeOffset(baseDate.AddTicks((6 * TimeSpan.TicksPerMillisecond) + (long)(0.7 * TimeSpan.TicksPerMillisecond)), TimeSpan.Zero); - using (Mock.Property(() => ClockResolver.UtcNowFunc, () => instant)) + using (Mock.Property(() => ClockResolver.TimeProvider, new Microsoft.Extensions.Time.Testing.FakeTimeProvider(instant))) { _fhirDataStore.UpsertAsync(Arg.Any(), Arg.Any()) .Returns(x => new UpsertOutcome(x.ArgAt(0).Wrapper, SaveOutcomeType.Created)); @@ -200,6 +200,7 @@ public async Task GivenAFhirMediator_WhenSavingAResource_ThenLastUpdatedShouldBe Assert.Equal(new DateTimeOffset(baseDate.AddMilliseconds(6), TimeSpan.Zero), deserializedResource.LastUpdated); } } +#endif [Fact] public async Task GivenAFhirMediator_WhenSavingAResource_ThenVersionShouldBeSet() diff --git a/src/Microsoft.Health.Fhir.Shared.Core.UnitTests/Features/Search/BundleFactoryTests.cs b/src/Microsoft.Health.Fhir.Shared.Core.UnitTests/Features/Search/BundleFactoryTests.cs index bd9a5546b9..bd1ddaa23c 100644 --- a/src/Microsoft.Health.Fhir.Shared.Core.UnitTests/Features/Search/BundleFactoryTests.cs +++ b/src/Microsoft.Health.Fhir.Shared.Core.UnitTests/Features/Search/BundleFactoryTests.cs @@ -11,7 +11,6 @@ using Hl7.Fhir.Serialization; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Health.Core.Features.Context; -using Microsoft.Health.Core.Internal; using Microsoft.Health.Fhir.Core.Extensions; using Microsoft.Health.Fhir.Core.Features.Context; using Microsoft.Health.Fhir.Core.Features.Persistence; @@ -60,6 +59,7 @@ public BundleFactoryTests() _fhirRequestContextAccessor.RequestContext.Returns(fhirRequestContext); } +#if NET8_0_OR_GREATER [Fact] public void GivenAnEmptySearchResult_WhenCreateSearchBundle_ThenCorrectBundleShouldBeReturned() { @@ -67,7 +67,7 @@ public void GivenAnEmptySearchResult_WhenCreateSearchBundle_ThenCorrectBundleSho ResourceElement actual = null; - using (Mock.Property(() => ClockResolver.UtcNowFunc, () => _dateTime)) + using (Mock.Property(() => ClockResolver.TimeProvider, new Microsoft.Extensions.Time.Testing.FakeTimeProvider(_dateTime))) { actual = _bundleFactory.CreateSearchBundle(new SearchResult(new SearchResultEntry[0], null, null, _unsupportedSearchParameters)); } @@ -100,7 +100,7 @@ public void GivenASearchResult_WhenCreateSearchBundle_ThenCorrectBundleShouldBeR ResourceElement actual = null; - using (Mock.Property(() => ClockResolver.UtcNowFunc, () => _dateTime)) + using (Mock.Property(() => ClockResolver.TimeProvider, new Microsoft.Extensions.Time.Testing.FakeTimeProvider(_dateTime))) { actual = _bundleFactory.CreateSearchBundle(searchResult); } @@ -139,6 +139,7 @@ async Task ValidateEntry(Observation expected, Bundle.EntryComponent actualEntry } } } +#endif private ResourceWrapper CreateResourceWrapper(ResourceElement resourceElement, HttpMethod httpMethod) { diff --git a/src/Microsoft.Health.Fhir.Shared.Core.UnitTests/Features/Search/Expressions/Parsers/SearchValueExpressionBuilderTests.cs b/src/Microsoft.Health.Fhir.Shared.Core.UnitTests/Features/Search/Expressions/Parsers/SearchValueExpressionBuilderTests.cs index e4b2a5391e..62ebccd21c 100644 --- a/src/Microsoft.Health.Fhir.Shared.Core.UnitTests/Features/Search/Expressions/Parsers/SearchValueExpressionBuilderTests.cs +++ b/src/Microsoft.Health.Fhir.Shared.Core.UnitTests/Features/Search/Expressions/Parsers/SearchValueExpressionBuilderTests.cs @@ -7,7 +7,6 @@ using System.Collections.Generic; using System.Linq; using Hl7.Fhir.Model; -using Microsoft.Health.Core.Internal; using Microsoft.Health.Fhir.Core.Extensions; using Microsoft.Health.Fhir.Core.Features.Definition; using Microsoft.Health.Fhir.Core.Features.Search; @@ -367,6 +366,7 @@ public void GivenADateWithComparatorOfSingleBinaryOperator_WhenBuilt_ThenCorrect expectStartTimeValue ? dateTimeSearchValue.Start : dateTimeSearchValue.End)); } +#if NET8_0_OR_GREATER [Theory] [InlineData("2016", "2015-11-25T12:00:00.0000000+00:00", "2017-02-06T11:59:59.9999999+00:00")] [InlineData("2016-02", "2015-11-25T21:36:00.0000000+00:00", "2016-05-07T02:23:59.9999999+00:00")] @@ -380,7 +380,7 @@ public void GivenADateWithComparatorOfSingleBinaryOperator_WhenBuilt_ThenCorrect [InlineData("2220-02-01T10:00-07:00", "2240-04-17T16:18:05.9999999+00:00", "2199-11-16T17:42:54.0000000+00:00")] public void GivenADateWithApComparator_WhenBuilt_ThenCorrectExpressionShouldBeCreated(string dateTimeInput, string expectedStartValue, string expectedEndValue) { - using (Mock.Property(() => ClockResolver.UtcNowFunc, () => DateTimeOffset.Parse("2018-01-01T00:00Z"))) + using (Mock.Property(() => ClockResolver.TimeProvider, new Microsoft.Extensions.Time.Testing.FakeTimeProvider(DateTimeOffset.Parse("2018-01-01T00:00Z")))) { var partialDateTime = PartialDateTime.Parse(dateTimeInput); var dateTimeSearchValue = new DateTimeSearchValue(partialDateTime); @@ -396,6 +396,7 @@ public void GivenADateWithApComparator_WhenBuilt_ThenCorrectExpressionShouldBeCr e1 => ValidateDateTimeBinaryOperatorExpression(e1, FieldName.DateTimeEnd, BinaryOperator.LessThanOrEqual, DateTimeOffset.Parse(expectedEndValue)))); } } +#endif [Theory] [MemberData(nameof(GetAllModifiersExceptMissing))] diff --git a/src/Microsoft.Health.Fhir.Shared.Core/Features/Operations/MemberMatch/MemberMatchService.cs b/src/Microsoft.Health.Fhir.Shared.Core/Features/Operations/MemberMatch/MemberMatchService.cs index d8861f3120..1b508188b8 100644 --- a/src/Microsoft.Health.Fhir.Shared.Core/Features/Operations/MemberMatch/MemberMatchService.cs +++ b/src/Microsoft.Health.Fhir.Shared.Core/Features/Operations/MemberMatch/MemberMatchService.cs @@ -124,14 +124,14 @@ private ResourceElement CreatePatientWithIdentity(ResourceElement patient, Searc return result; } - private Expression CreateSearchExpression(ResourceElement coverage, ResourceElement patient) + private MultiaryExpression CreateSearchExpression(ResourceElement coverage, ResourceElement patient) { - var coverageValues = _searchIndexer.Extract(coverage); - var patientValues = _searchIndexer.Extract(patient); + IReadOnlyCollection coverageValues = _searchIndexer.Extract(coverage); + IReadOnlyCollection patientValues = _searchIndexer.Extract(patient); var expressions = new List(); var reverseChainExpressions = new List(); expressions.Add(Expression.SearchParameter(_resourceTypeSearchParameter, Expression.StringEquals(FieldName.TokenCode, null, KnownResourceTypes.Patient, false))); - foreach (var patientValue in patientValues) + foreach (SearchIndexEntry patientValue in patientValues) { if (IgnoreInSearch(patientValue)) { @@ -175,7 +175,7 @@ private Expression CreateSearchExpression(ResourceElement coverage, ResourceElem reverseChainedExpression = Expression.And(reverseChainExpressions); } - var expression = Expression.Chained(new[] { KnownResourceTypes.Coverage }, _coverageBeneficiaryParameter, new[] { KnownResourceTypes.Patient }, true, reverseChainedExpression); + ChainedExpression expression = Expression.Chained(new[] { KnownResourceTypes.Coverage }, _coverageBeneficiaryParameter, new[] { KnownResourceTypes.Patient }, true, reverseChainedExpression); expressions.Add(expression); } diff --git a/src/Microsoft.Health.Fhir.Shared.Web/DevelopmentIdentityProviderRegistrationExtensions.cs b/src/Microsoft.Health.Fhir.Shared.Web/DevelopmentIdentityProviderRegistrationExtensions.cs index a4dbcd5378..a242a85177 100644 --- a/src/Microsoft.Health.Fhir.Shared.Web/DevelopmentIdentityProviderRegistrationExtensions.cs +++ b/src/Microsoft.Health.Fhir.Shared.Web/DevelopmentIdentityProviderRegistrationExtensions.cs @@ -163,7 +163,7 @@ public static IConfigurationBuilder AddDevelopmentAuthEnvironmentIfConfigured(th return configurationBuilder.Add(new DevelopmentAuthEnvironmentConfigurationSource(testEnvironmentFilePath, existingConfiguration)); } - private static IReadOnlyCollection GenerateSmartClinicalScopes() + private static List GenerateSmartClinicalScopes() { ModelExtensions.SetModelInfoProvider(); var resourceTypes = ModelInfoProvider.Instance.GetResourceTypeNames(); @@ -192,7 +192,7 @@ private static IReadOnlyCollection GenerateSmartClinicalScopes() return scopes; } - private static IEnumerable CreateFhirUserClaims(string userId, string host) + private static ClientClaim[] CreateFhirUserClaims(string userId, string host) { string userType = null; @@ -209,11 +209,11 @@ private static IEnumerable CreateFhirUserClaims(string userId, stri userType = "System"; } - return new ClientClaim[] - { + return + [ new ClientClaim("appid", userId), new ClientClaim("fhirUser", $"{host}{userType}/" + userId), - }; + ]; } private sealed class DevelopmentAuthEnvironmentConfigurationSource : IConfigurationSource diff --git a/src/Microsoft.Health.Fhir.Shared.Web/Startup.cs b/src/Microsoft.Health.Fhir.Shared.Web/Startup.cs index 341e87dfd6..eb6e73cc46 100644 --- a/src/Microsoft.Health.Fhir.Shared.Web/Startup.cs +++ b/src/Microsoft.Health.Fhir.Shared.Web/Startup.cs @@ -189,7 +189,7 @@ public virtual void Configure(IApplicationBuilder app) string instanceKey = KnownHeaders.InstanceId; if (!context.Response.Headers.ContainsKey(instanceKey)) { - context.Response.Headers.Add(instanceKey, new StringValues(instanceId)); + context.Response.Headers[instanceKey] = new StringValues(instanceId); } } @@ -234,6 +234,8 @@ private static void AddAuthenticationLibrary(IServiceCollection services, Securi { options.Authority = securityConfiguration.Authentication.Authority; options.Audience = securityConfiguration.Authentication.Audience; + options.TokenValidationParameters.RoleClaimType = securityConfiguration.Authorization.RolesClaim; + options.MapInboundClaims = false; options.RequireHttpsMetadata = true; options.Challenge = $"Bearer authorization_uri=\"{securityConfiguration.Authentication.Authority}\", resource_id=\"{securityConfiguration.Authentication.Audience}\", realm=\"{securityConfiguration.Authentication.Audience}\""; }); diff --git a/src/Microsoft.Health.Fhir.SqlServer/Features/Operations/Import/ImportOrchestratorJob.cs b/src/Microsoft.Health.Fhir.SqlServer/Features/Operations/Import/ImportOrchestratorJob.cs index b61809b56d..5963401d54 100644 --- a/src/Microsoft.Health.Fhir.SqlServer/Features/Operations/Import/ImportOrchestratorJob.cs +++ b/src/Microsoft.Health.Fhir.SqlServer/Features/Operations/Import/ImportOrchestratorJob.cs @@ -22,6 +22,7 @@ using Microsoft.Health.Core.Features.Audit; using Microsoft.Health.Core.Features.Context; using Microsoft.Health.Fhir.Core.Configs; +using Microsoft.Health.Fhir.Core.Extensions; using Microsoft.Health.Fhir.Core.Features.Audit; using Microsoft.Health.Fhir.Core.Features.Context; using Microsoft.Health.Fhir.Core.Features.Operations; @@ -105,10 +106,7 @@ public async Task ExecuteAsync(JobInfo jobInfo, IProgress progre try { - if (cancellationToken.IsCancellationRequested) - { - throw new OperationCanceledException(); - } + cancellationToken.ThrowIfCancellationRequested(); if (currentResult.Progress == ImportOrchestratorJobProgress.Initialized) { diff --git a/src/Microsoft.Health.Fhir.SqlServer/Features/Operations/Import/ImportProcessingJob.cs b/src/Microsoft.Health.Fhir.SqlServer/Features/Operations/Import/ImportProcessingJob.cs index 4098c6bcff..54e4f4408e 100644 --- a/src/Microsoft.Health.Fhir.SqlServer/Features/Operations/Import/ImportProcessingJob.cs +++ b/src/Microsoft.Health.Fhir.SqlServer/Features/Operations/Import/ImportProcessingJob.cs @@ -69,10 +69,7 @@ public async Task ExecuteAsync(JobInfo jobInfo, IProgress progre try { - if (cancellationToken.IsCancellationRequested) - { - throw new OperationCanceledException(); - } + cancellationToken.ThrowIfCancellationRequested(); // Initialize error store IImportErrorStore importErrorStore = await _importErrorStoreFactory.InitializeAsync(GetErrorFileName(definition.ResourceType, jobInfo.GroupId, jobInfo.Id), cancellationToken); diff --git a/src/Microsoft.Health.Fhir.SqlServer/Features/Operations/Import/SqlImporter.cs b/src/Microsoft.Health.Fhir.SqlServer/Features/Operations/Import/SqlImporter.cs index 6990c08a45..1d55684ae8 100644 --- a/src/Microsoft.Health.Fhir.SqlServer/Features/Operations/Import/SqlImporter.cs +++ b/src/Microsoft.Health.Fhir.SqlServer/Features/Operations/Import/SqlImporter.cs @@ -60,10 +60,7 @@ public async Task Import(Channel input var resourceBuffer = new List(); await foreach (ImportResource resource in inputChannel.Reader.ReadAllAsync(cancellationToken)) { - if (cancellationToken.IsCancellationRequested) - { - throw new OperationCanceledException(); - } + cancellationToken.ThrowIfCancellationRequested(); currentIndex = resource.Index; diff --git a/src/Microsoft.Health.Fhir.SqlServer/Features/Search/CustomQueries.cs b/src/Microsoft.Health.Fhir.SqlServer/Features/Search/CustomQueries.cs index 0aba28935b..f410214127 100644 --- a/src/Microsoft.Health.Fhir.SqlServer/Features/Search/CustomQueries.cs +++ b/src/Microsoft.Health.Fhir.SqlServer/Features/Search/CustomQueries.cs @@ -8,6 +8,7 @@ using System.Data; using Microsoft.Extensions.Logging; using Microsoft.Health.Core; +using Microsoft.Health.Fhir.Core.Extensions; namespace Microsoft.Health.Fhir.SqlServer.Features.Search { @@ -22,7 +23,7 @@ internal static class CustomQueries public static string CheckQueryHash(IDbConnection connection, string hash, ILogger logger) { - var now = Clock.UtcNow; + DateTimeOffset now = Clock.UtcNow; if (now > _lastUpdatedQueryCache.AddSeconds(WaitTime)) { lock (lockObject) diff --git a/src/Microsoft.Health.Fhir.SqlServer/Features/Search/Expressions/Visitors/IncludeRewriter.cs b/src/Microsoft.Health.Fhir.SqlServer/Features/Search/Expressions/Visitors/IncludeRewriter.cs index 1f4bbdb093..e4469f52f3 100644 --- a/src/Microsoft.Health.Fhir.SqlServer/Features/Search/Expressions/Visitors/IncludeRewriter.cs +++ b/src/Microsoft.Health.Fhir.SqlServer/Features/Search/Expressions/Visitors/IncludeRewriter.cs @@ -121,8 +121,10 @@ public IncludeIterateExpressionDependencyGraph(IEnumerable> OutgoingEdges { get; private set; } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1859:Use concrete types when possible for improved performance", Justification = "This is a public signature.")] public IDictionary IncomingEdgesCount { get; private set; } public IEnumerable NodesWithoutIncomingEdges diff --git a/src/Microsoft.Health.Fhir.SqlServer/Features/Search/Expressions/Visitors/SqlRootExpressionRewriter.cs b/src/Microsoft.Health.Fhir.SqlServer/Features/Search/Expressions/Visitors/SqlRootExpressionRewriter.cs index 299bb00145..ea38a1cf7c 100644 --- a/src/Microsoft.Health.Fhir.SqlServer/Features/Search/Expressions/Visitors/SqlRootExpressionRewriter.cs +++ b/src/Microsoft.Health.Fhir.SqlServer/Features/Search/Expressions/Visitors/SqlRootExpressionRewriter.cs @@ -81,7 +81,7 @@ public override Expression VisitMultiary(MultiaryExpression expression, int cont public override Expression VisitChained(ChainedExpression expression, int context) => ConvertNonMultiary(expression); - private Expression ConvertNonMultiary(Expression expression) + private SqlRootExpression ConvertNonMultiary(Expression expression) { if (TryGetSearchParamTableExpressionQueryGenerator(expression, out var generator, out var kind)) { diff --git a/src/Microsoft.Health.Fhir.SqlServer/Features/Search/HashingSqlQueryParameterManager.cs b/src/Microsoft.Health.Fhir.SqlServer/Features/Search/HashingSqlQueryParameterManager.cs index e19f2b20c6..b09a8ce80d 100644 --- a/src/Microsoft.Health.Fhir.SqlServer/Features/Search/HashingSqlQueryParameterManager.cs +++ b/src/Microsoft.Health.Fhir.SqlServer/Features/Search/HashingSqlQueryParameterManager.cs @@ -195,7 +195,11 @@ private static void WriteAndAdvance(Span buffer, ref int currentIndex, Debug.Assert(buffer.Length >= elementLength, "Initial buffer size is not large enough for the datatypes we are trying to write to it"); } +#if NET8_0_OR_GREATER + MemoryMarshal.Write(buffer[currentIndex..], in element); +#else MemoryMarshal.Write(buffer[currentIndex..], ref element); +#endif currentIndex += elementLength; } diff --git a/src/Microsoft.Health.Fhir.SqlServer/Features/Search/SqlCommandSimplifier.cs b/src/Microsoft.Health.Fhir.SqlServer/Features/Search/SqlCommandSimplifier.cs index 897439317b..deead819d2 100644 --- a/src/Microsoft.Health.Fhir.SqlServer/Features/Search/SqlCommandSimplifier.cs +++ b/src/Microsoft.Health.Fhir.SqlServer/Features/Search/SqlCommandSimplifier.cs @@ -51,12 +51,13 @@ private static string RemoveRedundantComparisons(string commandText, SqlParamete foreach (Match match in operatorMatches) { var groups = match.Groups; - if (!fieldToParameterComparisons.ContainsKey(groups[1].Value)) + if (!fieldToParameterComparisons.TryGetValue(groups[1].Value, out List<(string, Match)> value)) { - fieldToParameterComparisons.Add(groups[1].Value, new List<(string, Match)>()); + value = new List<(string, Match)>(); + fieldToParameterComparisons.Add(groups[1].Value, value); } - fieldToParameterComparisons[groups[1].Value].Add((groups[2].Value, match)); + value.Add((groups[2].Value, match)); } foreach (string field in fieldToParameterComparisons.Keys) diff --git a/src/Microsoft.Health.Fhir.SqlServer/Features/Storage/SqlServerFhirDataStore.cs b/src/Microsoft.Health.Fhir.SqlServer/Features/Storage/SqlServerFhirDataStore.cs index af328bb13e..75027013c6 100644 --- a/src/Microsoft.Health.Fhir.SqlServer/Features/Storage/SqlServerFhirDataStore.cs +++ b/src/Microsoft.Health.Fhir.SqlServer/Features/Storage/SqlServerFhirDataStore.cs @@ -574,7 +574,7 @@ private string GetJsonValue(string json, string propName, bool isExisting) } startIndex = startIndex + propName.Length + 4; - var endIndex = json.IndexOf("\"", startIndex, StringComparison.Ordinal); + var endIndex = json.IndexOf('"', startIndex); if (endIndex == -1) { _logger.LogWarning($"Cannot parse {propName} value from {(isExisting ? "existing" : "input")} {json}"); diff --git a/src/Microsoft.Health.Fhir.SqlServer/Features/Watchdogs/DefragWatchdog.cs b/src/Microsoft.Health.Fhir.SqlServer/Features/Watchdogs/DefragWatchdog.cs index 865b4d7cb3..dceaed120b 100644 --- a/src/Microsoft.Health.Fhir.SqlServer/Features/Watchdogs/DefragWatchdog.cs +++ b/src/Microsoft.Health.Fhir.SqlServer/Features/Watchdogs/DefragWatchdog.cs @@ -23,6 +23,7 @@ public sealed class DefragWatchdog : Watchdog private int _heartbeatPeriodSec; private int _heartbeatTimeoutSec; private CancellationToken _cancellationToken; + private static readonly string[] Definitions = { "Defrag" }; private readonly ISqlRetryService _sqlRetryService; private readonly SqlQueueClient _sqlQueueClient; @@ -208,7 +209,7 @@ private async Task InitDefragAsync(long groupId, CancellationToken cancella (long groupId, long jobId, long version) id = (-1, -1, -1); try { - var jobs = await _sqlQueueClient.EnqueueAsync(QueueType, new[] { "Defrag" }, null, true, false, cancellationToken); + var jobs = await _sqlQueueClient.EnqueueAsync(QueueType, Definitions, null, true, false, cancellationToken); if (jobs.Count > 0) { diff --git a/src/Microsoft.Health.Fhir.Store.Utils/BatchExtensions.cs b/src/Microsoft.Health.Fhir.Store.Utils/BatchExtensions.cs index 39d9b1c05c..d4153ddb62 100644 --- a/src/Microsoft.Health.Fhir.Store.Utils/BatchExtensions.cs +++ b/src/Microsoft.Health.Fhir.Store.Utils/BatchExtensions.cs @@ -103,7 +103,7 @@ public static void ExecuteInParallelBatches(IEnumerable objects, int threa } } - private static void AddToQueueWithTrapping(BlockingCollection>> queue, int batchId, IList batchList, CancelRequest cancel, ICollection workers, int maxWorkers, Action workerAction) + private static void AddToQueueWithTrapping(BlockingCollection>> queue, int batchId, IList batchList, CancelRequest cancel, List workers, int maxWorkers, Action workerAction) { try { diff --git a/src/Microsoft.Health.TaskManagement/JobHosting.cs b/src/Microsoft.Health.TaskManagement/JobHosting.cs index 3d1e948881..497db7fcff 100644 --- a/src/Microsoft.Health.TaskManagement/JobHosting.cs +++ b/src/Microsoft.Health.TaskManagement/JobHosting.cs @@ -123,7 +123,11 @@ private async Task ExecuteJobAsync(JobInfo jobInfo, bool useHeavyHeartbeats) if (jobInfo.CancelRequested) { // For cancelled job, try to execute it for potential cleanup. +#if NET6_0 jobCancellationToken.Cancel(); +#else + await jobCancellationToken.CancelAsync(); +#endif } var progress = new Progress((result) => { jobInfo.Result = result; }); @@ -237,7 +241,11 @@ private static async Task PutJobHeartbeatAsync(IQueueClient queueClient, JobInfo var cancel = await queueClient.PutJobHeartbeatAsync(jobInfo, cancellationTokenSource.Token); if (cancel) { +#if NET6_0 cancellationTokenSource.Cancel(); +#else + await cancellationTokenSource.CancelAsync(); +#endif } } catch diff --git a/test/Microsoft.Health.Fhir.R4.Tests.Integration/Microsoft.Health.Fhir.R4.Tests.Integration.csproj b/test/Microsoft.Health.Fhir.R4.Tests.Integration/Microsoft.Health.Fhir.R4.Tests.Integration.csproj index 181cb61fe0..6bb17786d9 100644 --- a/test/Microsoft.Health.Fhir.R4.Tests.Integration/Microsoft.Health.Fhir.R4.Tests.Integration.csproj +++ b/test/Microsoft.Health.Fhir.R4.Tests.Integration/Microsoft.Health.Fhir.R4.Tests.Integration.csproj @@ -14,6 +14,9 @@ + + + diff --git a/test/Microsoft.Health.Fhir.R4B.Tests.Integration/Microsoft.Health.Fhir.R4B.Tests.Integration.csproj b/test/Microsoft.Health.Fhir.R4B.Tests.Integration/Microsoft.Health.Fhir.R4B.Tests.Integration.csproj index 5420f6aa62..4aff50f58a 100644 --- a/test/Microsoft.Health.Fhir.R4B.Tests.Integration/Microsoft.Health.Fhir.R4B.Tests.Integration.csproj +++ b/test/Microsoft.Health.Fhir.R4B.Tests.Integration/Microsoft.Health.Fhir.R4B.Tests.Integration.csproj @@ -14,6 +14,9 @@ + + + diff --git a/test/Microsoft.Health.Fhir.R5.Tests.Integration/Microsoft.Health.Fhir.R5.Tests.Integration.csproj b/test/Microsoft.Health.Fhir.R5.Tests.Integration/Microsoft.Health.Fhir.R5.Tests.Integration.csproj index 36f01188b1..b355731b34 100644 --- a/test/Microsoft.Health.Fhir.R5.Tests.Integration/Microsoft.Health.Fhir.R5.Tests.Integration.csproj +++ b/test/Microsoft.Health.Fhir.R5.Tests.Integration/Microsoft.Health.Fhir.R5.Tests.Integration.csproj @@ -14,6 +14,9 @@ + + + diff --git a/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/FhirStorageTests.cs b/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/FhirStorageTests.cs index 217b8f6b2a..c5cdb2c436 100644 --- a/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/FhirStorageTests.cs +++ b/test/Microsoft.Health.Fhir.Shared.Tests.Integration/Persistence/FhirStorageTests.cs @@ -16,7 +16,6 @@ using MediatR; using Microsoft.Health.Abstractions.Exceptions; using Microsoft.Health.Abstractions.Features.Transactions; -using Microsoft.Health.Core.Internal; using Microsoft.Health.Fhir.Core; using Microsoft.Health.Fhir.Core.Exceptions; using Microsoft.Health.Fhir.Core.Extensions; @@ -217,11 +216,12 @@ public async Task GivenASavedResource_WhenUpsertIsAnUpdate_ThenTheExistingResour } } +#if NET8_0_OR_GREATER [Fact] public async Task GivenAResource_WhenUpserting_ThenTheNewResourceHasMetaSet() { var instant = new DateTimeOffset(DateTimeOffset.Now.Date, TimeSpan.Zero); - using (Mock.Property(() => ClockResolver.UtcNowFunc, () => instant)) + using (Mock.Property(() => ClockResolver.TimeProvider, new Microsoft.Extensions.Time.Testing.FakeTimeProvider(instant))) { var versionId = Guid.NewGuid().ToString(); var resource = Samples.GetJsonSample("Weight").UpdateVersion(versionId); @@ -243,6 +243,7 @@ public async Task GivenAResource_WhenUpserting_ThenTheNewResourceHasMetaSet() Assert.NotEqual(versionId, deserialized.VersionId); } } +#endif [Fact(Skip = "Not valid for merge")] public async Task GivenASavedResource_WhenUpserting_ThenRawResourceVersionIsSetOrMetaSetIsSetToFalse() diff --git a/tools/Importer/Importer.cs b/tools/Importer/Importer.cs index efde905d03..f1858e3e30 100644 --- a/tools/Importer/Importer.cs +++ b/tools/Importer/Importer.cs @@ -110,7 +110,7 @@ internal static void Run() Console.WriteLine($"{globalPrefix}.Readers=[{readers}/{ReadThreads}].Writers=[{writers}].EndPointCalls=[{epCalls}].Waits=[{waits}]: total reads={totalReads} total writes={totalWrites} secs={(int)swWrites.Elapsed.TotalSeconds} read-speed={(int)(totalReads / swReads.Elapsed.TotalSeconds)} lines/sec write-speed={(int)(totalWrites / swWrites.Elapsed.TotalSeconds)} res/sec"); } - private static IEnumerable GetLinesInBlobRange(IList blobs, string logPrefix) + private static List GetLinesInBlobRange(IList blobs, string logPrefix) { Interlocked.Increment(ref readers); swReads.Start(); // just in case it was stopped by decrement logic below @@ -268,7 +268,7 @@ private static (string resourceType, string resourceId) ParseJson(string jsonStr { var idStart = jsonString.IndexOf("\"id\":\"", StringComparison.OrdinalIgnoreCase) + 6; var idShort = jsonString.Substring(idStart, 50); - var idEnd = idShort.IndexOf("\"", StringComparison.OrdinalIgnoreCase); + var idEnd = idShort.IndexOf('"', StringComparison.OrdinalIgnoreCase); var resourceId = idShort.Substring(0, idEnd); if (string.IsNullOrEmpty(resourceId)) { @@ -277,7 +277,7 @@ private static (string resourceType, string resourceId) ParseJson(string jsonStr var rtStart = jsonString.IndexOf("\"resourceType\":\"", StringComparison.OrdinalIgnoreCase) + 16; var rtShort = jsonString.Substring(rtStart, 50); - var rtEnd = rtShort.IndexOf("\"", StringComparison.OrdinalIgnoreCase); + var rtEnd = rtShort.IndexOf('"', StringComparison.OrdinalIgnoreCase); var resourceType = rtShort.Substring(0, rtEnd); if (string.IsNullOrEmpty(resourceType)) { diff --git a/tools/IndexRebuilder/IndexRebuilder.cs b/tools/IndexRebuilder/IndexRebuilder.cs index 3a78781414..4cc68f7a6b 100644 --- a/tools/IndexRebuilder/IndexRebuilder.cs +++ b/tools/IndexRebuilder/IndexRebuilder.cs @@ -76,7 +76,7 @@ private CancelRequest RunCommands(IList<(string Table, IList SqlCommands return cancelInt; } - private IList<(string Table, IList SqlCommands)> GetCommandsForRebuildIndexes(bool rebuildClustered) // Item1 is Table name, Items - list of SQL commands in the order they have to be executed + private List<(string Table, IList SqlCommands)> GetCommandsForRebuildIndexes(bool rebuildClustered) // Item1 is Table name, Items - list of SQL commands in the order they have to be executed { var resultsDic = new Dictionary>(); var tablesWithPreservedOrder = new List(); diff --git a/tools/Microsoft.Health.Fhir.R4.ResourceParser/Code/MinimalSearchParameterDefinitionBuilder.cs b/tools/Microsoft.Health.Fhir.R4.ResourceParser/Code/MinimalSearchParameterDefinitionBuilder.cs index 0e808883c6..ab2210557b 100644 --- a/tools/Microsoft.Health.Fhir.R4.ResourceParser/Code/MinimalSearchParameterDefinitionBuilder.cs +++ b/tools/Microsoft.Health.Fhir.R4.ResourceParser/Code/MinimalSearchParameterDefinitionBuilder.cs @@ -22,7 +22,7 @@ namespace Microsoft.Health.Fhir.R4.ResourceParser.Code { public static class MinimalSearchParameterDefinitionBuilder { - private static readonly ISet _missingExpressionsInR5 = new HashSet + private static readonly HashSet _missingExpressionsInR5 = new() { new("http://hl7.org/fhir/SearchParameter/EvidenceVariable-topic"), new("http://hl7.org/fhir/SearchParameter/ImagingStudy-reason"), diff --git a/tools/Microsoft.Health.Fhir.R4.ResourceParser/ResourceWrapperParser.cs b/tools/Microsoft.Health.Fhir.R4.ResourceParser/ResourceWrapperParser.cs index ee0fe751a5..38ca6d27cd 100644 --- a/tools/Microsoft.Health.Fhir.R4.ResourceParser/ResourceWrapperParser.cs +++ b/tools/Microsoft.Health.Fhir.R4.ResourceParser/ResourceWrapperParser.cs @@ -89,7 +89,7 @@ public string SerializeToString(ResourceWrapper wrapper) return _fhirJsonSerializer.SerializeToString(_fhirJsonParser.Parse(wrapper.RawResource.ToITypedElement(_modelInfoProvider))); } - private static IEnumerable MakeConverters(RequestContextAccessor requestContextAccessor, ICodeSystemResolver codeSystemResolver) + private static List MakeConverters(RequestContextAccessor requestContextAccessor, ICodeSystemResolver codeSystemResolver) { var fhirTypedElementConverters = new List(); var referenceSearchValueParser = new ReferenceSearchValueParser(requestContextAccessor); diff --git a/tools/RegisterAndMonitorImport/RegisterAndMonitorImport.cs b/tools/RegisterAndMonitorImport/RegisterAndMonitorImport.cs index e940dd1360..258c1e7ad8 100644 --- a/tools/RegisterAndMonitorImport/RegisterAndMonitorImport.cs +++ b/tools/RegisterAndMonitorImport/RegisterAndMonitorImport.cs @@ -358,7 +358,7 @@ private static ImportResponse TryParseJson(string value) else { value = value.Trim(); - if (value.StartsWith("{", StringComparison.Ordinal) && value.EndsWith("}", StringComparison.Ordinal)) + if (value.StartsWith('{') && value.EndsWith('}')) { try {