├── .config └── dotnet-tools.json ├── .editorconfig ├── .gitattributes ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── user-story.md └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── .globalconfig ├── Directory.Build.props ├── Directory.Packages.props ├── GeoPol.xml ├── LICENSE ├── Microsoft.Health.Dicom.sln ├── NOTICE.md ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── SECURITY.md ├── global.json ├── nuget.config ├── renovate.json └── src └── Microsoft.Health.Dicom.Client ├── DicomApiVersions.cs ├── DicomClientResource.Designer.cs ├── DicomClientResource.resx ├── DicomWebAsyncEnumerableResponse.cs ├── DicomWebClient.ChangeFeed.cs ├── DicomWebClient.Delete.cs ├── DicomWebClient.Export.cs ├── DicomWebClient.ExtendedQueryTag.cs ├── DicomWebClient.Operation.cs ├── DicomWebClient.Partition.cs ├── DicomWebClient.Reference.cs ├── DicomWebClient.Retrieve.cs ├── DicomWebClient.Search.cs ├── DicomWebClient.Store.cs ├── DicomWebClient.Update.cs ├── DicomWebClient.Workitem.cs ├── DicomWebClient.cs ├── DicomWebConstants.cs ├── DicomWebException.cs ├── DicomWebResponse.cs ├── DicomWebResponse{T}.cs ├── Http └── DicomContent.cs ├── IDicomWebClient.ChangeFeed.cs ├── IDicomWebClient.Delete.cs ├── IDicomWebClient.Export.cs ├── IDicomWebClient.ExtendedQueryTag.cs ├── IDicomWebClient.Operation.cs ├── IDicomWebClient.Partition.cs ├── IDicomWebClient.Reference.cs ├── IDicomWebClient.Retrieve.cs ├── IDicomWebClient.Search.cs ├── IDicomWebClient.Store.cs ├── IDicomWebClient.Update.cs ├── IDicomWebClient.Workitem.cs ├── IDicomWebClient.cs ├── Microsoft.Health.Dicom.Client.csproj ├── Models ├── AddExtendedQueryTagEntry.cs ├── ChangeFeedAction.cs ├── ChangeFeedEntry.cs ├── ChangeFeedState.cs ├── DicomIdentifier.cs ├── DicomOperation.cs ├── DicomOperationReference.cs ├── Export │ ├── AzureBlobExportOptions.cs │ └── IdentifierExportOptions.cs ├── ExportDataOptions.cs ├── ExportDestination.cs ├── ExportDestinationType.cs ├── ExportResults.cs ├── ExportSource.cs ├── ExportSourceType.cs ├── ExportSpecification.cs ├── ExtendedQueryTagError.cs ├── ExtendedQueryTagErrorReference.cs ├── ExtendedQueryTagStatus.cs ├── GetExtendedQueryTagEntry.cs ├── IResourceReference.cs ├── Partition.cs ├── QueryStatus.cs ├── QueryTagLevel.cs ├── UpdateExtendedQueryTagEntry.cs ├── UpdateResults.cs └── UpdateSpecification.cs ├── Properties └── AssemblyInfo.cs └── Serialization ├── DicomIdentifierJsonConverter.cs ├── ExportDataOptionsJsonConverter.T.cs ├── ExportDataOptionsJsonConverter.cs └── OperationStateConverter.cs /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "swashbuckle.aspnetcore.cli": { 6 | "version": "6.5.0", 7 | "commands": [ 8 | "swagger" 9 | ] 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Default settings: 7 | # A newline ending every file 8 | # Use 4 spaces as indentation 9 | [*] 10 | insert_final_newline = true 11 | indent_style = space 12 | indent_size = 4 13 | trim_trailing_whitespace = true 14 | 15 | # Generated code 16 | [*{_AssemblyInfo.cs,.notsupported.cs,.Generated.cs}] 17 | generated_code = true 18 | 19 | # C# files 20 | [*.cs] 21 | charset = utf-8 22 | file_header_template = -------------------------------------------------------------------------------------------------\nCopyright (c) Microsoft Corporation. All rights reserved.\nLicensed under the MIT License (MIT). See LICENSE in the repo root for license information.\n------------------------------------------------------------------------------------------------- 23 | csharp_using_directive_placement = outside_namespace:silent 24 | csharp_prefer_simple_using_statement = true:suggestion 25 | csharp_prefer_braces = when_multiline:error 26 | csharp_style_namespace_declarations = file_scoped:error 27 | csharp_style_prefer_method_group_conversion = true:silent 28 | csharp_style_prefer_top_level_statements = true:silent 29 | csharp_style_prefer_primary_constructors = true:suggestion 30 | csharp_prefer_system_threading_lock = true:suggestion 31 | csharp_style_expression_bodied_methods = true:silent 32 | csharp_style_expression_bodied_constructors = true:silent 33 | csharp_style_expression_bodied_operators = true:silent 34 | csharp_style_expression_bodied_properties = true:suggestion 35 | csharp_style_expression_bodied_indexers = true:suggestion 36 | csharp_style_expression_bodied_accessors = true:suggestion 37 | csharp_style_expression_bodied_lambdas = true:silent 38 | csharp_style_expression_bodied_local_functions = false:silent 39 | csharp_style_throw_expression = true:suggestion 40 | csharp_style_prefer_null_check_over_type_check = true:suggestion 41 | csharp_prefer_simple_default_expression = true:suggestion 42 | csharp_style_prefer_local_over_anonymous_function = true:suggestion 43 | csharp_style_prefer_index_operator = true:suggestion 44 | csharp_style_prefer_range_operator = true:suggestion 45 | csharp_style_implicit_object_creation_when_type_is_apparent = false:suggestion 46 | csharp_style_prefer_tuple_swap = true:suggestion 47 | csharp_style_prefer_utf8_string_literals = true:suggestion 48 | csharp_indent_labels = no_change 49 | csharp_style_inlined_variable_declaration = true:suggestion 50 | csharp_style_unused_value_assignment_preference = discard_variable:suggestion 51 | csharp_style_deconstructed_variable_declaration = true:suggestion 52 | csharp_style_unused_value_expression_statement_preference = discard_variable:silent 53 | csharp_prefer_static_local_function = true:suggestion 54 | csharp_prefer_static_anonymous_function = true:suggestion 55 | csharp_style_prefer_readonly_struct = true:suggestion 56 | csharp_style_prefer_readonly_struct_member = true:suggestion 57 | csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent 58 | csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent 59 | csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent 60 | csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent 61 | csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent 62 | csharp_style_conditional_delegate_call = true:suggestion 63 | csharp_style_prefer_pattern_matching = true:silent 64 | csharp_style_prefer_switch_expression = true:suggestion 65 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 66 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 67 | csharp_style_prefer_not_pattern = true:suggestion 68 | csharp_style_prefer_extended_property_pattern = true:suggestion 69 | csharp_style_var_for_built_in_types = false:suggestion 70 | csharp_style_var_when_type_is_apparent = true:suggestion 71 | csharp_style_var_elsewhere = false:suggestion 72 | 73 | # Xml project files 74 | [*.{csproj,dcproj}] 75 | charset = utf-8 76 | indent_size = 2 77 | 78 | # Xml files 79 | [*.{xml,resx}] 80 | charset = utf-8 81 | indent_size = 2 82 | 83 | # Xml config files 84 | [*.{props,targets,config,nuspec}] 85 | charset = utf-8 86 | indent_size = 2 87 | 88 | # JSON files 89 | [*.json] 90 | charset = utf-8 91 | indent_size = 2 92 | 93 | # YAML files 94 | [*.{yml,yaml}] 95 | charset = utf-8 96 | indent_size = 2 97 | 98 | # Shell scripts 99 | [*.sh] 100 | charset = utf-8 101 | end_of_line = lf 102 | 103 | [*.{cmd,bat}] 104 | charset = utf-8 105 | end_of_line = crlf 106 | 107 | [*.{cs,vb}] 108 | #### Naming styles #### 109 | 110 | # Naming rules 111 | 112 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion 113 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface 114 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i 115 | 116 | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion 117 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types 118 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case 119 | 120 | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion 121 | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members 122 | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case 123 | 124 | # Symbol specifications 125 | 126 | dotnet_naming_symbols.interface.applicable_kinds = interface 127 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 128 | dotnet_naming_symbols.interface.required_modifiers = 129 | 130 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum 131 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 132 | dotnet_naming_symbols.types.required_modifiers = 133 | 134 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method 135 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 136 | dotnet_naming_symbols.non_field_members.required_modifiers = 137 | 138 | # Naming styles 139 | 140 | dotnet_naming_style.begins_with_i.required_prefix = I 141 | dotnet_naming_style.begins_with_i.required_suffix = 142 | dotnet_naming_style.begins_with_i.word_separator = 143 | dotnet_naming_style.begins_with_i.capitalization = pascal_case 144 | 145 | dotnet_naming_style.pascal_case.required_prefix = 146 | dotnet_naming_style.pascal_case.required_suffix = 147 | dotnet_naming_style.pascal_case.word_separator = 148 | dotnet_naming_style.pascal_case.capitalization = pascal_case 149 | 150 | dotnet_naming_style.pascal_case.required_prefix = 151 | dotnet_naming_style.pascal_case.required_suffix = 152 | dotnet_naming_style.pascal_case.word_separator = 153 | dotnet_naming_style.pascal_case.capitalization = pascal_case 154 | dotnet_style_coalesce_expression = true:suggestion 155 | dotnet_style_null_propagation = true:suggestion 156 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion 157 | dotnet_style_prefer_auto_properties = true:error 158 | dotnet_style_object_initializer = true:suggestion 159 | dotnet_style_collection_initializer = true:suggestion 160 | dotnet_style_prefer_simplified_boolean_expressions = true:suggestion 161 | dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion 162 | dotnet_style_prefer_conditional_expression_over_return = false:silent 163 | dotnet_style_explicit_tuple_names = true:suggestion 164 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 165 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion 166 | dotnet_style_prefer_compound_assignment = true:suggestion 167 | dotnet_style_prefer_simplified_interpolation = true:suggestion 168 | dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion 169 | dotnet_style_namespace_match_folder = true:suggestion 170 | dotnet_style_operator_placement_when_wrapping = beginning_of_line 171 | tab_width = 4 172 | end_of_line = crlf 173 | dotnet_style_readonly_field = true:suggestion 174 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 175 | dotnet_style_predefined_type_for_member_access = true:suggestion 176 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:error 177 | dotnet_style_allow_multiple_blank_lines_experimental = true:silent 178 | dotnet_style_allow_statement_immediately_after_block_experimental = true:silent 179 | dotnet_code_quality_unused_parameters = all:suggestion 180 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent 181 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent 182 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent 183 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent 184 | dotnet_style_qualification_for_field = false:error 185 | dotnet_style_qualification_for_property = false:error 186 | dotnet_style_qualification_for_method = false:error 187 | dotnet_style_qualification_for_event = false:error 188 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | 65 | # Force bash scripts to always use lf line endings so that if a repo is accessed 66 | # in Unix via a file share from Windows, the scripts will work. 67 | *.sh text eol=lf -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Lines starting with '#' are comments. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | # These owners will be the default owners for everything in the repo. 5 | * @microsoft/resolute-admins 6 | * @microsoft/Medical-Imaging-Team 7 | 8 | # PMs own markdown files in the `/docs` directory 9 | /docs/**/*.md @mmitrik @microsoft/Medical-Imaging-Team 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | labels: 5 | 6 | --- 7 | 8 | **Describe the bug** 9 | A clear and concise description of what the bug is. 10 | 11 | **To Reproduce** 12 | Steps to reproduce the behavior: 13 | 1. 14 | 2. 15 | 3. 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Actual behavior** 21 | A clear and concise description of what actually happened. 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/user-story.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: User story 3 | about: Tell us something new you want to do 4 | labels: 5 | 6 | --- 7 | 8 | **User story** 9 | As a [type of user], I want [some functionality] so that [benefit]. 10 | 11 | **Acceptance criteria** 12 | 1. When I do [A], then [B] happens. 13 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | schedule: 9 | - cron: '0 0 * * 0' 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze 14 | runs-on: ubuntu-latest 15 | 16 | permissions: 17 | security-events: write 18 | 19 | strategy: 20 | fail-fast: false 21 | matrix: 22 | language: [ 'csharp', 'javascript' ] 23 | 24 | steps: 25 | - name: Checkout repository 26 | uses: actions/checkout@v4.1.6 27 | 28 | - name: 'Setup dotnet 6.x' 29 | uses: actions/setup-dotnet@v4 30 | if: ${{ matrix.language == 'csharp' }} 31 | with: 32 | dotnet-version: '6.x' 33 | 34 | - name: Setup dotnet from global.json 35 | uses: actions/setup-dotnet@v4 36 | if: ${{ matrix.language == 'csharp' }} 37 | 38 | # Initializes the CodeQL tools for scanning. 39 | - name: Initialize CodeQL 40 | uses: github/codeql-action/init@v3 41 | with: 42 | languages: ${{ matrix.language }} 43 | # If you wish to specify custom queries, you can do so here or in a config file. 44 | # By default, queries listed here will override any specified in a config file. 45 | # Prefix the list here with "+" to use these queries and those in the config file. 46 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 47 | 48 | # Build C# solutions manually 49 | - name: dotnet build 50 | run: | 51 | dotnet build Microsoft.Health.Dicom.sln -c Release -p:ContinuousIntegrationBuild=true -warnaserror 52 | if: ${{ matrix.language == 'csharp' }} 53 | 54 | - name: Perform CodeQL Analysis 55 | uses: github/codeql-action/analyze@v3 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | x64/ 17 | x86/ 18 | bld/ 19 | [Bb]in/ 20 | [Oo]bj/ 21 | [Ll]og/ 22 | 23 | # Visual Studio 2015 cache/options directory 24 | .vs/ 25 | # Uncomment if you have tasks that create the project's static files in wwwroot 26 | #wwwroot/ 27 | 28 | #Visual Studio Code 29 | .vscode/ 30 | 31 | # MSTest test Results 32 | [Tt]est[Rr]esult*/ 33 | [Bb]uild[Ll]og.* 34 | 35 | # NUNIT 36 | *.VisualState.xml 37 | TestResult.xml 38 | 39 | # Build Results of an ATL Project 40 | [Dd]ebugPS/ 41 | [Rr]eleasePS/ 42 | dlldata.c 43 | 44 | # .NET Core 45 | project.lock.json 46 | project.fragment.lock.json 47 | artifacts/ 48 | 49 | *_i.c 50 | *_p.c 51 | *_i.h 52 | *.ilk 53 | *.meta 54 | *.obj 55 | *.pch 56 | *.pdb 57 | *.pgc 58 | *.pgd 59 | *.rsp 60 | *.sbr 61 | *.tlb 62 | *.tli 63 | *.tlh 64 | *.tmp 65 | *.tmp_proj 66 | *.log 67 | *.vspscc 68 | *.vssscc 69 | .builds 70 | *.pidb 71 | *.svclog 72 | *.scc 73 | 74 | # Chutzpah Test files 75 | _Chutzpah* 76 | 77 | # Visual C++ cache files 78 | ipch/ 79 | *.aps 80 | *.ncb 81 | *.opendb 82 | *.opensdf 83 | *.sdf 84 | *.cachefile 85 | *.VC.db 86 | *.VC.VC.opendb 87 | 88 | # Visual Studio profiler 89 | *.psess 90 | *.vsp 91 | *.vspx 92 | *.sap 93 | 94 | # TFS 2012 Local Workspace 95 | $tf/ 96 | 97 | # Guidance Automation Toolkit 98 | *.gpState 99 | 100 | # ReSharper is a .NET coding add-in 101 | _ReSharper*/ 102 | *.[Rr]e[Ss]harper 103 | *.DotSettings.user 104 | 105 | # JustCode is a .NET coding add-in 106 | .JustCode 107 | 108 | # TeamCity is a build add-in 109 | _TeamCity* 110 | 111 | # DotCover is a Code Coverage Tool 112 | *.dotCover 113 | 114 | # Visual Studio code coverage results 115 | *.coverage 116 | *.coveragexml 117 | 118 | # NCrunch 119 | _NCrunch_* 120 | .*crunch*.local.xml 121 | nCrunchTemp_* 122 | 123 | # MightyMoose 124 | *.mm.* 125 | AutoTest.Net/ 126 | 127 | # Web workbench (sass) 128 | .sass-cache/ 129 | 130 | # Installshield output folder 131 | [Ee]xpress/ 132 | 133 | # DocProject is a documentation generator add-in 134 | DocProject/buildhelp/ 135 | DocProject/Help/*.HxT 136 | DocProject/Help/*.HxC 137 | DocProject/Help/*.hhc 138 | DocProject/Help/*.hhk 139 | DocProject/Help/*.hhp 140 | DocProject/Help/Html2 141 | DocProject/Help/html 142 | 143 | # Click-Once directory 144 | publish/ 145 | 146 | # Publish Web Output 147 | *.[Pp]ublish.xml 148 | *.azurePubxml 149 | # TODO: Comment the next line if you want to checkin your web deploy settings 150 | # but database connection strings (with potential passwords) will be unencrypted 151 | *.pubxml 152 | *.publishproj 153 | 154 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 155 | # checkin your Azure Web App publish settings, but sensitive information contained 156 | # in these scripts will be unencrypted 157 | PublishScripts/ 158 | 159 | # NuGet Packages 160 | *.nupkg 161 | # The packages folder can be ignored because of Package Restore 162 | **/packages/* 163 | # except build/, which is used as an MSBuild target. 164 | !**/packages/build/ 165 | # Uncomment if necessary however generally it will be regenerated when needed 166 | #!**/packages/repositories.config 167 | # NuGet v3's project.json files produces more ignorable files 168 | *.nuget.props 169 | *.nuget.targets 170 | 171 | # Microsoft Azure Build Output 172 | csx/ 173 | *.build.csdef 174 | 175 | # Microsoft Azure Emulator 176 | ecf/ 177 | rcf/ 178 | 179 | # Windows Store app package directories and files 180 | AppPackages/ 181 | BundleArtifacts/ 182 | Package.StoreAssociation.xml 183 | _pkginfo.txt 184 | 185 | # Visual Studio cache files 186 | # files ending in .cache can be ignored 187 | *.[Cc]ache 188 | # but keep track of directories ending in .cache 189 | !*.[Cc]ache/ 190 | 191 | # Others 192 | ClientBin/ 193 | ~$* 194 | *~ 195 | *.dbmdl 196 | *.dbproj.schemaview 197 | *.jfm 198 | *.pfx 199 | *.publishsettings 200 | orleans.codegen.cs 201 | 202 | # Since there are multiple workflows, uncomment next line to ignore bower_components 203 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 204 | #bower_components/ 205 | 206 | # RIA/Silverlight projects 207 | Generated_Code/ 208 | 209 | # Backup & report files from converting an old project file 210 | # to a newer Visual Studio version. Backup files are not needed, 211 | # because we have git ;-) 212 | _UpgradeReport_Files/ 213 | Backup*/ 214 | UpgradeLog*.XML 215 | UpgradeLog*.htm 216 | 217 | # SQL Server files 218 | *.mdf 219 | *.ldf 220 | *.ndf 221 | 222 | # Business Intelligence projects 223 | *.rdl.data 224 | *.bim.layout 225 | *.bim_*.settings 226 | 227 | # Microsoft Fakes 228 | FakesAssemblies/ 229 | 230 | # GhostDoc plugin setting file 231 | *.GhostDoc.xml 232 | 233 | # Node.js Tools for Visual Studio 234 | .ntvs_analysis.dat 235 | node_modules/ 236 | 237 | # Typescript v1 declaration files 238 | typings/ 239 | 240 | # Visual Studio 6 build log 241 | *.plg 242 | 243 | # Visual Studio 6 workspace options file 244 | *.opt 245 | 246 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 247 | *.vbw 248 | 249 | # Visual Studio LightSwitch build output 250 | **/*.HTMLClient/GeneratedArtifacts 251 | **/*.DesktopClient/GeneratedArtifacts 252 | **/*.DesktopClient/ModelManifest.xml 253 | **/*.Server/GeneratedArtifacts 254 | **/*.Server/ModelManifest.xml 255 | _Pvt_Extensions 256 | 257 | # Paket dependency manager 258 | .paket/paket.exe 259 | paket-files/ 260 | 261 | # FAKE - F# Make 262 | .fake/ 263 | 264 | # JetBrains Rider 265 | .idea/ 266 | *.sln.iml 267 | 268 | # CodeRush 269 | .cr/ 270 | 271 | # Python Tools for Visual Studio (PTVS) 272 | __pycache__/ 273 | *.pyc 274 | 275 | # Cake - Uncomment if you are using it 276 | # tools/** 277 | # !tools/packages.config 278 | 279 | # Telerik's JustMock configuration file 280 | *.jmconfig 281 | 282 | # BizTalk build output 283 | *.btp.cs 284 | *.btm.cs 285 | *.odx.cs 286 | *.xsd.cs 287 | 288 | # IdentityServer workspace 289 | tempkey.rsa 290 | 291 | # docker .env file 292 | **/.env 293 | 294 | # azurite files 295 | __azurite_db_* 296 | __blobstorage__/ 297 | __queuestorage__/ 298 | __tablestorage__/ 299 | 300 | # Console App Launch Settings 301 | /tools/**/launchSettings.json 302 | -------------------------------------------------------------------------------- /.globalconfig: -------------------------------------------------------------------------------- 1 | # Global AnalyzerConfig file 2 | # For details: https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/configuration-files 3 | # For rules: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference 4 | is_global = true 5 | global_level = 1 6 | 7 | # Language code styles 8 | # Most use "options_name = false|true : none|silent|suggestion|warning|error" 9 | 10 | # .NET code style settings 11 | # "This." and "Me." qualifiers 12 | dotnet_style_qualification_for_field = false:error 13 | dotnet_style_qualification_for_property = false:error 14 | dotnet_style_qualification_for_method = false:error 15 | dotnet_style_qualification_for_event = false:error 16 | 17 | # Language keywords instead of framework type names for type references 18 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 19 | dotnet_style_predefined_type_for_member_access = true:suggestion 20 | 21 | # Modifier preferences 22 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:error 23 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion 24 | dotnet_style_readonly_field = true:suggestion 25 | 26 | # Parentheses preferences 27 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent 28 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent 29 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent 30 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent 31 | 32 | # Expression-level preferences 33 | dotnet_style_object_initializer = true:suggestion 34 | dotnet_style_collection_initializer = true:suggestion 35 | dotnet_style_explicit_tuple_names = true:suggestion 36 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 37 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion 38 | dotnet_style_prefer_auto_properties = true:error 39 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion 40 | dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion 41 | dotnet_style_prefer_conditional_expression_over_return = false:silent 42 | 43 | # "Null" checking preferences 44 | dotnet_style_coalesce_expression = true:suggestion 45 | dotnet_style_null_propagation = true:suggestion 46 | 47 | # C# code style settings 48 | # Implicit and explicit types 49 | csharp_style_var_for_built_in_types = false:suggestion 50 | csharp_style_var_when_type_is_apparent = true:suggestion 51 | csharp_style_var_elsewhere = false:suggestion 52 | 53 | # 'new(...)' ctor 54 | csharp_style_implicit_object_creation_when_type_is_apparent = false 55 | 56 | # Expression-bodied members 57 | csharp_style_expression_bodied_methods = true:silent 58 | csharp_style_expression_bodied_constructors = true:silent 59 | csharp_style_expression_bodied_operators = true:silent 60 | csharp_style_expression_bodied_properties = true:suggestion 61 | csharp_style_expression_bodied_indexers = true:suggestion 62 | csharp_style_expression_bodied_accessors = true:suggestion 63 | 64 | # Pattern matching 65 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 66 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 67 | 68 | # Inlined variable declarations 69 | csharp_style_inlined_variable_declaration = true:suggestion 70 | 71 | # Expression-level preferences 72 | csharp_prefer_simple_default_expression = true:suggestion 73 | csharp_style_deconstructed_variable_declaration = true:suggestion 74 | csharp_style_pattern_local_over_anonymous_function = false:suggestion 75 | 76 | # "Null" checking preferences 77 | csharp_style_throw_expression = true:suggestion 78 | csharp_style_conditional_delegate_call = true:suggestion 79 | 80 | # Code block preferences 81 | csharp_prefer_braces = when_multiline:error 82 | 83 | # Namespaces 84 | csharp_style_namespace_declarations = file_scoped:error 85 | 86 | # Formatting conventions 87 | # Most use "rule_name = false|true" 88 | 89 | # .NET formatting settings 90 | # Organize usings 91 | dotnet_sort_system_directives_first = true 92 | dotnet_separate_import_directive_groups = false 93 | 94 | # C# formatting settings 95 | # Newline options 96 | csharp_new_line_before_open_brace = all 97 | csharp_new_line_before_else = true 98 | csharp_new_line_before_catch = true 99 | csharp_new_line_before_finally = true 100 | csharp_new_line_before_members_in_object_initializers = true 101 | csharp_new_line_before_members_in_anonymous_types = true 102 | csharp_new_line_between_query_expression_clauses = true 103 | 104 | # Indentation options 105 | csharp_indent_case_contents = true 106 | csharp_indent_switch_labels = true 107 | csharp_indent_labels = no_change 108 | 109 | # Spacing options 110 | csharp_space_after_cast = false 111 | csharp_space_after_keywords_in_control_flow_statements = true 112 | csharp_space_between_method_declaration_parameter_list_parentheses = false 113 | csharp_space_between_method_call_parameter_list_parentheses = false 114 | csharp_space_before_colon_in_inheritance_clause = true 115 | csharp_space_after_colon_in_inheritance_clause = true 116 | csharp_space_around_binary_operators = before_and_after 117 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 118 | csharp_space_between_method_call_name_and_opening_parenthesis = false 119 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 120 | 121 | # Wrapping options 122 | csharp_preserve_single_line_statements = false 123 | csharp_preserve_single_line_blocks = true 124 | 125 | # Naming conventions ("borrowed" from CoreFX) 126 | # See https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-naming-conventions 127 | 128 | # name all constant fields using PascalCase 129 | dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = error 130 | dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields 131 | dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style 132 | 133 | dotnet_naming_symbols.constant_fields.applicable_kinds = field 134 | dotnet_naming_symbols.constant_fields.required_modifiers = const 135 | 136 | # name all static readonly fields using PascalCase 137 | dotnet_naming_rule.static_readonly_fields_pascal_case.severity = error 138 | dotnet_naming_rule.static_readonly_fields_pascal_case.symbols = static_readonly_fields 139 | dotnet_naming_rule.static_readonly_fields_pascal_case.style = pascal_case_style 140 | 141 | dotnet_naming_symbols.static_readonly_fields.applicable_kinds = field 142 | dotnet_naming_symbols.static_readonly_fields.applicable_accessibilities = private 143 | dotnet_naming_symbols.static_readonly_fields.required_modifiers = readonly,static 144 | 145 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case 146 | 147 | # static fields should have s_ prefix 148 | dotnet_naming_rule.static_private_internal_fields_should_have_prefix.severity = error 149 | dotnet_naming_rule.static_private_internal_fields_should_have_prefix.symbols = static_private_internal_fields 150 | dotnet_naming_rule.static_private_internal_fields_should_have_prefix.style = static_prefix_style 151 | 152 | dotnet_naming_symbols.static_private_internal_fields.applicable_kinds = field 153 | dotnet_naming_symbols.static_private_internal_fields.applicable_accessibilities = private 154 | dotnet_naming_symbols.static_private_internal_fields.required_modifiers = static 155 | 156 | dotnet_naming_style.static_prefix_style.required_prefix = s_ 157 | dotnet_naming_style.static_prefix_style.capitalization = camel_case 158 | 159 | # internal and private fields should be _camelCase 160 | dotnet_naming_rule.camel_case_for_private_internal_fields.severity = error 161 | dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields 162 | dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style 163 | 164 | dotnet_naming_symbols.private_internal_fields.applicable_kinds = field 165 | dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private 166 | 167 | dotnet_naming_style.camel_case_underscore_style.required_prefix = _ 168 | dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case 169 | 170 | # Analyzers 171 | # See https://github.com/dotnet/roslyn-analyzers/blob/master/docs/Analyzer%20Configuration.md 172 | 173 | # Note that the above severities do not affect builds by design. These values are only used 174 | # to configure the entries in Visual Studio's "Error List" and power its Intellisense. 175 | # Instead, the rules below are used to configure build-time analyzer behavior. 176 | # Unfortunately, some rules have been disabled due to performance reasons outside of 177 | # Visual Studio and can be found here: 178 | # https://github.com/dotnet/roslyn/blob/0a73f08951f408624639e1601bb828b396f154c8/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs#L99 179 | 180 | # Code Quality Rules 181 | dotnet_code_quality.api_surface = all 182 | dotnet_code_quality.exclude_async_void_methods = true 183 | dotnet_code_quality.exclude_single_letter_type_parameters = true 184 | dotnet_code_quality.dispose_analysis_kind = AllPaths 185 | dotnet_code_quality.enum_values_prefix_trigger = AllEnumValues 186 | dotnet_code_quality.exclude_indirect_base_types = false 187 | 188 | dotnet_code_quality.CA1002.api_surface = public, internal # Do not expose generic lists 189 | dotnet_code_quality.CA1062.api_surface = public # Validate arguments of public methods 190 | dotnet_code_quality.CA1711.api_surface = public # Identifiers should not have incorrect suffix 191 | dotnet_code_quality.CA1802 = static # Use Literals Where Appropriate 192 | dotnet_code_quality.CA1815.api_surface = public, internal # Override equals and operator equals on value types 193 | 194 | dotnet_diagnostic.CA1031.severity = error # Do not catch general exception types 195 | dotnet_diagnostic.CA1032.severity = suggestion # Implement standard exception constructors 196 | dotnet_diagnostic.CA1054.severity = error # URI parameters should not be strings 197 | dotnet_diagnostic.CA1305.severity = error # Specify IFormatProvider 198 | dotnet_diagnostic.CA1510.severity = none # Use ArgumentNullException throw helper 199 | dotnet_diagnostic.CA1511.severity = none # Use ArgumentException throw helper 200 | dotnet_diagnostic.CA1512.severity = none # Use ArgumentOutOfRangeException throw helper 201 | dotnet_diagnostic.CA1513.severity = none # Use ObjectDisposedException throw helper 202 | dotnet_diagnostic.CA1716.severity = error # Identifiers should not match keywords 203 | dotnet_diagnostic.CA1822.severity = error # Mark members as static 204 | dotnet_diagnostic.CA1848.severity = none # Do not encourage LoggerMessage delegates in every instance 205 | dotnet_diagnostic.CA1863.severity = none # Use 'CompositeFormat' 206 | dotnet_diagnostic.CA2007.severity = none # Do not directly await a Task 207 | 208 | # C# Compiler Rules 209 | dotnet_diagnostic.CS1591.severity = none # Missing XML comment for publicly visible type or member 210 | 211 | # Code Style Rules 212 | dotnet_diagnostic.IDE0003.severity = error # Remove this or Me qualification 213 | dotnet_diagnostic.IDE0004.severity = error # Remove unnecessary cast 214 | dotnet_diagnostic.IDE0005.severity = error # Remove unnecessary import 215 | dotnet_diagnostic.IDE0010.severity = none # Add missing cases to switch statement 216 | dotnet_diagnostic.IDE0032.severity = error # Use auto property 217 | dotnet_diagnostic.IDE0044.severity = error # Add readonly modifier 218 | dotnet_diagnostic.IDE0055.severity = error # Fix formatting 219 | dotnet_diagnostic.IDE0065.severity = error # 'using' directive placement 220 | dotnet_diagnostic.IDE0073.severity = error # Require file header 221 | dotnet_diagnostic.IDE0161.severity = error # Use file-scoped namespace 222 | dotnet_diagnostic.IDE1005.severity = error # Use conditional delegate call 223 | dotnet_diagnostic.IDE1006.severity = error # Naming rule violation 224 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 5 | Microsoft Health Team 6 | Microsoft Corporation 7 | Copyright © Microsoft Corporation. All rights reserved. 8 | true 9 | true 10 | true 11 | true 12 | true 13 | Latest 14 | true 15 | MIT 16 | Microsoft Health 17 | true 18 | true 19 | partial 20 | 21 | 22 | 23 | 24 | 25 | net8.0 26 | net8.0;net6.0 27 | 28 | 29 | 30 | 31 | 32 | 33 | 6.0.1 34 | 12.1.1 35 | 1.6.0 36 | 6.0.0 37 | 2.2.0 38 | 39 | 40 | 41 | 42 | 8.0.0 43 | 12.2.0 44 | 1.7.0 45 | 8.0.0 46 | 2.2.0 47 | 48 | 49 | 50 | 51 | 8.0.0 52 | 12.2.0 53 | 1.7.0 54 | 8.0.0 55 | 8.0.0 56 | 57 | 58 | 59 | 60 | 67 | 68 | x64 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | false 80 | $(NoWarn);CS1591 81 | 82 | 83 | 84 | 85 | latest-All 86 | true 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /Directory.Packages.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /GeoPol.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | ]> 7 | 8 | 9 | 10 | &GitReposFolder;\VSTS\&GitRepoName; 11 | &GitRepoName; 12 | 13 | 14 | . 15 | 16 | 17 | .gitignore 18 | GeoPol.xml 19 | 20 | 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /Microsoft.Health.Dicom.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 17 3 | VisualStudioVersion = 17.0.31825.309 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{404C6C33-DB00-4182-BD90-F10A8B17C321}" 6 | ProjectSection(SolutionItems) = preProject 7 | .editorconfig = .editorconfig 8 | .globalconfig = .globalconfig 9 | Directory.Build.props = Directory.Build.props 10 | Directory.Packages.props = Directory.Packages.props 11 | .config\dotnet-tools.json = .config\dotnet-tools.json 12 | global.json = global.json 13 | nuget.config = nuget.config 14 | EndProjectSection 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Health.Dicom.Client", "src\Microsoft.Health.Dicom.Client\Microsoft.Health.Dicom.Client.csproj", "{D100EA60-8DC8-4576-A177-56BC7193BF4A}" 17 | EndProject 18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{176641B3-297C-4E04-A83D-8F80F80485E8}" 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|x64 = Debug|x64 23 | Release|x64 = Release|x64 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {D100EA60-8DC8-4576-A177-56BC7193BF4A}.Debug|x64.ActiveCfg = Debug|x64 27 | {D100EA60-8DC8-4576-A177-56BC7193BF4A}.Debug|x64.Build.0 = Debug|x64 28 | {D100EA60-8DC8-4576-A177-56BC7193BF4A}.Release|x64.ActiveCfg = Release|x64 29 | {D100EA60-8DC8-4576-A177-56BC7193BF4A}.Release|x64.Build.0 = Release|x64 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(NestedProjects) = preSolution 35 | {D100EA60-8DC8-4576-A177-56BC7193BF4A} = {176641B3-297C-4E04-A83D-8F80F80485E8} 36 | EndGlobalSection 37 | GlobalSection(ExtensibilityGlobals) = postSolution 38 | RESX_SortFileContentOnSave = True 39 | SolutionGuid = {E370FB31-CF95-47D1-B1E1-863A77973FF8} 40 | EndGlobalSection 41 | EndGlobal 42 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | Describe the changes in this PR. 3 | 4 | ## Related issues 5 | Addresses [issue #]. 6 | 7 | ## Testing 8 | Describe how this change was tested. 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Medical Imaging Server for DICOM 2 | 3 | > [!IMPORTANT] 4 | > 📢 **After more than 3 years, our team is planning to archive the DICOM server project to allow us to focus on delivering customer value to our [managed service offering on Azure](https://learn.microsoft.com/en-us/azure/healthcare-apis/dicom/overview).** 5 | > 6 | > Learn more in our recent [discussion post](https://github.com/microsoft/dicom-server/discussions/3401) 7 | 8 | ## Project Cleanup: Client Code Retention 9 | 10 | This repository has undergone a significant cleanup. All code except for the client has been removed. If you need access to the removed code, it has been preserved in an archived branch for reference. 11 | 12 | ### Key Details 13 | 14 | **Current State**: Only the client code remains in the main branch. 15 | 16 | **Archived Code**: All other components have been moved to the [`archived`](https://github.com/microsoft/dicom-server/tree/archived) branch. 17 | 18 | ### Accessing the Archived Code 19 | 20 | To access the archived code: 21 | 22 | 1. Clone the repository if you haven't already: 23 | 24 | `git clone https://github.com/microsoft/dicom-server.git` 25 | 26 | 2. Switch to the archive branch: 27 | 28 | `git checkout archived` 29 | 30 | 31 | ## Contributing 32 | 33 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 34 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 35 | the rights to use your contribution. For details, visit https://cla.microsoft.com. 36 | 37 | When you submit a pull request, a CLA-bot will automatically determine whether you need to provide 38 | a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions 39 | provided by the bot. You will only need to do this once across all repositories using our CLA. 40 | 41 | There are many other ways to contribute to Medical Imaging Server for DICOM. 42 | * Join the [#dicomonazure](https://twitter.com/hashtag/dicomonazure?f=tweets&vertical=default) discussion on Twitter. 43 | 44 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 45 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 46 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "8.0.404" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | ":dependencyDashboard", 5 | ":semanticPrefixFixDepsChoreOthers", 6 | "group:dotNetCore", 7 | "group:monorepos", 8 | "group:recommended", 9 | "replacements:all", 10 | "workarounds:all" 11 | ], 12 | "labels": [ 13 | "dependencies" 14 | ], 15 | "packageRules": [ 16 | { 17 | "groupName": "ApplicationInsights", 18 | "matchPackagePatterns": [ 19 | "ApplicationInsights" 20 | ] 21 | }, 22 | { 23 | "groupName": "Dotnet", 24 | "matchPackageNames": [ 25 | "dotnet-sdk", 26 | "mcr.microsoft.com/dotnet/aspnet", 27 | "mcr.microsoft.com/dotnet/sdk" 28 | ] 29 | }, 30 | { 31 | "groupName": "fo-dicom", 32 | "matchPackagePrefixes": [ 33 | "fo-dicom" 34 | ] 35 | }, 36 | { 37 | "excludePackagePrefixes": [ 38 | "Microsoft.Health.Fhir." 39 | ], 40 | "groupName": "Healthcare Shared Components", 41 | "matchPackagePrefixes": [ 42 | "Microsoft.Health." 43 | ] 44 | }, 45 | { 46 | "enabled": false, 47 | "groupName": "FHIR", 48 | "matchPackagePatterns": [ 49 | "Fhir" 50 | ] 51 | }, 52 | { 53 | "groupName": "IdentityModel.Tokens", 54 | "matchPackagePatterns": [ 55 | "IdentityModel.Tokens" 56 | ] 57 | }, 58 | { 59 | "groupName": "IdentityServer4", 60 | "matchPackagePrefixes": [ 61 | "IdentityServer4" 62 | ] 63 | }, 64 | { 65 | "groupName": "Microsoft.AspNetCore.Mvc.Versioning", 66 | "matchPackagePrefixes": [ 67 | "Microsoft.AspNetCore.Mvc.Versioning" 68 | ] 69 | }, 70 | { 71 | "groupName": "OpenTelemetry", 72 | "matchPackagePrefixes": [ 73 | "OpenTelemetry" 74 | ] 75 | }, 76 | { 77 | "allowedVersions": "<3.0.0", 78 | "groupName": "SixLabors.ImageSharp", 79 | "matchPackagePatterns": [ 80 | "SixLabors.ImageSharp" 81 | ] 82 | }, 83 | { 84 | "allowedVersions": "/^focal/", 85 | "groupName": "SQL Base Image", 86 | "includePaths": [ 87 | "docker/sql/Dockerfile" 88 | ], 89 | "matchPackageNames": [ 90 | "ubuntu" 91 | ] 92 | }, 93 | { 94 | "groupName": "Swashbuckle.AspNetCore", 95 | "matchPackagePrefixes": [ 96 | "Swashbuckle.AspNetCore." 97 | ] 98 | }, 99 | { 100 | "groupName": "System.CommandLine", 101 | "matchPackagePrefixes": [ 102 | "System.CommandLine" 103 | ] 104 | }, 105 | { 106 | "groupName": "XUnit", 107 | "matchPackagePrefixes": [ 108 | "xunit" 109 | ] 110 | } 111 | ], 112 | "prConcurrentLimit": 0, 113 | "prHourlyLimit": 0 114 | } 115 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/DicomApiVersions.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | namespace Microsoft.Health.Dicom.Client; 7 | 8 | public static class DicomApiVersions 9 | { 10 | public const string V1Prerelease = "v1.0-prerelease"; 11 | public const string V1 = "v1"; 12 | public const string Latest = "v2"; 13 | public const string V2 = "v2"; 14 | } 15 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/DicomClientResource.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Microsoft.Health.Dicom.Client { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class DicomClientResource { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal DicomClientResource() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Health.Dicom.Client.DicomClientResource", typeof(DicomClientResource).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized string similar to '{0}' is not a supported export destination.. 65 | /// 66 | internal static string InvalidExportDestination { 67 | get { 68 | return ResourceManager.GetString("InvalidExportDestination", resourceCulture); 69 | } 70 | } 71 | 72 | /// 73 | /// Looks up a localized string similar to '{0}' is not a supported export source.. 74 | /// 75 | internal static string InvalidExportSource { 76 | get { 77 | return ResourceManager.GetString("InvalidExportSource", resourceCulture); 78 | } 79 | } 80 | 81 | /// 82 | /// Looks up a localized string similar to Type '{0}' is not supported.. 83 | /// 84 | internal static string InvalidType { 85 | get { 86 | return ResourceManager.GetString("InvalidType", resourceCulture); 87 | } 88 | } 89 | 90 | /// 91 | /// Looks up a localized string similar to Read is not supported for the type '{0}'.. 92 | /// 93 | internal static string JsonReadNotSupported { 94 | get { 95 | return ResourceManager.GetString("JsonReadNotSupported", resourceCulture); 96 | } 97 | } 98 | 99 | /// 100 | /// Looks up a localized string similar to Write is not supported for the type '{0}'.. 101 | /// 102 | internal static string JsonWriteNotSupported { 103 | get { 104 | return ResourceManager.GetString("JsonWriteNotSupported", resourceCulture); 105 | } 106 | } 107 | 108 | /// 109 | /// Looks up a localized string similar to Expected value '{0}' to be one of the following values: [{1}]. 110 | /// 111 | internal static string UnexpectedValue { 112 | get { 113 | return ResourceManager.GetString("UnexpectedValue", resourceCulture); 114 | } 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/DicomClientResource.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | '{0}' is not a supported export destination. 122 | {0} Value 123 | 124 | 125 | '{0}' is not a supported export source. 126 | {0} Value 127 | 128 | 129 | Type '{0}' is not supported. 130 | {0} Type name 131 | 132 | 133 | Read is not supported for the type '{0}'. 134 | {0} Type Name 135 | 136 | 137 | Write is not supported for the type '{0}'. 138 | {0} Type Name 139 | 140 | 141 | Expected value '{0}' to be one of the following values: [{1}] 142 | {0} Actual value. {1} Comma-separated list of expected values. 143 | 144 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/DicomWebAsyncEnumerableResponse.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System.Collections.Generic; 7 | using System.Net.Http; 8 | using System.Threading; 9 | 10 | namespace Microsoft.Health.Dicom.Client; 11 | 12 | public class DicomWebAsyncEnumerableResponse : DicomWebResponse, IAsyncEnumerable 13 | { 14 | private readonly IAsyncEnumerable _enumerable; 15 | 16 | public DicomWebAsyncEnumerableResponse(HttpResponseMessage response, IAsyncEnumerable enumerable) 17 | : base(response) 18 | { 19 | _enumerable = enumerable; 20 | } 21 | 22 | public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) 23 | => _enumerable.GetAsyncEnumerator(cancellationToken); 24 | } 25 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/DicomWebClient.ChangeFeed.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | using System.Net.Http; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using Microsoft.Health.Dicom.Client.Models; 11 | 12 | namespace Microsoft.Health.Dicom.Client; 13 | 14 | public partial class DicomWebClient : IDicomWebClient 15 | { 16 | public async Task> GetChangeFeed(string queryString, CancellationToken cancellationToken = default) 17 | { 18 | using var request = new HttpRequestMessage( 19 | HttpMethod.Get, 20 | new Uri($"/{ApiVersion}/changefeed{queryString}", UriKind.Relative)); 21 | 22 | HttpResponseMessage response = await HttpClient.SendAsync(request, cancellationToken) 23 | .ConfigureAwait(false); 24 | 25 | await EnsureSuccessStatusCodeAsync(response).ConfigureAwait(false); 26 | 27 | return new DicomWebAsyncEnumerableResponse( 28 | response, 29 | DeserializeAsAsyncEnumerable(response.Content)); 30 | } 31 | 32 | public async Task> GetChangeFeedLatest(string queryString, CancellationToken cancellationToken = default) 33 | { 34 | using var request = new HttpRequestMessage( 35 | HttpMethod.Get, 36 | new Uri($"/{ApiVersion}/changefeed/latest{queryString}", UriKind.Relative)); 37 | 38 | HttpResponseMessage response = await HttpClient.SendAsync(request, cancellationToken) 39 | .ConfigureAwait(false); 40 | 41 | await EnsureSuccessStatusCodeAsync(response).ConfigureAwait(false); 42 | 43 | return new DicomWebResponse(response, ValueFactory); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/DicomWebClient.Delete.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | using System.Globalization; 8 | using System.Net.Http; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using EnsureThat; 12 | 13 | namespace Microsoft.Health.Dicom.Client; 14 | 15 | public partial class DicomWebClient : IDicomWebClient 16 | { 17 | public Task DeleteStudyAsync( 18 | string studyInstanceUid, 19 | string partitionName = default, 20 | CancellationToken cancellationToken = default) 21 | { 22 | EnsureArg.IsNotNullOrWhiteSpace(studyInstanceUid, nameof(studyInstanceUid)); 23 | 24 | return DeleteAsync( 25 | GenerateRequestUri(string.Format(CultureInfo.InvariantCulture, DicomWebConstants.BaseStudyUriFormat, studyInstanceUid), partitionName), 26 | cancellationToken); 27 | } 28 | 29 | public Task DeleteSeriesAsync( 30 | string studyInstanceUid, 31 | string seriesInstanceUid, 32 | string partitionName = default, 33 | CancellationToken cancellationToken = default) 34 | { 35 | EnsureArg.IsNotNullOrWhiteSpace(studyInstanceUid, nameof(studyInstanceUid)); 36 | EnsureArg.IsNotNullOrWhiteSpace(seriesInstanceUid, nameof(seriesInstanceUid)); 37 | 38 | return DeleteAsync( 39 | GenerateRequestUri(string.Format(CultureInfo.InvariantCulture, DicomWebConstants.BaseSeriesUriFormat, studyInstanceUid, seriesInstanceUid), partitionName), 40 | cancellationToken); 41 | } 42 | 43 | public Task DeleteInstanceAsync( 44 | string studyInstanceUid, 45 | string seriesInstanceUid, 46 | string sopInstanceUid, 47 | string partitionName = default, 48 | CancellationToken cancellationToken = default) 49 | { 50 | EnsureArg.IsNotNullOrWhiteSpace(studyInstanceUid, nameof(studyInstanceUid)); 51 | EnsureArg.IsNotNullOrWhiteSpace(seriesInstanceUid, nameof(seriesInstanceUid)); 52 | EnsureArg.IsNotNullOrWhiteSpace(sopInstanceUid, nameof(sopInstanceUid)); 53 | 54 | 55 | return DeleteAsync( 56 | GenerateRequestUri(string.Format(CultureInfo.InvariantCulture, DicomWebConstants.BaseInstanceUriFormat, studyInstanceUid, seriesInstanceUid, sopInstanceUid), partitionName), 57 | cancellationToken); 58 | } 59 | 60 | private async Task DeleteAsync(Uri requestUri, CancellationToken cancellationToken) 61 | { 62 | using var request = new HttpRequestMessage(HttpMethod.Delete, requestUri); 63 | 64 | HttpResponseMessage response = await HttpClient.SendAsync(request, cancellationToken) 65 | .ConfigureAwait(false); 66 | 67 | await EnsureSuccessStatusCodeAsync(response).ConfigureAwait(false); 68 | 69 | return new DicomWebResponse(response); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/DicomWebClient.Export.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System.Net.Http; 7 | using System.Text.Json; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using EnsureThat; 11 | using Microsoft.Health.Dicom.Client.Models; 12 | 13 | namespace Microsoft.Health.Dicom.Client; 14 | 15 | public partial class DicomWebClient : IDicomWebClient 16 | { 17 | public async Task> StartExportAsync( 18 | ExportDataOptions source, 19 | ExportDataOptions destination, 20 | string partitionName = default, 21 | CancellationToken cancellationToken = default) 22 | { 23 | EnsureArg.IsNotNull(source, nameof(source)); 24 | EnsureArg.IsNotNull(destination, nameof(destination)); 25 | 26 | string jsonString = JsonSerializer.Serialize( 27 | new ExportSpecification 28 | { 29 | Destination = destination, 30 | Source = source, 31 | }, 32 | JsonSerializerOptions); 33 | 34 | using var request = new HttpRequestMessage(HttpMethod.Post, GenerateRequestUri(DicomWebConstants.ExportUriString, partitionName)); 35 | request.Content = new StringContent(jsonString); 36 | request.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(DicomWebConstants.ApplicationJsonMediaType); 37 | 38 | HttpResponseMessage response = await HttpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); 39 | await EnsureSuccessStatusCodeAsync(response).ConfigureAwait(false); 40 | return new DicomWebResponse(response, ValueFactory); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/DicomWebClient.ExtendedQueryTag.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Globalization; 9 | using System.Net.Http; 10 | using System.Net.Http.Headers; 11 | using System.Text; 12 | using System.Text.Json; 13 | using System.Threading; 14 | using System.Threading.Tasks; 15 | using EnsureThat; 16 | using Microsoft.Health.Dicom.Client.Models; 17 | 18 | namespace Microsoft.Health.Dicom.Client; 19 | 20 | public partial class DicomWebClient : IDicomWebClient 21 | { 22 | public async Task> AddExtendedQueryTagAsync(IEnumerable tagEntries, CancellationToken cancellationToken = default) 23 | { 24 | EnsureArg.IsNotNull(tagEntries, nameof(tagEntries)); 25 | string jsonString = JsonSerializer.Serialize(tagEntries, JsonSerializerOptions); 26 | var uri = new Uri($"/{ApiVersion}{DicomWebConstants.BaseExtendedQueryTagUri}", UriKind.Relative); 27 | using var request = new HttpRequestMessage(HttpMethod.Post, uri); 28 | { 29 | request.Content = new StringContent(jsonString); 30 | request.Content.Headers.ContentType = new MediaTypeHeaderValue(DicomWebConstants.ApplicationJsonMediaType) { CharSet = Encoding.UTF8.WebName }; 31 | } 32 | 33 | HttpResponseMessage response = await HttpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); 34 | await EnsureSuccessStatusCodeAsync(response).ConfigureAwait(false); 35 | return new DicomWebResponse(response, ValueFactory); 36 | } 37 | 38 | public async Task DeleteExtendedQueryTagAsync(string tagPath, CancellationToken cancellationToken = default) 39 | { 40 | EnsureArg.IsNotNullOrWhiteSpace(tagPath, nameof(tagPath)); 41 | 42 | var uri = new Uri($"/{ApiVersion}{DicomWebConstants.BaseExtendedQueryTagUri}/{tagPath}", UriKind.Relative); 43 | using var request = new HttpRequestMessage(HttpMethod.Delete, uri); 44 | 45 | HttpResponseMessage response = await HttpClient.SendAsync(request, cancellationToken) 46 | .ConfigureAwait(false); 47 | 48 | await EnsureSuccessStatusCodeAsync(response).ConfigureAwait(false); 49 | 50 | return new DicomWebResponse(response); 51 | } 52 | 53 | public async Task>> GetExtendedQueryTagsAsync(int limit, long offset, CancellationToken cancellationToken = default) 54 | { 55 | EnsureArg.IsGte(limit, 1, nameof(limit)); 56 | EnsureArg.IsGte(offset, 0, nameof(offset)); 57 | 58 | var uri = new Uri($"/{ApiVersion}{DicomWebConstants.BaseExtendedQueryTagUri}?{DicomWebConstants.LimitParameter}={limit}&{DicomWebConstants.OffsetParameter}={offset}", UriKind.Relative); 59 | using var request = new HttpRequestMessage(HttpMethod.Get, uri); 60 | HttpResponseMessage response = await HttpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); 61 | await EnsureSuccessStatusCodeAsync(response).ConfigureAwait(false); 62 | return new DicomWebResponse>(response, ValueFactory>); 63 | } 64 | 65 | public async Task> GetExtendedQueryTagAsync(string tagPath, CancellationToken cancellationToken) 66 | { 67 | EnsureArg.IsNotNullOrWhiteSpace(tagPath, nameof(tagPath)); 68 | 69 | var uri = new Uri($"/{ApiVersion}{DicomWebConstants.BaseExtendedQueryTagUri}/{tagPath}", UriKind.Relative); 70 | using var request = new HttpRequestMessage(HttpMethod.Get, uri); 71 | HttpResponseMessage response = await HttpClient.SendAsync(request, cancellationToken) 72 | .ConfigureAwait(false); 73 | await EnsureSuccessStatusCodeAsync(response).ConfigureAwait(false); 74 | return new DicomWebResponse(response, ValueFactory); 75 | } 76 | 77 | public async Task>> GetExtendedQueryTagErrorsAsync(string tagPath, int limit, long offset, CancellationToken cancellationToken = default) 78 | { 79 | EnsureArg.IsNotNullOrWhiteSpace(tagPath, nameof(tagPath)); 80 | EnsureArg.IsGte(limit, 1, nameof(limit)); 81 | EnsureArg.IsGte(offset, 0, nameof(offset)); 82 | 83 | var uri = new Uri( 84 | string.Format( 85 | CultureInfo.InvariantCulture, 86 | $"/{ApiVersion}{DicomWebConstants.BaseErrorsUriFormat}?{DicomWebConstants.LimitParameter}={limit}&{DicomWebConstants.OffsetParameter}={offset}", 87 | tagPath), 88 | UriKind.Relative); 89 | 90 | using var request = new HttpRequestMessage(HttpMethod.Get, uri); 91 | HttpResponseMessage response = await HttpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); 92 | await EnsureSuccessStatusCodeAsync(response).ConfigureAwait(false); 93 | return new DicomWebResponse>(response, ValueFactory>); 94 | } 95 | 96 | public async Task> UpdateExtendedQueryTagAsync(string tagPath, UpdateExtendedQueryTagEntry newValue, CancellationToken cancellationToken = default) 97 | { 98 | EnsureArg.IsNotNullOrWhiteSpace(tagPath, nameof(tagPath)); 99 | EnsureArg.IsNotNull(newValue, nameof(newValue)); 100 | EnsureArg.EnumIsDefined(newValue.QueryStatus, nameof(newValue)); 101 | string jsonString = JsonSerializer.Serialize(newValue, JsonSerializerOptions); 102 | var uri = new Uri($"/{ApiVersion}{DicomWebConstants.BaseExtendedQueryTagUri}/{tagPath}", UriKind.Relative); 103 | 104 | using var request = new HttpRequestMessage( 105 | #if NETSTANDARD2_0 106 | new HttpMethod("PATCH"), 107 | #else 108 | HttpMethod.Patch, 109 | #endif 110 | uri); 111 | { 112 | request.Content = new StringContent(jsonString); 113 | request.Content.Headers.ContentType = new MediaTypeHeaderValue(DicomWebConstants.ApplicationJsonMediaType) { CharSet = Encoding.UTF8.WebName }; 114 | } 115 | 116 | HttpResponseMessage response = await HttpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); 117 | await EnsureSuccessStatusCodeAsync(response).ConfigureAwait(false); 118 | return new DicomWebResponse(response, ValueFactory); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/DicomWebClient.Operation.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | using System.Net.Http; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using Microsoft.Health.Dicom.Client.Models; 11 | using Microsoft.Health.Operations; 12 | 13 | namespace Microsoft.Health.Dicom.Client; 14 | 15 | public partial class DicomWebClient : IDicomWebClient 16 | { 17 | public async Task>> GetOperationStateAsync(Guid operationId, CancellationToken cancellationToken = default) 18 | { 19 | var uri = new Uri($"/{ApiVersion}{DicomWebConstants.BaseOperationUri}/{operationId.ToString(OperationId.FormatSpecifier)}", UriKind.Relative); 20 | using var request = new HttpRequestMessage(HttpMethod.Get, uri); 21 | HttpResponseMessage response = await HttpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); 22 | await EnsureSuccessStatusCodeAsync(response).ConfigureAwait(false); 23 | return new DicomWebResponse>(response, ValueFactory>); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/DicomWebClient.Partition.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System.Collections.Generic; 7 | using System.Net.Http; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using Microsoft.Health.Dicom.Client.Models; 11 | 12 | namespace Microsoft.Health.Dicom.Client; 13 | 14 | public partial class DicomWebClient : IDicomWebClient 15 | { 16 | public async Task>> GetPartitionsAsync(CancellationToken cancellationToken = default) 17 | { 18 | using var request = new HttpRequestMessage( 19 | HttpMethod.Get, 20 | GenerateRequestUri(DicomWebConstants.PartitionsUriString)); 21 | 22 | HttpResponseMessage response = await HttpClient.SendAsync(request, cancellationToken) 23 | .ConfigureAwait(false); 24 | 25 | await EnsureSuccessStatusCodeAsync(response).ConfigureAwait(false); 26 | 27 | return new DicomWebResponse>(response, ValueFactory>); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/DicomWebClient.Reference.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System.Net.Http; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using EnsureThat; 10 | using Microsoft.Health.Dicom.Client.Models; 11 | 12 | namespace Microsoft.Health.Dicom.Client; 13 | 14 | public partial class DicomWebClient : IDicomWebClient 15 | { 16 | public async Task> ResolveReferenceAsync(IResourceReference resourceReference, CancellationToken cancellationToken = default) 17 | { 18 | EnsureArg.IsNotNull(resourceReference, nameof(resourceReference)); 19 | 20 | using var request = new HttpRequestMessage(HttpMethod.Get, resourceReference.Href); 21 | HttpResponseMessage response = await HttpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); 22 | await EnsureSuccessStatusCodeAsync(response).ConfigureAwait(false); 23 | return new DicomWebResponse(response, ValueFactory); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/DicomWebClient.Retrieve.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | using System.Globalization; 8 | using System.IO; 9 | using System.Net; 10 | using System.Net.Http; 11 | using System.Threading; 12 | using System.Threading.Tasks; 13 | using EnsureThat; 14 | using FellowOakDicom; 15 | using Microsoft.Net.Http.Headers; 16 | 17 | namespace Microsoft.Health.Dicom.Client; 18 | 19 | public partial class DicomWebClient : IDicomWebClient 20 | { 21 | public async Task> RetrieveStudyAsync( 22 | string studyInstanceUid, 23 | string dicomTransferSyntax = DicomWebConstants.OriginalDicomTransferSyntax, 24 | string partitionName = default, 25 | bool requestOriginalVersion = default, 26 | CancellationToken cancellationToken = default) 27 | { 28 | EnsureArg.IsNotNullOrWhiteSpace(studyInstanceUid, nameof(studyInstanceUid)); 29 | 30 | return await RetrieveInstancesAsync( 31 | GenerateRequestUri(string.Format(CultureInfo.InvariantCulture, DicomWebConstants.BaseStudyUriFormat, studyInstanceUid), partitionName), 32 | dicomTransferSyntax, 33 | requestOriginalVersion, 34 | cancellationToken).ConfigureAwait(false); 35 | } 36 | 37 | public async Task> RetrieveStudyMetadataAsync( 38 | string studyInstanceUid, 39 | string ifNoneMatch = default, 40 | string partitionName = default, 41 | bool requestOriginalVersion = default, 42 | CancellationToken cancellationToken = default) 43 | { 44 | EnsureArg.IsNotNullOrWhiteSpace(studyInstanceUid, nameof(studyInstanceUid)); 45 | 46 | return await RetrieveMetadataAsync( 47 | GenerateRequestUri(string.Format(CultureInfo.InvariantCulture, DicomWebConstants.BaseRetrieveStudyMetadataUriFormat, studyInstanceUid), partitionName), 48 | ifNoneMatch, 49 | requestOriginalVersion, 50 | cancellationToken).ConfigureAwait(false); 51 | } 52 | 53 | public async Task> RetrieveSeriesAsync( 54 | string studyInstanceUid, 55 | string seriesInstanceUid, 56 | string dicomTransferSyntax = DicomWebConstants.OriginalDicomTransferSyntax, 57 | string partitionName = default, 58 | bool requestOriginalVersion = default, 59 | CancellationToken cancellationToken = default) 60 | { 61 | EnsureArg.IsNotNullOrWhiteSpace(studyInstanceUid, nameof(studyInstanceUid)); 62 | EnsureArg.IsNotNullOrWhiteSpace(seriesInstanceUid, nameof(seriesInstanceUid)); 63 | 64 | return await RetrieveInstancesAsync( 65 | GenerateRequestUri(string.Format(CultureInfo.InvariantCulture, DicomWebConstants.BaseSeriesUriFormat, studyInstanceUid, seriesInstanceUid), partitionName), 66 | dicomTransferSyntax, 67 | requestOriginalVersion, 68 | cancellationToken).ConfigureAwait(false); 69 | } 70 | 71 | public async Task> RetrieveSeriesMetadataAsync( 72 | string studyInstanceUid, 73 | string seriesInstanceUid, 74 | string ifNoneMatch = default, 75 | string partitionName = default, 76 | bool requestOriginalVersion = default, 77 | CancellationToken cancellationToken = default) 78 | { 79 | EnsureArg.IsNotNullOrWhiteSpace(studyInstanceUid, nameof(studyInstanceUid)); 80 | EnsureArg.IsNotNullOrWhiteSpace(seriesInstanceUid, nameof(seriesInstanceUid)); 81 | 82 | return await RetrieveMetadataAsync( 83 | GenerateRequestUri(string.Format(CultureInfo.InvariantCulture, DicomWebConstants.BaseRetrieveSeriesMetadataUriFormat, studyInstanceUid, seriesInstanceUid), partitionName), 84 | ifNoneMatch, 85 | requestOriginalVersion, 86 | cancellationToken).ConfigureAwait(false); 87 | } 88 | 89 | public async Task> RetrieveInstanceAsync( 90 | string studyInstanceUid, 91 | string seriesInstanceUid, 92 | string sopInstanceUid, 93 | string dicomTransferSyntax = DicomWebConstants.OriginalDicomTransferSyntax, 94 | string partitionName = default, 95 | bool requestOriginalVersion = default, 96 | CancellationToken cancellationToken = default) 97 | { 98 | EnsureArg.IsNotNullOrWhiteSpace(studyInstanceUid, nameof(studyInstanceUid)); 99 | EnsureArg.IsNotNullOrWhiteSpace(seriesInstanceUid, nameof(seriesInstanceUid)); 100 | EnsureArg.IsNotNullOrWhiteSpace(sopInstanceUid, nameof(sopInstanceUid)); 101 | 102 | return await RetrieveInstanceAsync( 103 | GenerateRequestUri(string.Format(CultureInfo.InvariantCulture, DicomWebConstants.BaseInstanceUriFormat, studyInstanceUid, seriesInstanceUid, sopInstanceUid), partitionName), 104 | dicomTransferSyntax, 105 | requestOriginalVersion, 106 | cancellationToken).ConfigureAwait(false); 107 | } 108 | 109 | public async Task> RetrieveInstanceStreamAsync( 110 | string studyInstanceUid, 111 | string seriesInstanceUid, 112 | string sopInstanceUid, 113 | string dicomTransferSyntax = DicomWebConstants.OriginalDicomTransferSyntax, 114 | string partitionName = default, 115 | bool requestOriginalVersion = default, 116 | CancellationToken cancellationToken = default) 117 | { 118 | EnsureArg.IsNotNullOrWhiteSpace(studyInstanceUid, nameof(studyInstanceUid)); 119 | EnsureArg.IsNotNullOrWhiteSpace(seriesInstanceUid, nameof(seriesInstanceUid)); 120 | EnsureArg.IsNotNullOrWhiteSpace(sopInstanceUid, nameof(sopInstanceUid)); 121 | 122 | return await RetrieveInstanceStreamAsync( 123 | GenerateRequestUri(string.Format(CultureInfo.InvariantCulture, DicomWebConstants.BaseInstanceUriFormat, studyInstanceUid, seriesInstanceUid, sopInstanceUid), partitionName), 124 | dicomTransferSyntax, 125 | requestOriginalVersion, 126 | cancellationToken).ConfigureAwait(false); 127 | } 128 | 129 | public async Task> RetrieveRenderedInstanceAsync( 130 | string studyInstanceUid, 131 | string seriesInstanceUid, 132 | string sopInstanceUid, 133 | int quality = 100, 134 | string mediaType = DicomWebConstants.ImageJpegMediaType, 135 | string partitionName = default, 136 | CancellationToken cancellationToken = default) 137 | { 138 | EnsureArg.IsNotNullOrWhiteSpace(studyInstanceUid, nameof(studyInstanceUid)); 139 | EnsureArg.IsNotNullOrWhiteSpace(seriesInstanceUid, nameof(seriesInstanceUid)); 140 | EnsureArg.IsNotNullOrWhiteSpace(sopInstanceUid, nameof(sopInstanceUid)); 141 | 142 | return await RetrieveRenderedAsync( 143 | GenerateRequestUri(string.Format(CultureInfo.InvariantCulture, DicomWebConstants.BaseRetrieveInstanceRenderedUriFormat, studyInstanceUid, seriesInstanceUid, sopInstanceUid, quality), partitionName), 144 | mediaType, 145 | cancellationToken).ConfigureAwait(false); 146 | } 147 | 148 | public async Task> RetrieveInstanceMetadataAsync( 149 | string studyInstanceUid, 150 | string seriesInstanceUid, 151 | string sopInstanceUid, 152 | string ifNoneMatch = default, 153 | string partitionName = default, 154 | bool requestOriginalVersion = default, 155 | CancellationToken cancellationToken = default) 156 | { 157 | EnsureArg.IsNotNullOrWhiteSpace(studyInstanceUid, nameof(studyInstanceUid)); 158 | EnsureArg.IsNotNullOrWhiteSpace(seriesInstanceUid, nameof(seriesInstanceUid)); 159 | EnsureArg.IsNotNullOrWhiteSpace(sopInstanceUid, nameof(sopInstanceUid)); 160 | 161 | return await RetrieveMetadataAsync( 162 | GenerateRequestUri(string.Format(CultureInfo.InvariantCulture, DicomWebConstants.BaseRetrieveInstanceMetadataUriFormat, studyInstanceUid, seriesInstanceUid, sopInstanceUid), partitionName), 163 | ifNoneMatch, 164 | requestOriginalVersion, 165 | cancellationToken).ConfigureAwait(false); 166 | } 167 | 168 | public async Task> RetrieveFramesAsync( 169 | string studyInstanceUid, 170 | string seriesInstanceUid, 171 | string sopInstanceUid, 172 | int[] frames = default, 173 | string mediaType = DicomWebConstants.ApplicationOctetStreamMediaType, 174 | string dicomTransferSyntax = DicomWebConstants.OriginalDicomTransferSyntax, 175 | string partitionName = default, 176 | CancellationToken cancellationToken = default) 177 | { 178 | EnsureArg.IsNotNullOrWhiteSpace(studyInstanceUid, nameof(studyInstanceUid)); 179 | EnsureArg.IsNotNullOrWhiteSpace(seriesInstanceUid, nameof(seriesInstanceUid)); 180 | EnsureArg.IsNotNullOrWhiteSpace(sopInstanceUid, nameof(sopInstanceUid)); 181 | var requestUri = GenerateRequestUri( 182 | string.Format( 183 | CultureInfo.InvariantCulture, 184 | DicomWebConstants.BaseRetrieveFramesUriFormat, 185 | studyInstanceUid, 186 | seriesInstanceUid, 187 | sopInstanceUid, 188 | string.Join("%2C", frames)), 189 | partitionName); 190 | return await RetrieveFramesAsync(requestUri, mediaType, dicomTransferSyntax, cancellationToken).ConfigureAwait(false); 191 | } 192 | 193 | public async Task> RetrieveRenderedFrameAsync( 194 | string studyInstanceUid, 195 | string seriesInstanceUid, 196 | string sopInstanceUid, 197 | int frame, 198 | int quality = 100, 199 | string mediaType = DicomWebConstants.ImageJpegMediaType, 200 | string partitionName = default, 201 | CancellationToken cancellationToken = default) 202 | { 203 | EnsureArg.IsNotNullOrWhiteSpace(studyInstanceUid, nameof(studyInstanceUid)); 204 | EnsureArg.IsNotNullOrWhiteSpace(seriesInstanceUid, nameof(seriesInstanceUid)); 205 | EnsureArg.IsNotNullOrWhiteSpace(sopInstanceUid, nameof(sopInstanceUid)); 206 | 207 | return await RetrieveRenderedAsync( 208 | GenerateRequestUri(string.Format(CultureInfo.InvariantCulture, DicomWebConstants.BaseRetrieveFrameRenderedUriFormat, studyInstanceUid, seriesInstanceUid, sopInstanceUid, frame, quality), partitionName), 209 | mediaType, 210 | cancellationToken).ConfigureAwait(false); 211 | } 212 | 213 | 214 | public async Task> RetrieveSingleFrameAsync( 215 | string studyInstanceUid, 216 | string seriesInstanceUid, 217 | string sopInstanceUid, 218 | int frame, 219 | string partitionName = default, 220 | CancellationToken cancellationToken = default) 221 | { 222 | EnsureArg.IsNotNullOrWhiteSpace(studyInstanceUid, nameof(studyInstanceUid)); 223 | EnsureArg.IsNotNullOrWhiteSpace(seriesInstanceUid, nameof(seriesInstanceUid)); 224 | EnsureArg.IsNotNullOrWhiteSpace(sopInstanceUid, nameof(sopInstanceUid)); 225 | var requestUri = GenerateRequestUri( 226 | string.Format( 227 | CultureInfo.InvariantCulture, 228 | DicomWebConstants.BaseRetrieveFramesUriFormat, 229 | studyInstanceUid, 230 | seriesInstanceUid, 231 | sopInstanceUid, 232 | frame), 233 | partitionName); 234 | 235 | using var request = new HttpRequestMessage(HttpMethod.Get, requestUri); 236 | request.Headers.TryAddWithoutValidation( 237 | "Accept", 238 | CreateAcceptHeader(DicomWebConstants.MediaTypeApplicationOctetStream, DicomWebConstants.OriginalDicomTransferSyntax)); 239 | 240 | HttpResponseMessage response = await HttpClient.SendAsync(request, cancellationToken) 241 | .ConfigureAwait(false); 242 | await EnsureSuccessStatusCodeAsync(response).ConfigureAwait(false); 243 | 244 | return new DicomWebResponse( 245 | response, 246 | async content => 247 | { 248 | MemoryStream memoryStream = GetMemoryStream(); 249 | await content.CopyToAsync(memoryStream).ConfigureAwait(false); 250 | memoryStream.Seek(0, SeekOrigin.Begin); 251 | return memoryStream; 252 | }); 253 | } 254 | 255 | private async Task> RetrieveInstanceAsync( 256 | Uri requestUri, 257 | string dicomTransferSyntax, 258 | bool requestOriginalVersion, 259 | CancellationToken cancellationToken) 260 | { 261 | EnsureArg.IsNotNull(requestUri, nameof(requestUri)); 262 | 263 | using var request = new HttpRequestMessage(HttpMethod.Get, requestUri); 264 | 265 | request.Headers.TryAddWithoutValidation( 266 | "Accept", 267 | CreateAcceptHeader(DicomWebConstants.MediaTypeApplicationDicom, dicomTransferSyntax)); 268 | 269 | if (requestOriginalVersion) 270 | { 271 | request.Headers.TryAddWithoutValidation(DicomWebConstants.RequestOriginalVersion, bool.TrueString); 272 | } 273 | 274 | HttpResponseMessage response = await HttpClient.SendAsync(request, cancellationToken) 275 | .ConfigureAwait(false); 276 | 277 | await EnsureSuccessStatusCodeAsync(response).ConfigureAwait(false); 278 | 279 | return new DicomWebResponse( 280 | response, 281 | async content => 282 | { 283 | MemoryStream memoryStream = GetMemoryStream(); 284 | await content.CopyToAsync(memoryStream).ConfigureAwait(false); 285 | memoryStream.Seek(0, SeekOrigin.Begin); 286 | 287 | return await DicomFile.OpenAsync(memoryStream).ConfigureAwait(false); 288 | }); 289 | } 290 | 291 | private async Task> RetrieveInstanceStreamAsync( 292 | Uri requestUri, 293 | string dicomTransferSyntax, 294 | bool requestOriginalVersion, 295 | CancellationToken cancellationToken) 296 | { 297 | EnsureArg.IsNotNull(requestUri, nameof(requestUri)); 298 | 299 | using var request = new HttpRequestMessage(HttpMethod.Get, requestUri); 300 | 301 | request.Headers.TryAddWithoutValidation( 302 | "Accept", 303 | CreateAcceptHeader(DicomWebConstants.MediaTypeApplicationDicom, dicomTransferSyntax)); 304 | 305 | if (requestOriginalVersion) 306 | { 307 | request.Headers.TryAddWithoutValidation(DicomWebConstants.RequestOriginalVersion, bool.TrueString); 308 | } 309 | 310 | HttpResponseMessage response = await HttpClient.SendAsync(request, cancellationToken) 311 | .ConfigureAwait(false); 312 | 313 | await EnsureSuccessStatusCodeAsync(response).ConfigureAwait(false); 314 | 315 | return new DicomWebResponse( 316 | response, 317 | async content => 318 | { 319 | MemoryStream memoryStream = GetMemoryStream(); 320 | await content.CopyToAsync(memoryStream).ConfigureAwait(false); 321 | memoryStream.Seek(0, SeekOrigin.Begin); 322 | return memoryStream; 323 | }); 324 | } 325 | 326 | private async Task> RetrieveRenderedAsync( 327 | Uri requestUri, 328 | string mediaType, 329 | CancellationToken cancellationToken) 330 | { 331 | EnsureArg.IsNotNull(requestUri, nameof(requestUri)); 332 | 333 | using var request = new HttpRequestMessage(HttpMethod.Get, requestUri); 334 | 335 | request.Headers.TryAddWithoutValidation( 336 | "Accept", 337 | mediaType); 338 | 339 | HttpResponseMessage response = await HttpClient.SendAsync(request, cancellationToken) 340 | .ConfigureAwait(false); 341 | 342 | await EnsureSuccessStatusCodeAsync(response).ConfigureAwait(false); 343 | 344 | return new DicomWebResponse( 345 | response, 346 | async content => 347 | { 348 | MemoryStream memoryStream = GetMemoryStream(); 349 | await content.CopyToAsync(memoryStream).ConfigureAwait(false); 350 | memoryStream.Seek(0, SeekOrigin.Begin); 351 | 352 | return memoryStream; 353 | }); 354 | } 355 | 356 | private async Task> RetrieveInstancesAsync( 357 | Uri requestUri, 358 | string dicomTransferSyntax, 359 | bool requestOriginalVersion, 360 | CancellationToken cancellationToken) 361 | { 362 | EnsureArg.IsNotNull(requestUri, nameof(requestUri)); 363 | 364 | using var request = new HttpRequestMessage(HttpMethod.Get, requestUri); 365 | 366 | request.Headers.TryAddWithoutValidation( 367 | "Accept", 368 | CreateAcceptHeader(CreateMultipartMediaTypeHeader(DicomWebConstants.ApplicationDicomMediaType), dicomTransferSyntax)); 369 | 370 | if (requestOriginalVersion) 371 | { 372 | request.Headers.TryAddWithoutValidation(DicomWebConstants.RequestOriginalVersion, bool.TrueString); 373 | } 374 | 375 | HttpResponseMessage response = await HttpClient.SendAsync(request, cancellationToken) 376 | .ConfigureAwait(false); 377 | 378 | await EnsureSuccessStatusCodeAsync(response).ConfigureAwait(false); 379 | 380 | return new DicomWebAsyncEnumerableResponse( 381 | response, 382 | ReadMultipartResponseAsDicomFileAsync(response.Content, cancellationToken)); 383 | } 384 | 385 | private async Task> RetrieveFramesAsync( 386 | Uri requestUri, 387 | string mediaType, 388 | string dicomTransferSyntax, 389 | CancellationToken cancellationToken) 390 | { 391 | EnsureArg.IsNotNull(requestUri, nameof(requestUri)); 392 | EnsureArg.IsNotNullOrWhiteSpace(mediaType, nameof(mediaType)); 393 | 394 | using var request = new HttpRequestMessage(HttpMethod.Get, requestUri); 395 | 396 | request.Headers.TryAddWithoutValidation( 397 | "Accept", 398 | CreateAcceptHeader(CreateMultipartMediaTypeHeader(mediaType), dicomTransferSyntax)); 399 | 400 | HttpResponseMessage response = await HttpClient.SendAsync(request, cancellationToken) 401 | .ConfigureAwait(false); 402 | 403 | await EnsureSuccessStatusCodeAsync(response).ConfigureAwait(false); 404 | 405 | return new DicomWebAsyncEnumerableResponse( 406 | response, 407 | ReadMultipartResponseAsStreamsAsync(response.Content, cancellationToken)); 408 | } 409 | 410 | private async Task> RetrieveMetadataAsync( 411 | Uri requestUri, 412 | string ifNoneMatch, 413 | bool requestOriginalVersion, 414 | CancellationToken cancellationToken) 415 | { 416 | EnsureArg.IsNotNull(requestUri, nameof(requestUri)); 417 | 418 | using var request = new HttpRequestMessage(HttpMethod.Get, requestUri); 419 | 420 | request.Headers.TryAddWithoutValidation( 421 | "Accept", 422 | CreateAcceptHeader(DicomWebConstants.MediaTypeApplicationDicomJson, null)); 423 | 424 | if (requestOriginalVersion) 425 | { 426 | request.Headers.TryAddWithoutValidation(DicomWebConstants.RequestOriginalVersion, bool.TrueString); 427 | } 428 | 429 | if (!string.IsNullOrEmpty(ifNoneMatch)) 430 | { 431 | request.Headers.TryAddWithoutValidation(HeaderNames.IfNoneMatch, ifNoneMatch); 432 | } 433 | 434 | HttpResponseMessage response = await HttpClient.SendAsync(request, cancellationToken) 435 | .ConfigureAwait(false); 436 | 437 | await EnsureSuccessStatusCodeAsync( 438 | response, 439 | additionalFailureInspector: (statusCode, responseHeaders, contentHeaders, responseBody) => 440 | { 441 | // If the content has not changed, the status returned will be NotModified and so we need to treat it specially. 442 | return statusCode == HttpStatusCode.NotModified; 443 | }) 444 | .ConfigureAwait(false); 445 | 446 | return new DicomWebAsyncEnumerableResponse( 447 | response, 448 | DeserializeAsAsyncEnumerable(response.Content)); 449 | } 450 | } 451 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/DicomWebClient.Search.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | using System.Globalization; 8 | using System.Net.Http; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using EnsureThat; 12 | using FellowOakDicom; 13 | 14 | namespace Microsoft.Health.Dicom.Client; 15 | 16 | public partial class DicomWebClient : IDicomWebClient 17 | { 18 | public async Task> QueryStudyAsync( 19 | string queryString, 20 | string partitionName = default, 21 | CancellationToken cancellationToken = default) 22 | { 23 | var uri = GenerateRequestUri(DicomWebConstants.StudiesUriString + FormatQueryString(queryString), partitionName); 24 | 25 | return await QueryAsync(uri, cancellationToken).ConfigureAwait(false); 26 | } 27 | 28 | public Task> QueryStudySeriesAsync( 29 | string studyInstanceUid, 30 | string queryString, 31 | string partitionName = default, 32 | CancellationToken cancellationToken = default) 33 | { 34 | EnsureArg.IsNotNullOrWhiteSpace(studyInstanceUid, nameof(studyInstanceUid)); 35 | 36 | var uri = GenerateRequestUri(string.Format(CultureInfo.InvariantCulture, DicomWebConstants.QueryStudySeriesUriFormat, studyInstanceUid) + FormatQueryString(queryString), partitionName); 37 | 38 | return QueryAsync(uri, cancellationToken); 39 | } 40 | 41 | public async Task> QueryStudyInstanceAsync( 42 | string studyInstanceUid, 43 | string queryString, 44 | string partitionName = default, 45 | CancellationToken cancellationToken = default) 46 | { 47 | EnsureArg.IsNotNullOrWhiteSpace(studyInstanceUid, nameof(studyInstanceUid)); 48 | 49 | var uri = GenerateRequestUri(string.Format(CultureInfo.InvariantCulture, DicomWebConstants.QueryStudyInstanceUriFormat, studyInstanceUid) + FormatQueryString(queryString), partitionName); 50 | 51 | return await QueryAsync(uri, cancellationToken).ConfigureAwait(false); 52 | } 53 | 54 | public async Task> QueryStudySeriesInstanceAsync( 55 | string studyInstanceUid, 56 | string seriesInstanceUid, 57 | string queryString, 58 | string partitionName = default, 59 | CancellationToken cancellationToken = default) 60 | { 61 | EnsureArg.IsNotNullOrWhiteSpace(studyInstanceUid, nameof(studyInstanceUid)); 62 | EnsureArg.IsNotNullOrWhiteSpace(seriesInstanceUid, nameof(seriesInstanceUid)); 63 | 64 | var uri = GenerateRequestUri(string.Format(CultureInfo.InvariantCulture, DicomWebConstants.QueryStudySeriesInstancesUriFormat, studyInstanceUid, seriesInstanceUid) + FormatQueryString(queryString), partitionName); 65 | 66 | return await QueryAsync(uri, cancellationToken).ConfigureAwait(false); 67 | } 68 | 69 | public async Task> QuerySeriesAsync( 70 | string queryString, 71 | string partitionName = default, 72 | CancellationToken cancellationToken = default) 73 | { 74 | var uri = GenerateRequestUri(DicomWebConstants.SeriesUriString + FormatQueryString(queryString), partitionName); 75 | 76 | return await QueryAsync(uri, cancellationToken).ConfigureAwait(false); 77 | } 78 | 79 | public async Task> QuerySeriesInstanceAsync( 80 | string seriesInstanceUid, 81 | string queryString, 82 | string partitionName = default, 83 | CancellationToken cancellationToken = default) 84 | { 85 | EnsureArg.IsNotNullOrWhiteSpace(seriesInstanceUid, nameof(seriesInstanceUid)); 86 | 87 | var uri = GenerateRequestUri(string.Format(CultureInfo.InvariantCulture, DicomWebConstants.QuerySeriesInstanceUriFormat, seriesInstanceUid) + FormatQueryString(queryString), partitionName); 88 | 89 | return await QueryAsync(uri, cancellationToken).ConfigureAwait(false); 90 | } 91 | 92 | public async Task> QueryInstancesAsync( 93 | string queryString, 94 | string partitionName = default, 95 | CancellationToken cancellationToken = default) 96 | { 97 | var uri = GenerateRequestUri(DicomWebConstants.InstancesUriString + FormatQueryString(queryString), partitionName); 98 | 99 | return await QueryAsync(uri, cancellationToken).ConfigureAwait(false); 100 | } 101 | 102 | private async Task> QueryAsync( 103 | Uri requestUri, 104 | CancellationToken cancellationToken) 105 | { 106 | using var request = new HttpRequestMessage(HttpMethod.Get, requestUri); 107 | 108 | request.Headers.Accept.Add(DicomWebConstants.MediaTypeApplicationDicomJson); 109 | 110 | HttpResponseMessage response = await HttpClient.SendAsync(request, cancellationToken) 111 | .ConfigureAwait(false); 112 | 113 | await EnsureSuccessStatusCodeAsync(response).ConfigureAwait(false); 114 | 115 | return new DicomWebAsyncEnumerableResponse( 116 | response, 117 | DeserializeAsAsyncEnumerable(response.Content)); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/DicomWebClient.Store.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Diagnostics.CodeAnalysis; 9 | using System.IO; 10 | using System.Net; 11 | using System.Net.Http; 12 | using System.Net.Http.Headers; 13 | using System.Text.Json; 14 | using System.Threading; 15 | using System.Threading.Tasks; 16 | using EnsureThat; 17 | using FellowOakDicom; 18 | using FellowOakDicom.IO.Writer; 19 | using Microsoft.Health.Dicom.Client.Http; 20 | 21 | namespace Microsoft.Health.Dicom.Client; 22 | 23 | public partial class DicomWebClient : IDicomWebClient 24 | { 25 | public async Task> StoreAsync( 26 | IEnumerable dicomFiles, 27 | string studyInstanceUid, 28 | string partitionName = default, 29 | CancellationToken cancellationToken = default) 30 | { 31 | EnsureArg.IsNotNull(dicomFiles, nameof(dicomFiles)); 32 | 33 | using MultipartContent content = DicomContent.CreateMultipart(dicomFiles); 34 | return await StoreAsync( 35 | GenerateStoreRequestUri(partitionName, studyInstanceUid), 36 | content, 37 | cancellationToken).ConfigureAwait(false); 38 | } 39 | 40 | public async Task> StoreAsync( 41 | IEnumerable streams, 42 | string studyInstanceUid, 43 | string partitionName = default, 44 | CancellationToken cancellationToken = default) 45 | { 46 | EnsureArg.IsNotNull(streams, nameof(streams)); 47 | 48 | using MultipartContent content = CreateMultipartDicomStreamContent(streams); 49 | return await StoreAsync( 50 | GenerateStoreRequestUri(partitionName, studyInstanceUid), 51 | content, 52 | cancellationToken).ConfigureAwait(false); 53 | } 54 | 55 | public async Task> StoreAsync( 56 | Stream stream, 57 | string studyInstanceUid, 58 | string partitionName = default, 59 | CancellationToken cancellationToken = default) 60 | { 61 | EnsureArg.IsNotNull(stream, nameof(stream)); 62 | 63 | stream.Seek(0, SeekOrigin.Begin); 64 | 65 | return await StoreAsync( 66 | GenerateStoreRequestUri(partitionName, studyInstanceUid), 67 | CreateDicomStreamContent(stream), 68 | cancellationToken).ConfigureAwait(false); 69 | } 70 | 71 | public async Task> StoreAsync( 72 | DicomFile dicomFile, 73 | string studyInstanceUid, 74 | string partitionName = default, 75 | CancellationToken cancellationToken = default) 76 | { 77 | EnsureArg.IsNotNull(dicomFile, nameof(dicomFile)); 78 | 79 | return await StoreAsync( 80 | GenerateStoreRequestUri(partitionName, studyInstanceUid), 81 | new DicomContent(dicomFile), 82 | cancellationToken).ConfigureAwait(false); 83 | } 84 | 85 | public async Task> StoreAsync( 86 | HttpContent content, 87 | string partitionName = default, 88 | CancellationToken cancellationToken = default) 89 | { 90 | return await StoreAsync( 91 | GenerateStoreRequestUri(partitionName), 92 | content, 93 | cancellationToken).ConfigureAwait(false); 94 | } 95 | 96 | private async Task> StoreAsync(Uri requestUri, HttpContent content, CancellationToken cancellationToken = default) 97 | { 98 | EnsureArg.IsNotNull(requestUri, nameof(requestUri)); 99 | EnsureArg.IsNotNull(content, nameof(content)); 100 | 101 | using HttpRequestMessage request = new(HttpMethod.Post, requestUri) { Content = content }; 102 | request.Headers.Accept.Add(DicomWebConstants.MediaTypeApplicationDicomJson); 103 | 104 | HttpResponseMessage response = await HttpClient 105 | .SendAsync(request, cancellationToken) 106 | .ConfigureAwait(false); 107 | 108 | await EnsureSuccessStatusCodeAsync( 109 | response, 110 | additionalFailureInspector: (statusCode, responseHeaders, contentHeaders, responseBody) => 111 | { 112 | // If store fails, we will get Conflict status code but the body will be a DicomDataset, 113 | // so we need to handle this case specially. 114 | if (statusCode == HttpStatusCode.Conflict) 115 | { 116 | throw new DicomWebException( 117 | statusCode, 118 | responseHeaders, 119 | contentHeaders, 120 | JsonSerializer.Deserialize(responseBody, JsonSerializerOptions)); 121 | } 122 | 123 | return false; 124 | }).ConfigureAwait(false); 125 | 126 | return new DicomWebResponse(response, ValueFactory); 127 | } 128 | 129 | private static StreamContent CreateDicomStreamContent(Stream stream) 130 | { 131 | StreamContent content = new(stream); 132 | content.Headers.ContentType = DicomWebConstants.MediaTypeApplicationDicom; 133 | 134 | return content; 135 | } 136 | 137 | [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "Callers will dispose of the StreamContent")] 138 | private static MultipartContent CreateMultipartDicomStreamContent(IEnumerable streams, DicomWriteOptions options = null) 139 | { 140 | MultipartContent content = new("related"); 141 | content.Headers.ContentType.Parameters.Add(new NameValueHeaderValue("type", $"\"{DicomWebConstants.MediaTypeApplicationDicom.MediaType}\"")); 142 | 143 | foreach (Stream stream in streams) 144 | content.Add(CreateDicomStreamContent(stream)); 145 | 146 | return content; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/DicomWebClient.Update.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System.Collections.Generic; 7 | using System.Net.Http; 8 | using System.Text.Json; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using EnsureThat; 12 | using FellowOakDicom; 13 | using Microsoft.Health.Dicom.Client.Models; 14 | 15 | namespace Microsoft.Health.Dicom.Client; 16 | 17 | public partial class DicomWebClient : IDicomWebClient 18 | { 19 | public async Task> UpdateStudyAsync( 20 | IReadOnlyList studyInstanceUids, 21 | DicomDataset dataset, 22 | string partitionName = default, 23 | CancellationToken cancellationToken = default) 24 | { 25 | EnsureArg.IsNotNull(studyInstanceUids, nameof(studyInstanceUids)); 26 | EnsureArg.IsNotNull(dataset, nameof(dataset)); 27 | 28 | string jsonString = JsonSerializer.Serialize( 29 | new UpdateSpecification(studyInstanceUids, dataset), 30 | JsonSerializerOptions); 31 | 32 | using var request = new HttpRequestMessage(HttpMethod.Post, GenerateUpdateRequestUri(partitionName)); 33 | { 34 | request.Content = new StringContent(jsonString); 35 | request.Content.Headers.ContentType = DicomWebConstants.MediaTypeApplicationJson; 36 | } 37 | 38 | HttpResponseMessage response = await HttpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); 39 | await EnsureSuccessStatusCodeAsync(response).ConfigureAwait(false); 40 | return new DicomWebResponse(response, ValueFactory); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/DicomWebClient.Workitem.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Net.Http; 9 | using System.Text.Json; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | using EnsureThat; 13 | using FellowOakDicom; 14 | 15 | namespace Microsoft.Health.Dicom.Client; 16 | 17 | public partial class DicomWebClient : IDicomWebClient 18 | { 19 | public async Task AddWorkitemAsync(IEnumerable dicomDatasets, string workitemUid, string partitionName = default, CancellationToken cancellationToken = default) 20 | { 21 | EnsureArg.IsNotNull(dicomDatasets, nameof(dicomDatasets)); 22 | EnsureArg.IsNotEmptyOrWhiteSpace(workitemUid, nameof(workitemUid)); 23 | 24 | var uri = GenerateWorkitemAddRequestUri(workitemUid, partitionName); 25 | 26 | return await Request(uri, dicomDatasets, HttpMethod.Post, cancellationToken) 27 | .ConfigureAwait(false); 28 | } 29 | 30 | public async Task CancelWorkitemAsync(IEnumerable dicomDatasets, string workitemUid, string partitionName = default, CancellationToken cancellationToken = default) 31 | { 32 | EnsureArg.IsNotNull(dicomDatasets, nameof(dicomDatasets)); 33 | EnsureArg.IsNotEmptyOrWhiteSpace(workitemUid, nameof(workitemUid)); 34 | 35 | var uri = GenerateWorkitemCancelRequestUri(workitemUid, partitionName); 36 | 37 | return await Request(uri, dicomDatasets, HttpMethod.Post, cancellationToken) 38 | .ConfigureAwait(false); 39 | } 40 | 41 | public async Task> RetrieveWorkitemAsync(string workitemUid, string partitionName = default, CancellationToken cancellationToken = default) 42 | { 43 | EnsureArg.IsNotEmptyOrWhiteSpace(workitemUid, nameof(workitemUid)); 44 | 45 | var requestUri = GenerateWorkitemRetrieveRequestUri(workitemUid, partitionName); 46 | 47 | using var request = new HttpRequestMessage(HttpMethod.Get, requestUri); 48 | 49 | request.Headers.Accept.Add(DicomWebConstants.MediaTypeApplicationDicomJson); 50 | 51 | var response = await HttpClient.SendAsync(request, cancellationToken) 52 | .ConfigureAwait(false); 53 | 54 | await EnsureSuccessStatusCodeAsync(response) 55 | .ConfigureAwait(false); 56 | 57 | var contentValueFactory = new Func>( 58 | content => ValueFactory(content)); 59 | 60 | return new DicomWebResponse(response, contentValueFactory); 61 | } 62 | 63 | public async Task ChangeWorkitemStateAsync( 64 | IEnumerable dicomDatasets, 65 | string workitemUid, 66 | string partitionName = default, 67 | CancellationToken cancellationToken = default) 68 | { 69 | EnsureArg.IsNotNull(dicomDatasets, nameof(dicomDatasets)); 70 | EnsureArg.IsNotEmptyOrWhiteSpace(workitemUid, nameof(workitemUid)); 71 | 72 | var uri = GenerateChangeWorkitemStateRequestUri(workitemUid, partitionName); 73 | 74 | return await Request(uri, dicomDatasets, HttpMethod.Put, cancellationToken) 75 | .ConfigureAwait(false); 76 | } 77 | 78 | public async Task> QueryWorkitemAsync(string queryString, string partitionName = default, CancellationToken cancellationToken = default) 79 | { 80 | EnsureArg.IsNotEmptyOrWhiteSpace(queryString, nameof(queryString)); 81 | 82 | var requestUri = GenerateRequestUri(DicomWebConstants.WorkitemUriString + FormatQueryString(queryString), partitionName); 83 | 84 | using var request = new HttpRequestMessage(HttpMethod.Get, requestUri); 85 | 86 | request.Headers.Accept.Add(DicomWebConstants.MediaTypeApplicationDicomJson); 87 | 88 | var response = await HttpClient.SendAsync(request, cancellationToken) 89 | .ConfigureAwait(false); 90 | 91 | await EnsureSuccessStatusCodeAsync(response) 92 | .ConfigureAwait(false); 93 | 94 | return new DicomWebAsyncEnumerableResponse( 95 | response, 96 | DeserializeAsAsyncEnumerable(response.Content)); 97 | } 98 | 99 | public async Task UpdateWorkitemAsync(IEnumerable dicomDatasets, string workitemUid, string transactionUid = default, string partitionName = default, CancellationToken cancellationToken = default) 100 | { 101 | EnsureArg.IsNotNull(dicomDatasets, nameof(dicomDatasets)); 102 | EnsureArg.IsNotEmptyOrWhiteSpace(workitemUid, nameof(workitemUid)); 103 | 104 | var uri = GenerateWorkitemUpdateRequestUri(workitemUid, transactionUid, partitionName); 105 | 106 | return await Request(uri, dicomDatasets, HttpMethod.Post, cancellationToken).ConfigureAwait(false); 107 | } 108 | 109 | private async Task Request( 110 | Uri uri, 111 | TContent requestContent, 112 | HttpMethod httpMethod, 113 | CancellationToken cancellationToken = default) where TContent : class 114 | { 115 | EnsureArg.IsNotNull(uri, nameof(uri)); 116 | EnsureArg.IsNotNull(requestContent, nameof(requestContent)); 117 | 118 | string jsonString = JsonSerializer.Serialize(requestContent, JsonSerializerOptions); 119 | using var request = new HttpRequestMessage(httpMethod, uri); 120 | { 121 | request.Content = new StringContent(jsonString); 122 | request.Content.Headers.ContentType = DicomWebConstants.MediaTypeApplicationDicomJson; 123 | } 124 | 125 | request.Headers.Accept.Add(DicomWebConstants.MediaTypeApplicationDicomJson); 126 | 127 | var response = await HttpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); 128 | 129 | await EnsureSuccessStatusCodeAsync(response).ConfigureAwait(false); 130 | 131 | return new DicomWebResponse(response); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/DicomWebClient.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Diagnostics.CodeAnalysis; 9 | using System.Globalization; 10 | using System.IO; 11 | using System.Net; 12 | using System.Net.Http; 13 | using System.Net.Http.Headers; 14 | using System.Runtime.CompilerServices; 15 | using System.Text.Json; 16 | using System.Text.Json.Serialization; 17 | using System.Threading; 18 | using System.Threading.Tasks; 19 | using EnsureThat; 20 | using FellowOakDicom; 21 | using FellowOakDicom.Serialization; 22 | using Microsoft.AspNetCore.WebUtilities; 23 | using Microsoft.Health.Dicom.Client.Serialization; 24 | using Microsoft.Net.Http.Headers; 25 | 26 | using MediaTypeHeaderValue = Microsoft.Net.Http.Headers.MediaTypeHeaderValue; 27 | using NameValueHeaderValue = System.Net.Http.Headers.NameValueHeaderValue; 28 | 29 | namespace Microsoft.Health.Dicom.Client; 30 | 31 | [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "Callers are responsible for disposing of return values.")] 32 | public partial class DicomWebClient : IDicomWebClient 33 | { 34 | internal static readonly JsonSerializerOptions JsonSerializerOptions = CreateJsonSerializerOptions(); 35 | 36 | /// 37 | /// New instance of DicomWebClient to talk to the server 38 | /// 39 | /// HttpClient 40 | /// Pin the DicomWebClient to a specific server API version. 41 | public DicomWebClient(HttpClient httpClient, string apiVersion = DicomApiVersions.Latest) 42 | { 43 | EnsureArg.IsNotNull(httpClient, nameof(httpClient)); 44 | 45 | HttpClient = httpClient; 46 | ApiVersion = apiVersion; 47 | GetMemoryStream = () => new MemoryStream(); 48 | } 49 | 50 | public HttpClient HttpClient { get; } 51 | 52 | public string ApiVersion { get; } 53 | 54 | /// 55 | /// Func used to obtain a . The default value returns a new memory stream. 56 | /// 57 | /// 58 | /// This can be used in conjunction with a memory stream pool. 59 | /// 60 | public Func GetMemoryStream { get; set; } 61 | 62 | private static async Task ValueFactory(HttpContent content) 63 | { 64 | string contentText = await content.ReadAsStringAsync().ConfigureAwait(false); 65 | return JsonSerializer.Deserialize(contentText, JsonSerializerOptions); 66 | } 67 | 68 | private static string FormatQueryString(string queryString) 69 | => string.IsNullOrWhiteSpace(queryString) ? string.Empty : "?" + queryString; 70 | 71 | private static string CreateAcceptHeader(MediaTypeWithQualityHeaderValue mediaTypeHeader, string dicomTransferSyntax) 72 | { 73 | string transferSyntaxHeader = dicomTransferSyntax == null ? string.Empty : $";{DicomWebConstants.TransferSyntaxHeaderName}=\"{dicomTransferSyntax}\""; 74 | 75 | return $"{mediaTypeHeader}{transferSyntaxHeader}"; 76 | } 77 | 78 | private static MediaTypeWithQualityHeaderValue CreateMultipartMediaTypeHeader(string contentType) 79 | { 80 | var multipartHeader = new MediaTypeWithQualityHeaderValue(DicomWebConstants.MultipartRelatedMediaType); 81 | var contentHeader = new NameValueHeaderValue("type", "\"" + contentType + "\""); 82 | 83 | multipartHeader.Parameters.Add(contentHeader); 84 | return multipartHeader; 85 | } 86 | 87 | private Uri GenerateRequestUri(string relativePath, string partitionName = default) 88 | { 89 | var uriString = "/" + ApiVersion; 90 | 91 | if (!string.IsNullOrEmpty(partitionName)) 92 | { 93 | uriString += string.Format(CultureInfo.InvariantCulture, DicomWebConstants.BasePartitionUriFormat, partitionName); 94 | } 95 | 96 | uriString += relativePath; 97 | 98 | return new Uri(uriString, UriKind.Relative); 99 | } 100 | 101 | private Uri GenerateStoreRequestUri(string partitionName = default, string studyInstanceUid = default) 102 | { 103 | if (string.IsNullOrEmpty(studyInstanceUid)) 104 | { 105 | return GenerateRequestUri(DicomWebConstants.StudiesUriString, partitionName); 106 | } 107 | 108 | return GenerateRequestUri(string.Format(CultureInfo.InvariantCulture, DicomWebConstants.BaseStudyUriFormat, studyInstanceUid), partitionName); 109 | } 110 | 111 | private Uri GenerateWorkitemAddRequestUri(string workitemUid, string partitionName = default) 112 | { 113 | return GenerateRequestUri(string.Format(CultureInfo.InvariantCulture, DicomWebConstants.AddWorkitemUriFormat, workitemUid), partitionName); 114 | } 115 | 116 | private Uri GenerateWorkitemCancelRequestUri(string workitemUid, string partitionName = default) 117 | { 118 | return GenerateRequestUri(string.Format(CultureInfo.InvariantCulture, DicomWebConstants.CancelWorkitemUriFormat, workitemUid), partitionName); 119 | } 120 | 121 | private Uri GenerateWorkitemRetrieveRequestUri(string workitemUid, string partitionName = default) 122 | { 123 | return GenerateRequestUri(string.Format(CultureInfo.InvariantCulture, DicomWebConstants.BaseWorkitemUriFormat, workitemUid), partitionName); 124 | } 125 | 126 | private Uri GenerateChangeWorkitemStateRequestUri(string workitemUid, string partitionName = default) 127 | { 128 | return GenerateRequestUri(string.Format(CultureInfo.InvariantCulture, DicomWebConstants.ChangeWorkitemStateUriFormat, workitemUid), partitionName); 129 | } 130 | 131 | private Uri GenerateWorkitemUpdateRequestUri(string workitemUid, string transactionUid, string partitionName = default) 132 | { 133 | return GenerateRequestUri(string.Format(CultureInfo.InvariantCulture, DicomWebConstants.UpdateWorkitemUriFormat, workitemUid, transactionUid), partitionName); 134 | } 135 | 136 | private Uri GenerateUpdateRequestUri(string partitionName = default) 137 | { 138 | return GenerateRequestUri(DicomWebConstants.UpdateAttributeUriString, partitionName); 139 | } 140 | 141 | private async IAsyncEnumerable ReadMultipartResponseAsStreamsAsync(HttpContent httpContent, [EnumeratorCancellation] CancellationToken cancellationToken = default) 142 | { 143 | EnsureArg.IsNotNull(httpContent, nameof(httpContent)); 144 | 145 | using Stream stream = await httpContent 146 | #if NETSTANDARD2_0 147 | .ReadAsStreamAsync() 148 | #else 149 | .ReadAsStreamAsync(cancellationToken) 150 | #endif 151 | .ConfigureAwait(false); 152 | stream.Seek(0, SeekOrigin.Begin); 153 | MultipartSection part; 154 | 155 | var media = MediaTypeHeaderValue.Parse(httpContent.Headers.ContentType.ToString()); 156 | var multipartReader = new MultipartReader(HeaderUtilities.RemoveQuotes(media.Boundary).Value, stream, 100); 157 | 158 | while ((part = await multipartReader.ReadNextSectionAsync(cancellationToken).ConfigureAwait(false)) != null) 159 | { 160 | MemoryStream memoryStream = GetMemoryStream(); 161 | await part.Body 162 | #if NETSTANDARD2_0 163 | .CopyToAsync(memoryStream) 164 | #else 165 | .CopyToAsync(memoryStream, cancellationToken) 166 | #endif 167 | .ConfigureAwait(false); 168 | 169 | memoryStream.Seek(0, SeekOrigin.Begin); 170 | 171 | yield return memoryStream; 172 | } 173 | } 174 | 175 | private static async Task EnsureSuccessStatusCodeAsync( 176 | HttpResponseMessage response, 177 | Func additionalFailureInspector = null) 178 | { 179 | if (!response.IsSuccessStatusCode) 180 | { 181 | HttpStatusCode statusCode = response.StatusCode; 182 | HttpResponseHeaders responseHeaders = response.Headers; 183 | HttpContentHeaders contentHeaders = response.Content?.Headers; 184 | string responseBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false); 185 | 186 | Exception exception = null; 187 | 188 | try 189 | { 190 | bool handled = additionalFailureInspector?.Invoke(statusCode, responseHeaders, contentHeaders, responseBody) ?? false; 191 | 192 | if (!handled) 193 | { 194 | throw new DicomWebException(statusCode, responseHeaders, contentHeaders, responseBody); 195 | } 196 | } 197 | catch (Exception ex) 198 | { 199 | exception = ex; 200 | throw; 201 | } 202 | finally 203 | { 204 | // If we are throwing exception, then we can close the response because we have already read the body. 205 | if (exception != null) 206 | { 207 | response.Dispose(); 208 | } 209 | } 210 | } 211 | } 212 | 213 | private async IAsyncEnumerable ReadMultipartResponseAsDicomFileAsync(HttpContent content, [EnumeratorCancellation] CancellationToken cancellationToken = default) 214 | { 215 | await foreach (Stream stream in ReadMultipartResponseAsStreamsAsync(content, cancellationToken).ConfigureAwait(false)) 216 | { 217 | yield return await DicomFile.OpenAsync(stream).ConfigureAwait(false); 218 | } 219 | } 220 | 221 | private static async IAsyncEnumerable DeserializeAsAsyncEnumerable(HttpContent content) 222 | { 223 | string contentText = await content.ReadAsStringAsync().ConfigureAwait(false); 224 | 225 | if (string.IsNullOrEmpty(contentText)) 226 | { 227 | yield break; 228 | } 229 | 230 | foreach (T item in JsonSerializer.Deserialize>(contentText, JsonSerializerOptions)) 231 | { 232 | yield return item; 233 | } 234 | } 235 | 236 | private static JsonSerializerOptions CreateJsonSerializerOptions() 237 | { 238 | var options = new JsonSerializerOptions 239 | { 240 | AllowTrailingCommas = true, 241 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, 242 | DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, 243 | Encoder = null, 244 | IgnoreReadOnlyFields = false, 245 | IgnoreReadOnlyProperties = false, 246 | IncludeFields = false, 247 | MaxDepth = 0, // 0 indicates the max depth of 64 248 | NumberHandling = JsonNumberHandling.Strict, 249 | PropertyNameCaseInsensitive = true, 250 | PropertyNamingPolicy = JsonNamingPolicy.CamelCase, 251 | ReadCommentHandling = JsonCommentHandling.Skip, 252 | WriteIndented = false, 253 | }; 254 | 255 | options.Converters.Add(new DicomIdentifierJsonConverter()); 256 | options.Converters.Add(new DicomJsonConverter(writeTagsAsKeywords: true, autoValidate: false, numberSerializationMode: NumberSerializationMode.PreferablyAsNumber)); 257 | options.Converters.Add(new ExportDataOptionsJsonConverter()); 258 | options.Converters.Add(new JsonStringEnumConverter()); 259 | options.Converters.Add(new OperationStateConverter()); 260 | 261 | return options; 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/DicomWebConstants.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System.Net.Http.Headers; 7 | 8 | namespace Microsoft.Health.Dicom.Client; 9 | 10 | public static class DicomWebConstants 11 | { 12 | public const string BasePartitionUriFormat = "/partitions/{0}"; 13 | public const string BaseStudyUriFormat = "/studies/{0}"; 14 | public const string BaseWorkitemUriFormat = "/workitems/{0}"; 15 | public const string AddWorkitemUriFormat = "/workitems?{0}"; 16 | public const string BaseRetrieveStudyMetadataUriFormat = BaseStudyUriFormat + "/metadata"; 17 | public const string BaseSeriesUriFormat = BaseStudyUriFormat + "/series/{1}"; 18 | public const string BaseRetrieveSeriesMetadataUriFormat = BaseSeriesUriFormat + "/metadata"; 19 | public const string BaseInstanceUriFormat = BaseSeriesUriFormat + "/instances/{2}"; 20 | public const string BaseRetrieveInstanceRenderedUriFormat = BaseInstanceUriFormat + "/rendered?quality={3}"; 21 | public const string BaseRetrieveInstanceThumbnailUriFormat = BaseInstanceUriFormat + "/thumbnail"; 22 | public const string BaseRetrieveInstanceMetadataUriFormat = BaseInstanceUriFormat + "/metadata"; 23 | public const string BaseRetrieveFramesUriFormat = BaseInstanceUriFormat + "/frames/{3}"; 24 | public const string BaseRetrieveFrameRenderedUriFormat = BaseRetrieveFramesUriFormat + "/rendered?quality={4}"; 25 | public const string BaseRetrieveFramesThumbnailUriFormat = BaseRetrieveFramesUriFormat + "/thumbnail"; 26 | public const string PartitionsUriString = "/partitions"; 27 | public const string StudiesUriString = "/studies"; 28 | public const string SeriesUriString = "/series"; 29 | public const string InstancesUriString = "/instances"; 30 | public const string QueryStudyInstanceUriFormat = BaseStudyUriFormat + InstancesUriString; 31 | public const string QueryStudySeriesUriFormat = BaseStudyUriFormat + SeriesUriString; 32 | public const string QueryStudySeriesInstancesUriFormat = BaseStudyUriFormat + SeriesUriString + "/{1}" + InstancesUriString; 33 | public const string QuerySeriesInstanceUriFormat = SeriesUriString + "/{0}" + InstancesUriString; 34 | public const string BaseExtendedQueryTagUri = "/extendedquerytags"; 35 | public const string BaseOperationUri = "/operations"; 36 | public const string BaseErrorsUriFormat = BaseExtendedQueryTagUri + "/{0}/errors"; 37 | public const string WorkitemUriString = "/workitems"; 38 | public const string CancelWorkitemUriFormat = $"{BaseWorkitemUriFormat}/cancelrequest"; 39 | public const string ExportUriString = "/export"; 40 | public const string ChangeWorkitemStateUriFormat = $"{BaseWorkitemUriFormat}/state"; 41 | public const string UpdateWorkitemUriFormat = "/workitems/{0}?{1}"; 42 | public const string UpdateAttributeUriString = StudiesUriString + "/$bulkupdate"; 43 | 44 | public const string LimitParameter = "limit"; 45 | public const string OffsetParameter = "offset"; 46 | 47 | public const string OriginalDicomTransferSyntax = "*"; 48 | 49 | public const string TransferSyntaxHeaderName = "transfer-syntax"; 50 | 51 | public const string RequestOriginalVersion = "msdicom-request-original"; 52 | 53 | public const string ApplicationDicomMediaType = "application/dicom"; 54 | public const string ApplicationDicomJsonMediaType = "application/dicom+json"; 55 | public const string ApplicationOctetStreamMediaType = "application/octet-stream"; 56 | public const string ApplicationJsonMediaType = "application/json"; 57 | public const string ImageJpegMediaType = "image/jpeg"; 58 | public const string ImagePngMediaType = "image/png"; 59 | public const string MultipartRelatedMediaType = "multipart/related"; 60 | public const string ImageJpeg2000MediaType = "image/jp2"; 61 | public const string ImageDicomRleMediaType = "image/dicom-rle"; 62 | public const string ImageJpegLsMediaType = "image/jls"; 63 | public const string ImageJpeg2000Part2MediaType = "image/jpx"; 64 | public const string VideoMpeg2MediaType = "video/mpeg2"; 65 | public const string VideoMp4MediaType = "video/mp4"; 66 | 67 | public static readonly MediaTypeWithQualityHeaderValue MediaTypeApplicationDicom = new MediaTypeWithQualityHeaderValue(ApplicationDicomMediaType); 68 | public static readonly MediaTypeWithQualityHeaderValue MediaTypeApplicationOctetStream = new MediaTypeWithQualityHeaderValue(ApplicationOctetStreamMediaType); 69 | public static readonly MediaTypeWithQualityHeaderValue MediaTypeApplicationDicomJson = new MediaTypeWithQualityHeaderValue(ApplicationDicomJsonMediaType); 70 | public static readonly MediaTypeWithQualityHeaderValue MediaTypeApplicationJson = new MediaTypeWithQualityHeaderValue(ApplicationJsonMediaType); 71 | } 72 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/DicomWebException.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | using System.Net; 8 | using System.Net.Http.Headers; 9 | using FellowOakDicom; 10 | 11 | namespace Microsoft.Health.Dicom.Client; 12 | 13 | public class DicomWebException : Exception 14 | { 15 | public DicomWebException( 16 | HttpStatusCode statusCode, 17 | HttpResponseHeaders responseHeaders, 18 | HttpContentHeaders contentHeaders, 19 | string responseMessage) 20 | : this(statusCode, responseHeaders, contentHeaders) 21 | { 22 | ResponseMessage = responseMessage; 23 | } 24 | 25 | public DicomWebException( 26 | HttpStatusCode statusCode, 27 | HttpResponseHeaders responseHeaders, 28 | HttpContentHeaders contentHeaders, 29 | DicomDataset responseDataset) 30 | : this(statusCode, responseHeaders, contentHeaders) 31 | { 32 | ResponseDataset = responseDataset; 33 | } 34 | 35 | private DicomWebException( 36 | HttpStatusCode statusCode, 37 | HttpResponseHeaders responseHeaders, 38 | HttpContentHeaders contentHeaders) 39 | { 40 | StatusCode = statusCode; 41 | ResponseHeaders = responseHeaders; 42 | ContentHeaders = contentHeaders; 43 | } 44 | 45 | public HttpStatusCode StatusCode { get; } 46 | 47 | public HttpResponseHeaders ResponseHeaders { get; } 48 | 49 | public HttpContentHeaders ContentHeaders { get; } 50 | 51 | public string ResponseMessage { get; } 52 | 53 | public DicomDataset ResponseDataset { get; } 54 | 55 | public override string Message 56 | { 57 | get 58 | { 59 | if (!string.IsNullOrEmpty(ResponseMessage)) 60 | { 61 | return $"{StatusCode}: {ResponseMessage}"; 62 | } 63 | 64 | return StatusCode.ToString(); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/DicomWebResponse.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | using System.Net; 8 | using System.Net.Http; 9 | using System.Net.Http.Headers; 10 | using EnsureThat; 11 | 12 | namespace Microsoft.Health.Dicom.Client; 13 | 14 | public class DicomWebResponse : IDisposable 15 | { 16 | private readonly HttpResponseMessage _response; 17 | private bool _disposed; 18 | 19 | public DicomWebResponse(HttpResponseMessage response) 20 | { 21 | EnsureArg.IsNotNull(response, nameof(response)); 22 | 23 | _response = response; 24 | } 25 | 26 | public HttpStatusCode StatusCode => _response.StatusCode; 27 | 28 | public bool IsSuccessStatusCode => _response.IsSuccessStatusCode; 29 | 30 | public HttpResponseHeaders ResponseHeaders => _response.Headers; 31 | 32 | public HttpContentHeaders ContentHeaders => _response.Content?.Headers; 33 | 34 | public HttpContent Content => _response.Content; 35 | 36 | protected virtual void Dispose(bool disposing) 37 | { 38 | if (!_disposed) 39 | { 40 | if (disposing) 41 | { 42 | _response.Dispose(); 43 | } 44 | 45 | _disposed = true; 46 | } 47 | } 48 | 49 | public void Dispose() 50 | { 51 | Dispose(disposing: true); 52 | GC.SuppressFinalize(this); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/DicomWebResponse{T}.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | using System.Net.Http; 8 | using System.Threading.Tasks; 9 | using EnsureThat; 10 | 11 | namespace Microsoft.Health.Dicom.Client; 12 | 13 | public class DicomWebResponse : DicomWebResponse 14 | { 15 | private readonly Func> _valueFactory; 16 | 17 | public DicomWebResponse(HttpResponseMessage response, Func> valueFactory) 18 | : base(response) 19 | { 20 | EnsureArg.IsNotNull(valueFactory, nameof(valueFactory)); 21 | 22 | _valueFactory = valueFactory; 23 | } 24 | 25 | public Task GetValueAsync() 26 | => _valueFactory(Content); 27 | } 28 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/Http/DicomContent.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System.Collections.Generic; 7 | using System.Diagnostics.CodeAnalysis; 8 | using System.IO; 9 | using System.Net; 10 | using System.Net.Http; 11 | using System.Net.Http.Headers; 12 | using System.Threading.Tasks; 13 | using EnsureThat; 14 | using FellowOakDicom; 15 | using FellowOakDicom.IO.Writer; 16 | 17 | namespace Microsoft.Health.Dicom.Client.Http; 18 | 19 | public sealed class DicomContent : HttpContent 20 | { 21 | private readonly DicomFile _file; 22 | private readonly DicomWriteOptions _options; 23 | 24 | public DicomContent(DicomFile file, DicomWriteOptions options = null) 25 | { 26 | _file = EnsureArg.IsNotNull(file, nameof(file)); 27 | _options = options; 28 | 29 | Headers.ContentType = DicomWebConstants.MediaTypeApplicationDicom; 30 | } 31 | 32 | protected override Task SerializeToStreamAsync(Stream stream, TransportContext context) 33 | => _file.SaveAsync(stream, _options); 34 | 35 | protected override bool TryComputeLength(out long length) 36 | { 37 | length = default; 38 | return false; 39 | } 40 | 41 | [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "Callers will dispose of the StreamContent")] 42 | public static MultipartContent CreateMultipart(IEnumerable files, DicomWriteOptions options = null) 43 | { 44 | EnsureArg.IsNotNull(files, nameof(files)); 45 | 46 | MultipartContent content = new("related"); 47 | content.Headers.ContentType.Parameters.Add(new NameValueHeaderValue("type", $"\"{DicomWebConstants.MediaTypeApplicationDicom.MediaType}\"")); 48 | 49 | foreach (DicomFile file in files) 50 | content.Add(new DicomContent(file, options)); 51 | 52 | return content; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/IDicomWebClient.ChangeFeed.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using Microsoft.Health.Dicom.Client.Models; 9 | 10 | namespace Microsoft.Health.Dicom.Client; 11 | 12 | public partial interface IDicomWebClient 13 | { 14 | Task> GetChangeFeed(string queryString = "", CancellationToken cancellationToken = default); 15 | Task> GetChangeFeedLatest(string queryString = "", CancellationToken cancellationToken = default); 16 | } 17 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/IDicomWebClient.Delete.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace Microsoft.Health.Dicom.Client; 10 | 11 | public partial interface IDicomWebClient 12 | { 13 | Task DeleteInstanceAsync(string studyInstanceUid, string seriesInstanceUid, string sopInstanceUid, string partitionName = default, CancellationToken cancellationToken = default); 14 | Task DeleteSeriesAsync(string studyInstanceUid, string seriesInstanceUid, string partitionName = default, CancellationToken cancellationToken = default); 15 | Task DeleteStudyAsync(string studyInstanceUid, string partitionName = default, CancellationToken cancellationToken = default); 16 | } 17 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/IDicomWebClient.Export.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using Microsoft.Health.Dicom.Client.Models; 9 | 10 | namespace Microsoft.Health.Dicom.Client; 11 | 12 | public partial interface IDicomWebClient 13 | { 14 | Task> StartExportAsync( 15 | ExportDataOptions source, 16 | ExportDataOptions destination, 17 | string partitionName = default, 18 | CancellationToken cancellationToken = default); 19 | } 20 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/IDicomWebClient.ExtendedQueryTag.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System.Collections.Generic; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using Microsoft.Health.Dicom.Client.Models; 10 | 11 | namespace Microsoft.Health.Dicom.Client; 12 | 13 | public partial interface IDicomWebClient 14 | { 15 | Task> AddExtendedQueryTagAsync(IEnumerable tagEntries, CancellationToken cancellationToken = default); 16 | 17 | Task DeleteExtendedQueryTagAsync(string tagPath, CancellationToken cancellationToken = default); 18 | 19 | Task> GetExtendedQueryTagAsync(string tagPath, CancellationToken cancellationToken = default); 20 | 21 | Task> UpdateExtendedQueryTagAsync(string tagPath, UpdateExtendedQueryTagEntry newValue, CancellationToken cancellationToken = default); 22 | 23 | Task>> GetExtendedQueryTagsAsync(int limit = 100, long offset = 0, CancellationToken cancellationToken = default); 24 | 25 | Task>> GetExtendedQueryTagErrorsAsync(string tagPath, int limit = 100, long offset = 0, CancellationToken cancellationToken = default); 26 | } 27 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/IDicomWebClient.Operation.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using Microsoft.Health.Dicom.Client.Models; 10 | using Microsoft.Health.Operations; 11 | 12 | namespace Microsoft.Health.Dicom.Client; 13 | 14 | public partial interface IDicomWebClient 15 | { 16 | Task>> GetOperationStateAsync(Guid operationId, CancellationToken cancellationToken = default); 17 | } 18 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/IDicomWebClient.Partition.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System.Collections.Generic; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using Microsoft.Health.Dicom.Client.Models; 10 | 11 | namespace Microsoft.Health.Dicom.Client; 12 | 13 | public partial interface IDicomWebClient 14 | { 15 | Task>> GetPartitionsAsync(CancellationToken cancellationToken = default); 16 | } 17 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/IDicomWebClient.Reference.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using Microsoft.Health.Dicom.Client.Models; 9 | 10 | namespace Microsoft.Health.Dicom.Client; 11 | 12 | public partial interface IDicomWebClient 13 | { 14 | Task> ResolveReferenceAsync(IResourceReference resourceReference, CancellationToken cancellationToken = default); 15 | } 16 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/IDicomWebClient.Retrieve.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System.IO; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using FellowOakDicom; 10 | 11 | namespace Microsoft.Health.Dicom.Client; 12 | 13 | public partial interface IDicomWebClient 14 | { 15 | Task> RetrieveSingleFrameAsync(string studyInstanceUid, string seriesInstanceUid, string sopInstanceUid, int frame, string partitionName = default, CancellationToken cancellationToken = default); 16 | Task> RetrieveFramesAsync(string studyInstanceUid, string seriesInstanceUid, string sopInstanceUid, int[] frames = default, string mediaType = DicomWebConstants.ApplicationOctetStreamMediaType, string dicomTransferSyntax = DicomWebConstants.OriginalDicomTransferSyntax, string partitionName = default, CancellationToken cancellationToken = default); 17 | Task> RetrieveRenderedFrameAsync(string studyInstanceUid, string seriesInstanceUid, string sopInstanceUid, int frame, int quality = 100, string mediaType = DicomWebConstants.ImageJpegMediaType, string partitionName = default, CancellationToken cancellationToken = default); 18 | Task> RetrieveInstanceAsync(string studyInstanceUid, string seriesInstanceUid, string sopInstanceUid, string dicomTransferSyntax = DicomWebConstants.OriginalDicomTransferSyntax, string partitionName = default, bool requestOriginalVersion = default, CancellationToken cancellationToken = default); 19 | Task> RetrieveInstanceStreamAsync(string studyInstanceUid, string seriesInstanceUid, string sopInstanceUid, string dicomTransferSyntax = DicomWebConstants.OriginalDicomTransferSyntax, string partitionName = default, bool requestOriginalVersion = default, CancellationToken cancellationToken = default); 20 | Task> RetrieveRenderedInstanceAsync(string studyInstanceUid, string seriesInstanceUid, string sopInstanceUid, int quality = 100, string mediaType = DicomWebConstants.ImageJpegMediaType, string partitionName = default, CancellationToken cancellationToken = default); 21 | Task> RetrieveInstanceMetadataAsync(string studyInstanceUid, string seriesInstanceUid, string sopInstanceUid, string ifNoneMatch = default, string partitionName = default, bool requestOriginalVersion = default, CancellationToken cancellationToken = default); 22 | Task> RetrieveSeriesAsync(string studyInstanceUid, string seriesInstanceUid, string dicomTransferSyntax = DicomWebConstants.OriginalDicomTransferSyntax, string partitionName = default, bool requestOriginalVersion = default, CancellationToken cancellationToken = default); 23 | Task> RetrieveSeriesMetadataAsync(string studyInstanceUid, string seriesInstanceUid, string ifNoneMatch = default, string partitionName = default, bool requestOriginalVersion = default, CancellationToken cancellationToken = default); 24 | Task> RetrieveStudyAsync(string studyInstanceUid, string dicomTransferSyntax = DicomWebConstants.OriginalDicomTransferSyntax, string partitionName = default, bool requestOriginalVersion = default, CancellationToken cancellationToken = default); 25 | Task> RetrieveStudyMetadataAsync(string studyInstanceUid, string ifNoneMatch = default, string partitionName = default, bool requestOriginalVersion = default, CancellationToken cancellationToken = default); 26 | } 27 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/IDicomWebClient.Search.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using FellowOakDicom; 9 | 10 | namespace Microsoft.Health.Dicom.Client; 11 | 12 | public partial interface IDicomWebClient 13 | { 14 | Task> QueryInstancesAsync(string queryString, string partitionName = default, CancellationToken cancellationToken = default); 15 | Task> QuerySeriesAsync(string queryString, string partitionName = default, CancellationToken cancellationToken = default); 16 | Task> QuerySeriesInstanceAsync(string seriesInstanceUid, string queryString, string partitionName = default, CancellationToken cancellationToken = default); 17 | Task> QueryStudyAsync(string queryString, string partitionName = default, CancellationToken cancellationToken = default); 18 | Task> QueryStudyInstanceAsync(string studyInstanceUid, string queryString, string partitionName = default, CancellationToken cancellationToken = default); 19 | Task> QueryStudySeriesAsync(string studyInstanceUid, string queryString, string partitionName = default, CancellationToken cancellationToken = default); 20 | Task> QueryStudySeriesInstanceAsync(string studyInstanceUid, string seriesInstanceUid, string queryString, string partitionName = default, CancellationToken cancellationToken = default); 21 | } 22 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/IDicomWebClient.Store.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System.Collections.Generic; 7 | using System.IO; 8 | using System.Net.Http; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using FellowOakDicom; 12 | 13 | namespace Microsoft.Health.Dicom.Client; 14 | 15 | public partial interface IDicomWebClient 16 | { 17 | Task> StoreAsync(DicomFile dicomFile, string studyInstanceUid = default, string partitionName = default, CancellationToken cancellationToken = default); 18 | Task> StoreAsync(HttpContent content, string partitionName = default, CancellationToken cancellationToken = default); 19 | Task> StoreAsync(IEnumerable dicomFiles, string studyInstanceUid = default, string partitionName = default, CancellationToken cancellationToken = default); 20 | Task> StoreAsync(IEnumerable streams, string studyInstanceUid = default, string partitionName = default, CancellationToken cancellationToken = default); 21 | Task> StoreAsync(Stream stream, string studyInstanceUid = default, string partitionName = default, CancellationToken cancellationToken = default); 22 | } 23 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/IDicomWebClient.Update.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System.Collections.Generic; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using FellowOakDicom; 10 | using Microsoft.Health.Dicom.Client.Models; 11 | 12 | namespace Microsoft.Health.Dicom.Client; 13 | 14 | public partial interface IDicomWebClient 15 | { 16 | Task> UpdateStudyAsync(IReadOnlyList studyInstanceUids, DicomDataset dataset, string partitionName = default, CancellationToken cancellationToken = default); 17 | } 18 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/IDicomWebClient.Workitem.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System.Collections.Generic; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using FellowOakDicom; 10 | 11 | namespace Microsoft.Health.Dicom.Client; 12 | 13 | public partial interface IDicomWebClient 14 | { 15 | Task AddWorkitemAsync(IEnumerable dicomDatasets, string workitemUid = default, string partitionName = default, CancellationToken cancellationToken = default); 16 | 17 | Task CancelWorkitemAsync(IEnumerable dicomDatasets, string workitemUid, string partitionName = default, CancellationToken cancellationToken = default); 18 | 19 | Task> QueryWorkitemAsync(string queryString, string partitionName = default, CancellationToken cancellationToken = default); 20 | 21 | Task> RetrieveWorkitemAsync(string workitemUid, string partitionName = default, CancellationToken cancellationToken = default); 22 | 23 | Task ChangeWorkitemStateAsync(IEnumerable dicomDatasets, string workitemUid, string partitionName = default, CancellationToken cancellationToken = default); 24 | 25 | Task UpdateWorkitemAsync(IEnumerable dicomDatasets, string workitemUid, string transactionUid = default, string partitionName = default, CancellationToken cancellationToken = default); 26 | } 27 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/IDicomWebClient.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | using System.IO; 8 | using System.Net.Http; 9 | 10 | namespace Microsoft.Health.Dicom.Client; 11 | 12 | public partial interface IDicomWebClient 13 | { 14 | Func GetMemoryStream { get; set; } 15 | HttpClient HttpClient { get; } 16 | string ApiVersion { get; } 17 | } 18 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/Microsoft.Health.Dicom.Client.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Defines a RESTful client for interacting with DICOMweb APIs. 5 | net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | DicomWebClient.cs 20 | 21 | 22 | IDicomWebClient.cs 23 | 24 | 25 | 26 | 27 | 28 | True 29 | True 30 | DicomClientResource.resx 31 | 32 | 33 | 34 | 35 | 36 | ResXFileCodeGenerator 37 | DicomClientResource.Designer.cs 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/Models/AddExtendedQueryTagEntry.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | namespace Microsoft.Health.Dicom.Client.Models; 7 | 8 | /// 9 | /// External representation of a extended query tag entry during add. 10 | /// 11 | public class AddExtendedQueryTagEntry 12 | { 13 | /// 14 | /// Path of this tag. Normally it's composed of groupid and elementid. 15 | /// E.g: 00100020 is path of patient id. 16 | /// 17 | public string Path { get; set; } 18 | 19 | /// 20 | /// VR of this tag. 21 | /// 22 | public string VR { get; set; } 23 | 24 | /// 25 | /// Level of this tag. Could be Study, Series or Instance. 26 | /// 27 | public QueryTagLevel Level { get; set; } 28 | 29 | /// 30 | /// Identification code of private tag implementer. 31 | /// 32 | public string PrivateCreator { get; set; } 33 | } 34 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/Models/ChangeFeedAction.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | namespace Microsoft.Health.Dicom.Client.Models; 7 | 8 | public enum ChangeFeedAction 9 | { 10 | Create = 0, 11 | Delete = 1, 12 | Update = 2, 13 | } 14 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/Models/ChangeFeedEntry.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | using System.Text.Json.Serialization; 8 | using EnsureThat; 9 | using FellowOakDicom; 10 | 11 | namespace Microsoft.Health.Dicom.Client.Models; 12 | 13 | /// 14 | /// Represents each change feed entry of a change has retrieved from the store 15 | /// 16 | public class ChangeFeedEntry 17 | { 18 | public ChangeFeedEntry( 19 | long sequence, 20 | DateTimeOffset timestamp, 21 | ChangeFeedAction action, 22 | string studyInstanceUid, 23 | string seriesInstanceUid, 24 | string sopInstanceUid, 25 | ChangeFeedState state, 26 | string partitionName = default, 27 | DicomDataset metadata = null, 28 | string filePath = null) 29 | { 30 | EnsureArg.IsNotNull(studyInstanceUid); 31 | EnsureArg.IsNotNull(seriesInstanceUid); 32 | EnsureArg.IsNotNull(sopInstanceUid); 33 | 34 | Sequence = sequence; 35 | StudyInstanceUid = studyInstanceUid; 36 | SeriesInstanceUid = seriesInstanceUid; 37 | SopInstanceUid = sopInstanceUid; 38 | Action = action; 39 | Timestamp = timestamp; 40 | State = state; 41 | PartitionName = partitionName; 42 | Metadata = metadata; 43 | FilePath = filePath; 44 | } 45 | 46 | public long Sequence { get; } 47 | 48 | public string PartitionName { get; } 49 | 50 | public string StudyInstanceUid { get; } 51 | 52 | public string SeriesInstanceUid { get; } 53 | 54 | public string SopInstanceUid { get; } 55 | 56 | public string FilePath { get; } 57 | 58 | [JsonConverter(typeof(JsonStringEnumConverter))] 59 | public ChangeFeedAction Action { get; } 60 | 61 | public DateTimeOffset Timestamp { get; } 62 | 63 | [JsonConverter(typeof(JsonStringEnumConverter))] 64 | public ChangeFeedState State { get; } 65 | 66 | public DicomDataset Metadata { get; } 67 | } 68 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/Models/ChangeFeedState.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | namespace Microsoft.Health.Dicom.Client.Models; 7 | 8 | public enum ChangeFeedState 9 | { 10 | Current, 11 | Replaced, 12 | Deleted, 13 | } 14 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/Models/DicomIdentifier.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | using EnsureThat; 8 | using FellowOakDicom; 9 | 10 | namespace Microsoft.Health.Dicom.Client.Models; 11 | 12 | /// 13 | /// Represents an identifier for a DICOM study, series, or SOP instance. 14 | /// 15 | public sealed class DicomIdentifier : IEquatable 16 | { 17 | /// 18 | /// Gets the unique identifier for the study. 19 | /// 20 | /// The value of the study instance UID attribute (0020,000D). 21 | public string StudyInstanceUid { get; } 22 | 23 | /// 24 | /// Gets the unique identifier for the series. 25 | /// 26 | /// 27 | /// May be for studies. 28 | /// 29 | /// The value of the series instance UID attribute (0020,000E). 30 | public string SeriesInstanceUid { get; } 31 | 32 | /// 33 | /// Gets the unique identifier for the SOP instance. 34 | /// 35 | /// 36 | /// May be for studies or series. 37 | /// 38 | /// The value of the SOP instance UID attribute (0008,0018). 39 | public string SopInstanceUid { get; } 40 | 41 | /// 42 | /// Initializes a new instance of the structure based on the given identifiers. 43 | /// 44 | /// The unique identifier for the study. 45 | /// The optional unique identifier for the series. 46 | /// The optional unique identifier for the SOP instance. 47 | /// 48 | /// is empty or consists entirely of white space characters. 49 | /// 50 | /// 51 | /// is or white space 52 | /// -or- 53 | /// 54 | /// is or white space, but 55 | /// has a value. 56 | /// 57 | /// 58 | public DicomIdentifier(string studyInstanceUid, string seriesInstanceUid, string sopInstanceUid) 59 | { 60 | StudyInstanceUid = EnsureArg.IsNotNullOrWhiteSpace(studyInstanceUid, nameof(studyInstanceUid)); 61 | if (string.IsNullOrWhiteSpace(seriesInstanceUid) && !string.IsNullOrWhiteSpace(sopInstanceUid)) 62 | throw new ArgumentNullException(nameof(seriesInstanceUid)); 63 | 64 | SeriesInstanceUid = seriesInstanceUid; 65 | SopInstanceUid = sopInstanceUid; 66 | } 67 | 68 | /// 69 | /// Returns a value indicating whether this instance is equal to a specified object. 70 | /// 71 | /// The object to compare to this instance. 72 | /// 73 | /// if is an instance of and 74 | /// equals the value of this instance; otherwise, . 75 | /// 76 | public override bool Equals(object obj) 77 | => obj is DicomIdentifier other && Equals(other); 78 | 79 | /// 80 | /// Returns a value indicating whether the value of this instance is equal to the value of the 81 | /// specified instance. 82 | /// 83 | /// The object to compare to this instance. 84 | /// 85 | /// if the parameter equals the 86 | /// value of this instance; otherwise, . 87 | /// 88 | public bool Equals(DicomIdentifier other) 89 | => other != null 90 | && string.Equals(StudyInstanceUid, other.StudyInstanceUid, StringComparison.Ordinal) 91 | && string.Equals(SeriesInstanceUid, other.SeriesInstanceUid, StringComparison.Ordinal) 92 | && string.Equals(SopInstanceUid, other.SopInstanceUid, StringComparison.Ordinal); 93 | 94 | /// 95 | /// Returns the hash code for this instance. 96 | /// 97 | /// A 32-bit signed integer hash code. 98 | public override int GetHashCode() 99 | => HashCode.Combine(StudyInstanceUid, SeriesInstanceUid, SopInstanceUid); 100 | 101 | /// 102 | /// Converts the value of the current structure to its equivalent string representation. 103 | /// 104 | /// A string representation of the value of the current structure. 105 | public override string ToString() 106 | { 107 | if (SopInstanceUid != null) 108 | return StudyInstanceUid + '/' + SeriesInstanceUid + '/' + SopInstanceUid; 109 | else if (SeriesInstanceUid != null) 110 | return StudyInstanceUid + '/' + SeriesInstanceUid; 111 | else 112 | return StudyInstanceUid; 113 | } 114 | 115 | /// 116 | /// Creates a new structure for the given study. 117 | /// 118 | /// The unique identifier for the study. 119 | /// A structure for the given study. 120 | /// 121 | /// is empty or consists entirely of white space characters. 122 | /// 123 | /// is . 124 | public static DicomIdentifier ForStudy(string uid) 125 | => new DicomIdentifier(uid, null, null); 126 | 127 | /// 128 | /// Creates a new structure for the given series. 129 | /// 130 | /// The unique identifier for the study. 131 | /// The unique identifier for the series. 132 | /// A structure for the given series. 133 | /// 134 | /// or 135 | /// is empty or consists entirely of white space characters. 136 | /// 137 | /// 138 | /// or is . 139 | /// 140 | public static DicomIdentifier ForSeries(string studyInstanceUid, string seriesInstanceUid) 141 | => new DicomIdentifier(studyInstanceUid, EnsureArg.IsNotNullOrWhiteSpace(seriesInstanceUid, nameof(seriesInstanceUid)), null); 142 | 143 | /// 144 | /// Creates a new structure for the given SOP instance. 145 | /// 146 | /// The unique identifier for the study. 147 | /// The unique identifier for the series. 148 | /// The unique identifier for the SOP instance. 149 | /// A structure for the given SOP instance. 150 | /// 151 | /// , , or 152 | /// is empty or consists entirely of white space characters. 153 | /// 154 | /// 155 | /// , , 156 | /// is . 157 | /// 158 | public static DicomIdentifier ForInstance(string studyInstanceUid, string seriesInstanceUid, string sopInstanceUid) 159 | => new DicomIdentifier( 160 | studyInstanceUid, 161 | EnsureArg.IsNotNullOrWhiteSpace(seriesInstanceUid, nameof(seriesInstanceUid)), 162 | EnsureArg.IsNotNullOrWhiteSpace(sopInstanceUid, nameof(sopInstanceUid))); 163 | 164 | /// 165 | /// Creates a new structure for the given instance. 166 | /// 167 | /// A DICOM dataset. 168 | /// A structure for the given dataset. 169 | /// 170 | /// Either the study, series, or SOP instance UIDs are empty or consists entirely of white space characters. 171 | /// 172 | /// is . 173 | public static DicomIdentifier ForInstance(DicomDataset dataset) 174 | => new DicomIdentifier( 175 | EnsureArg.IsNotNull(dataset, nameof(dataset)).GetString(DicomTag.StudyInstanceUID), 176 | EnsureArg.IsNotNullOrWhiteSpace(dataset.GetString(DicomTag.SeriesInstanceUID), nameof(dataset)), 177 | EnsureArg.IsNotNullOrWhiteSpace(dataset.GetString(DicomTag.SOPInstanceUID), nameof(dataset))); 178 | } 179 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/Models/DicomOperation.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | namespace Microsoft.Health.Dicom.Client.Models; 7 | 8 | /// 9 | /// Specifies the category of a DICOM operation. 10 | /// 11 | public enum DicomOperation 12 | { 13 | /// 14 | /// Specifies an data cleanup operation that cleans up instance data. 15 | /// 16 | DataCleanup, 17 | 18 | /// 19 | /// Specifies an content length backfill operation. 20 | /// 21 | ContentLengthBackFill, 22 | 23 | /// 24 | /// Specifies an export operation that copies data out of the DICOM server and into an external data store. 25 | /// 26 | Export, 27 | 28 | /// 29 | /// Specifies a reindexing operation that updates the indicies for previously added data based on new tags. 30 | /// 31 | Reindex, 32 | 33 | /// 34 | /// Specifies an operation whose type is missing or unrecognized. 35 | /// 36 | Unknown, 37 | 38 | /// 39 | /// Specifies an update operation that updates the Dicom attributes. 40 | /// 41 | Update, 42 | } 43 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/Models/DicomOperationReference.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | using Microsoft.Health.Operations; 8 | 9 | namespace Microsoft.Health.Dicom.Client.Models; 10 | 11 | /// 12 | /// Represents a reference to an existing DICOM long-running operation. 13 | /// 14 | public class DicomOperationReference : OperationReference, IResourceReference> 15 | { 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The unique operation ID. 20 | /// The resource URL for the operation. 21 | /// is . 22 | public DicomOperationReference(Guid id, Uri href) 23 | : base(id, href) 24 | { } 25 | } 26 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/Models/Export/AzureBlobExportOptions.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | 8 | namespace Microsoft.Health.Dicom.Client.Models.Export; 9 | 10 | internal sealed class AzureBlobExportOptions 11 | { 12 | public string BlobContainerName { get; init; } 13 | 14 | public Uri BlobContainerUri { get; init; } 15 | 16 | public string ConnectionString { get; init; } 17 | 18 | public bool UseManagedIdentity { get; init; } 19 | } 20 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/Models/Export/IdentifierExportOptions.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System.Collections.Generic; 7 | 8 | namespace Microsoft.Health.Dicom.Client.Models.Export; 9 | 10 | internal sealed class IdentifierExportOptions 11 | { 12 | public IReadOnlyCollection Values { get; init; } 13 | } 14 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/Models/ExportDataOptions.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using EnsureThat; 7 | 8 | namespace Microsoft.Health.Dicom.Client.Models; 9 | 10 | /// 11 | /// Represents options for either what data should be exported from the DICOM service, 12 | /// or where that data should be copied. 13 | /// 14 | public sealed class ExportDataOptions 15 | { 16 | /// 17 | /// Creates a new instance of the class 18 | /// with the given type. 19 | /// 20 | /// The type of options this new instance represents. 21 | public ExportDataOptions(T type) 22 | { 23 | Type = type; 24 | } 25 | 26 | internal ExportDataOptions(T type, object settings) 27 | { 28 | Type = type; 29 | Settings = EnsureArg.IsNotNull(settings, nameof(settings)); 30 | } 31 | 32 | /// 33 | /// Gets the type of options this instance represents. 34 | /// 35 | /// A type denoting the kind of options. 36 | public T Type { get; } 37 | 38 | internal object Settings { get; } 39 | } 40 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/Models/ExportDestination.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | using EnsureThat; 8 | using Microsoft.Health.Dicom.Client.Models.Export; 9 | 10 | namespace Microsoft.Health.Dicom.Client.Models; 11 | 12 | /// 13 | /// A collection of utilities for creating export destination options 14 | /// that specify to where data should be copied from the DICOM server. 15 | /// 16 | public static class ExportDestination 17 | { 18 | /// 19 | /// Creates export destination options for Azure Blob storage based on a URI. 20 | /// 21 | /// 22 | /// The may contain a SAS token for authentication. 23 | /// 24 | /// A URI specifying the Azure Blob container. 25 | /// Optionally connect to the storage account using the configured managed identity. 26 | /// The corresponding export destination options. 27 | /// is . 28 | public static ExportDataOptions ForAzureBlobStorage(Uri blobContainerUri, bool useManagedIdentity = false) 29 | { 30 | EnsureArg.IsNotNull(blobContainerUri, nameof(blobContainerUri)); 31 | return new ExportDataOptions( 32 | ExportDestinationType.AzureBlob, 33 | new AzureBlobExportOptions 34 | { 35 | BlobContainerUri = blobContainerUri, 36 | UseManagedIdentity = useManagedIdentity, 37 | }); 38 | } 39 | 40 | /// 41 | /// Creates export destination options for Azure Blob storage based on a connection string and container name. 42 | /// 43 | /// 44 | /// The may contain a SAS token for authentication. 45 | /// 46 | /// A connection string for the Azure Blob Storage account. 47 | /// The name of the blob container. 48 | /// The corresponding export destination options. 49 | /// 50 | /// or is empty or 51 | /// consists of white space characters. 52 | /// 53 | /// 54 | /// or is . 55 | /// 56 | public static ExportDataOptions ForAzureBlobStorage(string connectionString, string blobContainerName) 57 | { 58 | EnsureArg.IsNotNullOrWhiteSpace(connectionString, nameof(connectionString)); 59 | EnsureArg.IsNotNullOrWhiteSpace(blobContainerName, nameof(blobContainerName)); 60 | return new ExportDataOptions( 61 | ExportDestinationType.AzureBlob, 62 | new AzureBlobExportOptions 63 | { 64 | ConnectionString = connectionString, 65 | BlobContainerName = blobContainerName, 66 | }); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/Models/ExportDestinationType.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | namespace Microsoft.Health.Dicom.Client.Models; 7 | 8 | /// 9 | /// Specifies the kind of destination where data may be exported. 10 | /// 11 | public enum ExportDestinationType 12 | { 13 | /// 14 | /// Specifies an unknown destination. 15 | /// 16 | Unknown, 17 | 18 | /// 19 | /// Specifies Azure Blob Storage. 20 | /// 21 | AzureBlob, 22 | } 23 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/Models/ExportResults.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | 8 | namespace Microsoft.Health.Dicom.Client.Models; 9 | 10 | /// 11 | /// Represents the current state of the export operation. 12 | /// 13 | public sealed class ExportResults 14 | { 15 | /// 16 | /// Gets or sets the number of DICOM files that were successfully exported. 17 | /// 18 | /// The non-negative number of exported DICOM files. 19 | public long Exported { get; set; } 20 | 21 | /// 22 | /// Gets or sets the number of DICOM files that were skipped because they failed to be exported. 23 | /// 24 | /// The non-negative number of DICOM files that failed to be exported. 25 | public long Skipped { get; set; } 26 | 27 | /// 28 | /// Gets or sets the URI for containing the errors for this operation. 29 | /// 30 | /// The for the resource containg export errors. 31 | public Uri ErrorHref { get; set; } 32 | } 33 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/Models/ExportSource.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using EnsureThat; 9 | using Microsoft.Health.Dicom.Client.Models.Export; 10 | 11 | namespace Microsoft.Health.Dicom.Client.Models; 12 | 13 | /// 14 | /// A collection of utilities for creating export source options 15 | /// that specify what data should be copied. 16 | /// 17 | public static class ExportSource 18 | { 19 | /// 20 | /// Creates export source options for a list of DICOM identifiers. 21 | /// 22 | /// One or more identifiers. 23 | /// The corresponding export source options. 24 | /// is empty. 25 | /// is . 26 | public static ExportDataOptions ForIdentifiers(params DicomIdentifier[] identifiers) 27 | { 28 | EnsureArg.HasItems(identifiers, nameof(identifiers)); 29 | return new ExportDataOptions( 30 | ExportSourceType.Identifiers, 31 | new IdentifierExportOptions { Values = identifiers }); 32 | } 33 | 34 | /// 35 | /// Creates export source options for a list of DICOM identifiers. 36 | /// 37 | /// One or more identifiers. 38 | /// The corresponding export source options. 39 | /// is empty. 40 | /// is . 41 | public static ExportDataOptions ForIdentifiers(IReadOnlyCollection identifiers) 42 | { 43 | EnsureArg.HasItems(identifiers, nameof(identifiers)); 44 | return new ExportDataOptions( 45 | ExportSourceType.Identifiers, 46 | new IdentifierExportOptions { Values = identifiers }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/Models/ExportSourceType.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | namespace Microsoft.Health.Dicom.Client.Models; 7 | 8 | /// 9 | /// Specifies the kind of format used to describe the data set to be exported. 10 | /// 11 | public enum ExportSourceType 12 | { 13 | /// 14 | /// Specifies an unknown source format. 15 | /// 16 | Unknown, 17 | 18 | /// 19 | /// Specifies a list of DICOM identifiers. 20 | /// 21 | Identifiers, 22 | } 23 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/Models/ExportSpecification.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | namespace Microsoft.Health.Dicom.Client.Models; 7 | 8 | internal sealed class ExportSpecification 9 | { 10 | public ExportDataOptions Source { get; init; } 11 | 12 | public ExportDataOptions Destination { get; init; } 13 | } 14 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/Models/ExtendedQueryTagError.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | 8 | namespace Microsoft.Health.Dicom.Client.Models; 9 | 10 | /// 11 | /// Represents a problem that arose when re-indexing the given DICOM instance. 12 | /// 13 | public class ExtendedQueryTagError 14 | { 15 | /// 16 | /// Gets or sets the DICOM study instance UID. 17 | /// 18 | public string StudyInstanceUid { get; set; } 19 | 20 | /// 21 | /// Gets or sets the DICOM series instance UID. 22 | /// 23 | public string SeriesInstanceUid { get; set; } 24 | 25 | /// 26 | /// Gets or sets the DICOM SOP instance UID. 27 | /// 28 | public string SopInstanceUid { get; set; } 29 | 30 | /// 31 | /// Gets or sets the date and time when the error was found. 32 | /// 33 | public DateTime CreatedTime { get; set; } 34 | 35 | /// 36 | /// Gets or sets the localized error message. 37 | /// 38 | public string ErrorMessage { get; set; } 39 | } 40 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/Models/ExtendedQueryTagErrorReference.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | 9 | namespace Microsoft.Health.Dicom.Client.Models; 10 | 11 | /// 12 | /// Represents a reference to a one or more extended query tag errors. 13 | /// 14 | public class ExtendedQueryTagErrorReference : IResourceReference> 15 | { 16 | /// 17 | /// Gets or sets the number of errors. 18 | /// 19 | /// The positive number of errors found at the . 20 | public int Count { get; set; } 21 | 22 | /// 23 | /// Gets or sets the resource reference for the errors. 24 | /// 25 | /// The unique resource URL for the extended query tag errors. 26 | public Uri Href { get; set; } 27 | } 28 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/Models/ExtendedQueryTagStatus.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | namespace Microsoft.Health.Dicom.Client.Models; 9 | 10 | /// 11 | /// Status of extended query tag. 12 | /// 13 | [SuppressMessage("Design", "CA1028:Enum Storage should be Int32", Justification = "Value is stored in SQL as TINYINT.")] 14 | public enum ExtendedQueryTagStatus : byte 15 | { 16 | /// 17 | /// The query tag is being added. 18 | /// 19 | Adding = 0, 20 | 21 | /// 22 | /// The query tag has been added to system. 23 | /// 24 | Ready = 1, 25 | 26 | /// 27 | /// The query tag is being deleting. 28 | /// 29 | Deleting = 2, 30 | } 31 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/Models/GetExtendedQueryTagEntry.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | namespace Microsoft.Health.Dicom.Client.Models; 7 | 8 | /// 9 | /// External representation of a extended query tag entry when retrieving. 10 | /// 11 | public class GetExtendedQueryTagEntry 12 | { 13 | /// 14 | /// Path of this tag. Normally it's composed of groupid and elementid. 15 | /// E.g: 00100020 is path of patient id. 16 | /// 17 | public string Path { get; set; } 18 | 19 | /// 20 | /// VR of this tag. 21 | /// 22 | public string VR { get; set; } 23 | 24 | /// 25 | /// Level of this tag. Could be Study, Series or Instance. 26 | /// 27 | public QueryTagLevel Level { get; set; } 28 | 29 | /// 30 | /// Status of this tag. 31 | /// 32 | public ExtendedQueryTagStatus Status { get; set; } 33 | 34 | /// 35 | /// Query Status of this tag. 36 | /// 37 | public QueryStatus QueryStatus { get; set; } 38 | 39 | /// 40 | /// Identification code of private tag implementer. 41 | /// 42 | public string PrivateCreator { get; set; } 43 | 44 | /// 45 | /// Optional errors associated with the query tag. 46 | /// 47 | public ExtendedQueryTagErrorReference Errors { get; set; } 48 | 49 | /// 50 | /// Optional reference to the operation acted upon the act. 51 | /// 52 | public DicomOperationReference Operation { get; set; } 53 | } 54 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/Models/IResourceReference.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | 8 | namespace Microsoft.Health.Dicom.Client.Models; 9 | 10 | /// 11 | /// Represents a reference to a RESTful resource. 12 | /// 13 | /// The type of resource. 14 | public interface IResourceReference 15 | { 16 | /// 17 | /// Gets the endpoint that returns resources of type . 18 | /// 19 | /// The resource URL. 20 | Uri Href { get; } 21 | } 22 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/Models/Partition.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | using System.Text.Json.Serialization; 8 | using EnsureThat; 9 | 10 | namespace Microsoft.Health.Dicom.Client.Models; 11 | 12 | public class Partition 13 | { 14 | public const string DefaultName = "Microsoft.Default"; 15 | 16 | public const int DefaultKey = 1; 17 | 18 | [JsonPropertyName("partitionKey")] // these explicit names are here for the REST schema 19 | public int Key { get; } 20 | 21 | [JsonPropertyName("partitionName")] // these explicit names are here for the REST schema 22 | public string Name { get; } 23 | 24 | public DateTimeOffset CreatedDate { get; set; } 25 | 26 | public static Partition Default { get; } = new(DefaultKey, DefaultName); 27 | 28 | public Partition(int key, string name, DateTimeOffset createdDate = default) 29 | { 30 | Key = key; 31 | Name = EnsureArg.IsNotNull(name, nameof(name)); 32 | CreatedDate = createdDate; 33 | } 34 | } -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/Models/QueryStatus.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | namespace Microsoft.Health.Dicom.Client.Models; 7 | 8 | /// 9 | /// Query status on an extended query tag. 10 | /// 11 | public enum QueryStatus 12 | { 13 | /// 14 | /// The tag is not allowed to be queried. 15 | /// 16 | Disabled, 17 | 18 | /// 19 | /// The tag is allowed to be queried. 20 | /// 21 | Enabled, 22 | } 23 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/Models/QueryTagLevel.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System.Text.Json.Serialization; 7 | 8 | namespace Microsoft.Health.Dicom.Client.Models; 9 | 10 | /// 11 | /// Level of a queryable tag. 12 | /// 13 | [JsonConverter(typeof(JsonStringEnumConverter))] 14 | public enum QueryTagLevel 15 | { 16 | /// 17 | /// The tag is queriable on instance level. 18 | /// 19 | Instance = 0, 20 | 21 | /// 22 | /// The tag is queriable on series level. 23 | /// 24 | Series = 1, 25 | 26 | /// 27 | /// The tag is queriable on study level. 28 | /// 29 | Study = 2, 30 | } 31 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/Models/UpdateExtendedQueryTagEntry.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | namespace Microsoft.Health.Dicom.Client.Models; 7 | 8 | /// 9 | /// Encapsulate parameters for updating extended query tag. 10 | /// 11 | public class UpdateExtendedQueryTagEntry 12 | { 13 | /// 14 | /// Gets or sets query status. 15 | /// 16 | public QueryStatus QueryStatus { get; set; } 17 | } 18 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/Models/UpdateResults.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System.Collections.Generic; 7 | 8 | namespace Microsoft.Health.Dicom.Client.Models; 9 | 10 | public class UpdateResults 11 | { 12 | public int StudyProcessed { get; set; } 13 | 14 | public int StudyUpdated { get; set; } 15 | 16 | public int StudyFailed { get; set; } 17 | 18 | public long InstanceUpdated { get; set; } 19 | 20 | public IReadOnlyList Errors { get; set; } 21 | } 22 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/Models/UpdateSpecification.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System.Collections.Generic; 7 | using FellowOakDicom; 8 | 9 | namespace Microsoft.Health.Dicom.Client.Models; 10 | 11 | public class UpdateSpecification 12 | { 13 | public UpdateSpecification(IReadOnlyList studyInstanceUids, DicomDataset changeDataset) 14 | { 15 | StudyInstanceUids = studyInstanceUids; 16 | ChangeDataset = changeDataset; 17 | } 18 | 19 | public IReadOnlyList StudyInstanceUids { get; } 20 | 21 | public DicomDataset ChangeDataset { get; } 22 | } 23 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | using System.Resources; 8 | 9 | [assembly: CLSCompliant(false)] 10 | [assembly: NeutralResourcesLanguage("en-us")] 11 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/Serialization/DicomIdentifierJsonConverter.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | using System.Globalization; 8 | using System.Text.Json; 9 | using System.Text.Json.Serialization; 10 | using EnsureThat; 11 | using Microsoft.Health.Dicom.Client.Models; 12 | 13 | namespace Microsoft.Health.Dicom.Client.Serialization; 14 | 15 | internal sealed class DicomIdentifierJsonConverter : JsonConverter 16 | { 17 | public override DicomIdentifier Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 18 | => throw new NotSupportedException( 19 | string.Format(CultureInfo.CurrentCulture, DicomClientResource.JsonReadNotSupported, nameof(DicomIdentifier))); 20 | 21 | public override void Write(Utf8JsonWriter writer, DicomIdentifier value, JsonSerializerOptions options) 22 | => EnsureArg.IsNotNull(writer, nameof(writer)).WriteStringValue(value.ToString()); 23 | } 24 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/Serialization/ExportDataOptionsJsonConverter.T.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | using System.Diagnostics.CodeAnalysis; 8 | using System.Text.Json; 9 | using System.Text.Json.Serialization; 10 | using EnsureThat; 11 | using Microsoft.Health.Dicom.Client.Models; 12 | 13 | namespace Microsoft.Health.Dicom.Client.Serialization; 14 | 15 | internal sealed class ExportDataOptionsJsonConverter : JsonConverter> 16 | { 17 | private readonly Func _getType; 18 | 19 | public ExportDataOptionsJsonConverter(Func getType) 20 | => _getType = EnsureArg.IsNotNull(getType, nameof(getType)); 21 | 22 | public override ExportDataOptions Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 23 | { 24 | ExportDataOptions intermediate = JsonSerializer.Deserialize(ref reader, options); 25 | 26 | Type type = _getType(intermediate.Type); 27 | return new ExportDataOptions(intermediate.Type, intermediate.Settings.Deserialize(type, options)); 28 | } 29 | 30 | public override void Write(Utf8JsonWriter writer, ExportDataOptions value, JsonSerializerOptions options) 31 | { 32 | writer.WriteStartObject(); 33 | 34 | writer.WritePropertyName(GetPropertyName(nameof(ExportDataOptions.Type), options.PropertyNamingPolicy)); 35 | JsonSerializer.Serialize(writer, value.Type, options); 36 | 37 | writer.WritePropertyName(GetPropertyName(nameof(ExportDataOptions.Settings), options.PropertyNamingPolicy)); 38 | JsonSerializer.Serialize(writer, value.Settings, _getType(value.Type), options); 39 | 40 | writer.WriteEndObject(); 41 | } 42 | 43 | private static string GetPropertyName(string name, JsonNamingPolicy policy) 44 | => policy != null ? policy.ConvertName(name) : name; 45 | 46 | [SuppressMessage("Microsoft.Performance", "CA1812:Avoid uninstantiated internal classes.", Justification = "This class is deserialized.")] 47 | private sealed class ExportDataOptions 48 | { 49 | public T Type { get; set; } 50 | 51 | public JsonElement Settings { get; set; } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/Serialization/ExportDataOptionsJsonConverter.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | using System.Globalization; 8 | using System.Text.Json; 9 | using System.Text.Json.Serialization; 10 | using EnsureThat; 11 | using Microsoft.Health.Dicom.Client.Models; 12 | using Microsoft.Health.Dicom.Client.Models.Export; 13 | 14 | namespace Microsoft.Health.Dicom.Client.Serialization; 15 | 16 | internal sealed class ExportDataOptionsJsonConverter : JsonConverterFactory 17 | { 18 | public override bool CanConvert(Type typeToConvert) 19 | => typeToConvert.IsGenericType && typeToConvert.GetGenericTypeDefinition() == typeof(ExportDataOptions<>); 20 | 21 | public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) 22 | { 23 | Type arg = EnsureArg.IsNotNull(typeToConvert, nameof(typeToConvert)).GetGenericArguments()[0]; 24 | if (arg == typeof(ExportSourceType)) 25 | return new ExportDataOptionsJsonConverter(MapSourceType); 26 | else if (arg == typeof(ExportDestinationType)) 27 | return new ExportDataOptionsJsonConverter(MapDestinationType); 28 | else 29 | throw new JsonException( 30 | string.Format(CultureInfo.CurrentCulture, DicomClientResource.InvalidType, typeToConvert)); 31 | } 32 | 33 | private static Type MapSourceType(ExportSourceType type) 34 | => type switch 35 | { 36 | ExportSourceType.Identifiers => typeof(IdentifierExportOptions), 37 | _ => throw new JsonException( 38 | string.Format( 39 | CultureInfo.CurrentCulture, 40 | DicomClientResource.UnexpectedValue, 41 | nameof(ExportDataOptions.Type), 42 | nameof(ExportSourceType.Identifiers))), 43 | }; 44 | 45 | private static Type MapDestinationType(ExportDestinationType type) 46 | => type switch 47 | { 48 | ExportDestinationType.AzureBlob => typeof(AzureBlobExportOptions), 49 | _ => throw new JsonException( 50 | string.Format( 51 | CultureInfo.CurrentCulture, 52 | DicomClientResource.UnexpectedValue, 53 | nameof(ExportDataOptions.Type), 54 | nameof(ExportDestinationType.AzureBlob))), 55 | }; 56 | } 57 | -------------------------------------------------------------------------------- /src/Microsoft.Health.Dicom.Client/Serialization/OperationStateConverter.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------------------------- 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. 4 | // ------------------------------------------------------------------------------------------------- 5 | 6 | using System; 7 | using System.Globalization; 8 | using System.Text.Json; 9 | using System.Text.Json.Nodes; 10 | using System.Text.Json.Serialization; 11 | using Microsoft.Health.Dicom.Client.Models; 12 | using Microsoft.Health.Operations; 13 | 14 | namespace Microsoft.Health.Dicom.Client.Serialization; 15 | 16 | internal sealed class OperationStateConverter : JsonConverter> 17 | { 18 | public override IOperationState Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 19 | { 20 | JsonObject obj = JsonSerializer.Deserialize(ref reader, options); 21 | 22 | if (!obj.TryGetPropertyValue(nameof(IOperationState.Type), out JsonNode value)) 23 | throw new JsonException(); 24 | 25 | return value.Deserialize(options) switch 26 | { 27 | DicomOperation.Export => obj.Deserialize>(options), 28 | DicomOperation.Update => obj.Deserialize>(options), 29 | _ => obj.Deserialize>(options), 30 | }; 31 | } 32 | 33 | public override void Write(Utf8JsonWriter writer, IOperationState value, JsonSerializerOptions options) 34 | => throw new NotSupportedException( 35 | string.Format(CultureInfo.CurrentCulture, DicomClientResource.JsonWriteNotSupported, nameof(DicomIdentifier))); 36 | } 37 | --------------------------------------------------------------------------------