-
Notifications
You must be signed in to change notification settings - Fork 22
Stream Correspondence attachments to support large file upload #1413
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Ceredron
wants to merge
40
commits into
Altinn:main
Choose a base branch
from
Ceredron:feat/streamed-correspondence-upload
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+321
−63
Open
Changes from all commits
Commits
Show all changes
40 commits
Select commit
Hold shift + click to select a range
28fb6c6
Stream Correspondence attachments to support large file upload
Ceredron c8d5744
Fix build
Ceredron 1d1d245
Fix build
Ceredron 0a37109
Revert "Fix build"
Ceredron 6fc12c1
Revert "Fix build"
Ceredron 6f1e076
Fix tests
Ceredron 29fb277
Typo
Ceredron eb5cb3a
Fix more tests
Ceredron c8d453d
Merge branch 'main' into feat/streamed-correspondence-upload
Ceredron fb8f85a
Formatting
Ceredron 61805b8
Merge branch 'feat/streamed-correspondence-upload' of https://github.…
Ceredron d3a6ad1
Break API
Ceredron 33dcf0b
Expanded interface to include stream implementation
Ceredron 4af8625
Break
Ceredron 2dccc09
Tempfix for testing streaming response
Ceredron ad99a4d
Only stream when necessary
Ceredron 96211e1
"Non-breaking" version
Ceredron 55b8fde
Fix build
Ceredron 4165255
Bang some tests
Ceredron e6a0504
Re-factor to try to break less
Ceredron 460e830
Use ResponseStreamWrapper to ensure correct disposal of response mess…
Ceredron 7c7031d
Xml docs
Ceredron cc5de34
Streams needs to be suffixed with Stream
Ceredron 8821d7b
Small fixes
Ceredron f30c680
More fix
Ceredron 574f29a
Internalize 'ResponseWrapperStream', disposal, extend XML docs for c…
martinothamar dbd86ab
Experiment to use REST instead of Form endpoints
Ceredron 723e980
Formatting etc
Ceredron 527bf2c
API for experiment
Ceredron 351cddd
Revert "API for experiment"
Ceredron dc7485a
Revert "Formatting etc"
Ceredron 351ad4f
Revert "Experiment to use REST instead of Form endpoints"
Ceredron d600d0a
Merge branch 'main' into feat/streamed-correspondence-upload
martinothamar 0379ca8
New experiment
Ceredron f8ae91f
Merge branch 'feat/streamed-correspondence-upload' of https://github.…
Ceredron 2204363
Remove usings
Ceredron 29a992a
Revert "Remove usings"
Ceredron d41e3f5
Revert "New experiment"
Ceredron 49cac69
Disposals, update integration test snapshots
martinothamar 1561d72
Merge branch 'main' into feat/streamed-correspondence-upload
martinothamar File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
25 changes: 25 additions & 0 deletions
25
src/Altinn.App.Core/Features/Correspondence/Models/CorrespondenceAttachmentInMemory.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| namespace Altinn.App.Core.Features.Correspondence.Models; | ||
|
|
||
| /// <summary> | ||
| /// Represents an attachment to a correspondence. | ||
| /// </summary> | ||
| public record CorrespondenceAttachmentInMemory : CorrespondenceAttachment | ||
| { | ||
| /// <summary> | ||
| /// The data content. | ||
| /// </summary> | ||
| public required ReadOnlyMemory<byte> Data { get; init; } | ||
|
|
||
| internal override void Serialise(MultipartFormDataContent content, int index, string? filenameOverride = null) | ||
| { | ||
| const string typePrefix = "Correspondence.Content.Attachments"; | ||
| string prefix = $"{typePrefix}[{index}]"; | ||
| string actualFilename = filenameOverride ?? Filename; | ||
|
|
||
| AddRequired(content, actualFilename, $"{prefix}.Filename"); | ||
| AddRequired(content, SendersReference, $"{prefix}.SendersReference"); | ||
| AddRequired(content, DataLocationType.ToString(), $"{prefix}.DataLocationType"); | ||
| AddRequired(content, Data, "Attachments", actualFilename); | ||
| AddIfNotNull(content, IsEncrypted?.ToString(), $"{prefix}.IsEncrypted"); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
32 changes: 32 additions & 0 deletions
32
src/Altinn.App.Core/Features/Correspondence/Models/CorrespondenceStreamedAttachment.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| namespace Altinn.App.Core.Features.Correspondence.Models; | ||
|
|
||
| /// <summary> | ||
| /// Represents an attachment to a correspondence with streaming data support. | ||
| /// Inherits from CorrespondenceAttachment and provides a Stream-based data property. | ||
| /// Is more efficient if the attachment is large in size. | ||
| /// The stream must be open (not disposed) until the correspondence is sent. | ||
| /// The caller is responsible for disposing the stream after the correspondence has been sent. | ||
| /// </summary> | ||
| public record CorrespondenceStreamedAttachment : CorrespondenceAttachment | ||
| { | ||
| /// <summary> | ||
| /// The data content as a stream. | ||
| /// Is more efficient if the attachment is large in size. | ||
| /// The stream must be open (not disposed) until the correspondence is sent. | ||
| /// The caller is responsible for disposing the stream after the correspondence has been sent. | ||
| /// </summary> | ||
| public required Stream Data { get; init; } | ||
|
|
||
| internal override void Serialise(MultipartFormDataContent content, int index, string? filenameOverride = null) | ||
| { | ||
| const string typePrefix = "Correspondence.Content.Attachments"; | ||
| string prefix = $"{typePrefix}[{index}]"; | ||
| string actualFilename = filenameOverride ?? Filename; | ||
|
|
||
| AddRequired(content, actualFilename, $"{prefix}.Filename"); | ||
| AddRequired(content, SendersReference, $"{prefix}.SendersReference"); | ||
| AddRequired(content, DataLocationType.ToString(), $"{prefix}.DataLocationType"); | ||
| AddRequired(content, Data, "Attachments", actualFilename); | ||
| AddIfNotNull(content, IsEncrypted?.ToString(), $"{prefix}.IsEncrypted"); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| namespace Altinn.App.Core.Helpers; | ||
|
|
||
| /// <summary> | ||
| /// A wrapper stream that ensures proper disposal of an HttpResponseMessage along with its content stream. | ||
| /// </summary> | ||
| internal sealed class ResponseWrapperStream : Stream | ||
| { | ||
| private readonly HttpResponseMessage _response; | ||
| private readonly Stream _innerStream; | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="ResponseWrapperStream"/> class. | ||
| /// </summary> | ||
| /// <param name="response">The HTTP response message to be disposed when the stream is disposed.</param> | ||
| /// <param name="innerStream">The inner stream to wrap and delegate operations to.</param> | ||
| public ResponseWrapperStream(HttpResponseMessage response, Stream innerStream) | ||
| { | ||
| _response = response; | ||
| _innerStream = innerStream; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Releases the unmanaged resources used by the <see cref="ResponseWrapperStream"/> and optionally releases the managed resources. | ||
| /// </summary> | ||
| /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param> | ||
| protected override void Dispose(bool disposing) | ||
| { | ||
| if (disposing) | ||
| { | ||
| _response?.Dispose(); // This will also dispose the inner stream | ||
| } | ||
| base.Dispose(disposing); | ||
| } | ||
|
|
||
| // Delegate all Stream operations to _innerStream | ||
|
|
||
| /// <summary> | ||
| /// Gets a value indicating whether the current stream supports reading. | ||
| /// </summary> | ||
| public override bool CanRead => _innerStream.CanRead; | ||
|
|
||
| /// <summary> | ||
| /// Gets a value indicating whether the current stream supports seeking. | ||
| /// </summary> | ||
| public override bool CanSeek => _innerStream.CanSeek; | ||
|
|
||
| /// <summary> | ||
| /// Gets a value indicating whether the current stream supports writing. | ||
| /// </summary> | ||
| public override bool CanWrite => _innerStream.CanWrite; | ||
|
|
||
| /// <summary> | ||
| /// Gets the length in bytes of the stream. | ||
| /// </summary> | ||
| /// <exception cref="NotSupportedException">The stream does not support seeking.</exception> | ||
| public override long Length => _innerStream.Length; | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the position within the current stream. | ||
| /// </summary> | ||
| /// <exception cref="NotSupportedException">The stream does not support seeking.</exception> | ||
| public override long Position | ||
| { | ||
| get => _innerStream.Position; | ||
| set => _innerStream.Position = value; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device. | ||
| /// </summary> | ||
| public override void Flush() => _innerStream.Flush(); | ||
|
|
||
| /// <summary> | ||
| /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. | ||
| /// </summary> | ||
| /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the current source.</param> | ||
| /// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the current stream.</param> | ||
| /// <param name="count">The maximum number of bytes to be read from the current stream.</param> | ||
| /// <returns>The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.</returns> | ||
| /// <exception cref="ArgumentNullException">buffer is null.</exception> | ||
| /// <exception cref="ArgumentOutOfRangeException">offset or count is negative.</exception> | ||
| /// <exception cref="ArgumentException">The sum of offset and count is larger than the buffer length.</exception> | ||
| /// <exception cref="NotSupportedException">The stream does not support reading.</exception> | ||
| public override int Read(byte[] buffer, int offset, int count) => _innerStream.Read(buffer, offset, count); | ||
|
|
||
| /// <summary> | ||
| /// Sets the position within the current stream. | ||
| /// </summary> | ||
| /// <param name="offset">A byte offset relative to the origin parameter.</param> | ||
| /// <param name="origin">A value of type <see cref="SeekOrigin"/> indicating the reference point used to obtain the new position.</param> | ||
| /// <returns>The new position within the current stream.</returns> | ||
| /// <exception cref="NotSupportedException">The stream does not support seeking.</exception> | ||
| public override long Seek(long offset, SeekOrigin origin) => _innerStream.Seek(offset, origin); | ||
|
|
||
| /// <summary> | ||
| /// Sets the length of the current stream. | ||
| /// </summary> | ||
| /// <param name="value">The desired length of the current stream in bytes.</param> | ||
| /// <exception cref="NotSupportedException">The stream does not support both writing and seeking.</exception> | ||
| /// <exception cref="ArgumentOutOfRangeException">value is negative.</exception> | ||
| public override void SetLength(long value) => _innerStream.SetLength(value); | ||
|
|
||
| /// <summary> | ||
| /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. | ||
| /// </summary> | ||
| /// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream.</param> | ||
| /// <param name="offset">The zero-based byte offset in buffer at which to begin copying bytes to the current stream.</param> | ||
| /// <param name="count">The number of bytes to be written to the current stream.</param> | ||
| /// <exception cref="ArgumentNullException">buffer is null.</exception> | ||
| /// <exception cref="ArgumentOutOfRangeException">offset or count is negative.</exception> | ||
| /// <exception cref="ArgumentException">The sum of offset and count is greater than the buffer length.</exception> | ||
| /// <exception cref="NotSupportedException">The stream does not support writing.</exception> | ||
| public override void Write(byte[] buffer, int offset, int count) => _innerStream.Write(buffer, offset, count); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.