Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
983cc24
Initial plan
Copilot Dec 16, 2025
145a994
Add LoadFromAsync and SaveToAsync methods to DataContent with MediaTy…
Copilot Dec 16, 2025
ffcb4c1
Fix collection expression syntax for consistency in tests
Copilot Dec 16, 2025
2e3be2b
Address review feedback: move MediaTypeMap to polyfills, update DataC…
Copilot Dec 16, 2025
3ec4410
Add C# 14 extension member polyfill for File.ReadAllBytesAsync
Copilot Dec 16, 2025
ecf0aa6
Remove name parameter from LoadFromAsync and use MemoryMarshal.TryGet…
Copilot Dec 16, 2025
ced14b3
Switch extension/MIME type mappings to use MediaTypeMap polyfill
Copilot Dec 16, 2025
2866279
Change return types to ValueTask and add WriteAllBytesAsync polyfill
Copilot Dec 16, 2025
1cceb2f
Add FilePolyfills to test project for net462 support
Copilot Dec 16, 2025
031c3c2
Fix path traversal in SaveToAsync, simplify IngestionDocumentReader, …
Copilot Dec 16, 2025
cc9326c
Simplify extension inference by removing unnecessary null check
Copilot Dec 17, 2025
e86d9f8
Use DataContent.LoadFromAsync in MarkItDownMcpReader
Copilot Dec 17, 2025
909b12b
Simplify MarkItDownMcpReader by passing mediaType directly to LoadFro…
Copilot Dec 17, 2025
731acc6
Pre-initialize MemoryStream capacity when stream is seekable
Copilot Dec 17, 2025
bd2f87a
Address PR feedback, plus some cleanup and more tests
stephentoub Jan 6, 2026
faa7318
Add Microsoft.Extensions.AI.Abstractions reference to MarkItDown project
Copilot Jan 6, 2026
2d2deda
Merge branch 'main' into copilot/add-helper-methods-data-content
stephentoub Jan 21, 2026
0de16a8
Use FileMode.CreateNew instead of OpenOrCreate in SaveToAsync
Copilot Jan 21, 2026
bc72d32
Add test for SaveToAsync with existing file to verify exception and n…
Copilot Jan 21, 2026
3bf025c
Add missing .cgm/image/cgm mapping to MediaTypeMap polyfill
Copilot Jan 21, 2026
71efd51
Document IOException for SaveToAsync when file already exists
Copilot Jan 21, 2026
5267589
Fix and clean up DataContent tests per review feedback
Copilot Jan 23, 2026
5d9c6d1
Add tests for relative path scenarios in SaveToAsync
Copilot Jan 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions eng/MSBuild/LegacySupport.props
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,16 @@
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\LegacySupport\CollectionBuilder\*.cs" LinkBase="LegacySupport\CollectionBuilder" />
</ItemGroup>

<ItemGroup Condition="'$(InjectMediaTypeMapOnLegacy)' == 'true' AND ('$(TargetFramework)' == 'net462' or '$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'net8.0' or '$(TargetFramework)' == 'net9.0' or '$(TargetFramework)' == 'net10.0')">
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\LegacySupport\MediaTypeMap\*.cs" LinkBase="LegacySupport\MediaTypeMap" />
</ItemGroup>

<ItemGroup Condition="'$(InjectNullabilityInfoContextOnLegacy)' == 'true' AND ('$(TargetFramework)' == 'net462' or '$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'netcoreapp3.1')">
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\LegacySupport\NullabilityInfoContext\*.cs" LinkBase="LegacySupport\NullabilityInfoContext" />
</ItemGroup>

<!-- FilePolyfills: Adds C# 14 extension members for File methods not available on legacy frameworks -->
<ItemGroup Condition="'$(InjectFilePolyfillsOnLegacy)' == 'true'">
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\LegacySupport\FilePolyfills\*.cs" LinkBase="LegacySupport\FilePolyfills" />
</ItemGroup>
</Project>
82 changes: 82 additions & 0 deletions src/LegacySupport/FilePolyfills/FilePolyfills.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#if !NET9_0_OR_GREATER

#pragma warning disable CA1835 // Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'

using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

namespace System.IO;

/// <summary>
/// Provides polyfill extension members for <see cref="File"/> for older frameworks.
/// </summary>
[ExcludeFromCodeCoverage]
internal static class FilePolyfills
{
extension(File)
{
#if !NET
/// <summary>
/// Asynchronously reads all bytes from a file.
/// </summary>
/// <param name="path">The file to read from.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A task that represents the asynchronous read operation, which wraps the byte array containing the contents of the file.</returns>
public static async Task<byte[]> ReadAllBytesAsync(string path, CancellationToken cancellationToken = default)
{
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 4096, useAsync: true);
byte[] data = new byte[stream.Length];
int totalRead = 0;
while (totalRead < data.Length)
{
int read = await stream.ReadAsync(data, totalRead, data.Length - totalRead, cancellationToken).ConfigureAwait(false);
if (read == 0)
{
break;
}

totalRead += read;
}

return data;
}
#endif

/// <summary>
/// Asynchronously writes all bytes to a file.
/// </summary>
/// <param name="path">The file to write to.</param>
/// <param name="bytes">The bytes to write to the file.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A task that represents the asynchronous write operation.</returns>
public static async Task WriteAllBytesAsync(string path, ReadOnlyMemory<byte> bytes, CancellationToken cancellationToken = default)
{
// Try to avoid ToArray() if the data is backed by a byte[] with offset 0 and matching length
byte[] byteArray;
if (MemoryMarshal.TryGetArray(bytes, out ArraySegment<byte> segment) &&
segment.Offset == 0 &&
segment.Count == segment.Array!.Length)
{
byteArray = segment.Array;
}
else
{
byteArray = bytes.ToArray();
}

#if NET
await File.WriteAllBytesAsync(path, byteArray, cancellationToken).ConfigureAwait(false);
#else
using var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true);
await stream.WriteAsync(byteArray, 0, byteArray.Length, cancellationToken).ConfigureAwait(false);
#endif
}
}
}

#endif
9 changes: 9 additions & 0 deletions src/LegacySupport/FilePolyfills/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# About FilePolyfills

This folder contains C# 14 extension member polyfills for `System.IO.File` methods
that are not available on older frameworks.

- `File.ReadAllBytesAsync` - Added in .NET Core 2.0, not available in .NET Framework 4.6.2 or .NET Standard 2.0

The polyfill uses C# 14 extension members so the call site can use `File.ReadAllBytesAsync` naturally
and it will use the real one on supported platforms and the polyfill elsewhere.
Loading
Loading