diff --git a/sdk/core/System.ClientModel/src/Convenience/MultiPartFile.cs b/sdk/core/System.ClientModel/src/Convenience/MultiPartFile.cs
new file mode 100644
index 000000000000..8847558059c6
--- /dev/null
+++ b/sdk/core/System.ClientModel/src/Convenience/MultiPartFile.cs
@@ -0,0 +1,57 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System.ClientModel.Internal;
+using System.IO;
+
+namespace System.ClientModel.Primitives;
+
+///
+/// A file to be uploaded as part of a multipart request.
+///
+public class MultiPartFile
+{
+ ///
+ /// Creates a new instance of .
+ ///
+ public MultiPartFile(Stream contents, string? filename = default, string? contentType = default)
+ {
+ Argument.AssertNotNull(contents, nameof(contents));
+
+ File = contents;
+ Filename = filename;
+ ContentType = contentType ?? "application/octet-stream";
+ }
+
+ ///
+ /// Creates a new instance of .
+ ///
+ public MultiPartFile(BinaryData contents, string? filename = default, string? contentType = default)
+ {
+ Argument.AssertNotNull(contents, nameof(contents));
+
+ Contents = contents;
+ Filename = filename;
+ ContentType = contentType ?? "application/octet-stream";
+ }
+
+ ///
+ /// The file stream to be uploaded as part of a multipart request.
+ ///
+ public Stream? File { get; }
+
+ ///
+ /// The file contents to be uploaded as part of a multipart request.
+ ///
+ public BinaryData? Contents { get; }
+
+ ///
+ /// The name of the file to be uploaded as part of a multipart request.
+ ///
+ public string? Filename { get; }
+
+ ///
+ /// The content type of the file to be uploaded as part of a multipart request.
+ ///
+ public string ContentType { get; }
+}
diff --git a/sdk/core/System.ClientModel/src/ModelReaderWriter/CollectionWriter.cs b/sdk/core/System.ClientModel/src/ModelReaderWriter/CollectionWriter.cs
index 43c17ba4f8cb..360e7edaff8f 100644
--- a/sdk/core/System.ClientModel/src/ModelReaderWriter/CollectionWriter.cs
+++ b/sdk/core/System.ClientModel/src/ModelReaderWriter/CollectionWriter.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
using System.Collections;
+using System.IO;
namespace System.ClientModel.Primitives;
@@ -72,4 +73,5 @@ private static object GetFirstObject(IEnumerable enumerable)
}
internal abstract BinaryData Write(IEnumerable enumerable, ModelReaderWriterOptions options);
+ internal abstract void WriteTo(IEnumerable enumerable, Stream stream, ModelReaderWriterOptions options);
}
diff --git a/sdk/core/System.ClientModel/src/ModelReaderWriter/IStreamModel.cs b/sdk/core/System.ClientModel/src/ModelReaderWriter/IStreamModel.cs
new file mode 100644
index 000000000000..d013d55a7047
--- /dev/null
+++ b/sdk/core/System.ClientModel/src/ModelReaderWriter/IStreamModel.cs
@@ -0,0 +1,22 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System.IO;
+
+namespace System.ClientModel.Primitives;
+
+///
+/// Allows an object to control its own writing to a .
+/// The format is determined by the implementer.
+///
+/// The type the model can be converted into.
+public interface IStreamModel : IPersistableModel
+{
+ ///
+ /// Writes the model into the provided .
+ ///
+ /// The to write the model into.
+ /// The to use.
+ /// If the model does not support the requested .
+ void Write(Stream stream, ModelReaderWriterOptions options);
+}
diff --git a/sdk/core/System.ClientModel/src/ModelReaderWriter/JsonCollectionWriter.cs b/sdk/core/System.ClientModel/src/ModelReaderWriter/JsonCollectionWriter.cs
index 48a75c6472af..634b75af6393 100644
--- a/sdk/core/System.ClientModel/src/ModelReaderWriter/JsonCollectionWriter.cs
+++ b/sdk/core/System.ClientModel/src/ModelReaderWriter/JsonCollectionWriter.cs
@@ -3,6 +3,7 @@
using System.ClientModel.Internal;
using System.Collections;
+using System.IO;
using System.Text.Json;
namespace System.ClientModel.Primitives;
@@ -18,6 +19,13 @@ internal override BinaryData Write(IEnumerable enumerable, ModelReaderWriterOpti
return sequenceWriter.ExtractReader().ToBinaryData();
}
+ internal override void WriteTo(IEnumerable enumerable, Stream stream, ModelReaderWriterOptions options)
+ {
+ using var writer = new Utf8JsonWriter(stream);
+ WriteEnumerable(enumerable, writer, options);
+ writer.Flush();
+ }
+
private static void WriteJson(object model, Utf8JsonWriter writer, ModelReaderWriterOptions options)
{
if (model is IJsonModel jsonModel)
diff --git a/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelReaderWriter.cs b/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelReaderWriter.cs
index 92a51804eac9..36bcc24f1d16 100644
--- a/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelReaderWriter.cs
+++ b/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelReaderWriter.cs
@@ -4,6 +4,7 @@
using System.ClientModel.Internal;
using System.Collections;
using System.Diagnostics.CodeAnalysis;
+using System.IO;
using System.Runtime.CompilerServices;
namespace System.ClientModel.Primitives;
@@ -67,6 +68,65 @@ public static BinaryData Write(object model, ModelReaderWriterOptions? options =
}
}
+ ///
+ /// Writes the model into the provided .
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static void Write(T model, Stream stream, ModelReaderWriterOptions? options = default)
+ where T : IStreamModel
+ {
+ if (model is null)
+ {
+ throw new ArgumentNullException(nameof(model));
+ }
+
+ if (stream is null)
+ {
+ throw new ArgumentNullException(nameof(stream));
+ }
+
+ options ??= ModelReaderWriterOptions.Json;
+
+ WriteStreamModel(model, stream, options);
+ }
+
+ ///
+ /// Writes the model into the provided .
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static void Write(object model, Stream stream, ModelReaderWriterOptions? options = default)
+ {
+ if (model is null)
+ {
+ throw new ArgumentNullException(nameof(model));
+ }
+ if (stream is null)
+ {
+ throw new ArgumentNullException(nameof(stream));
+ }
+
+ options ??= ModelReaderWriterOptions.Json;
+
+ //temp blocking this for symetry of functionality on read/write with no context.
+ //will be allowed after https://github.com/Azure/azure-sdk-for-net/issues/48294
+ if (model is IStreamModel iModel)
+ {
+ WriteStreamModel(iModel, stream, options);
+ }
+ else
+ {
+ throw new InvalidOperationException($"{model.GetType().Name} does not implement IStreamModel");
+ }
+ }
+
///
/// Converts the value of a model into a .
///
@@ -111,6 +171,56 @@ public static BinaryData Write(object model, ModelReaderWriterContext context, M
return WritePersistableOrEnumerable(model, context, options);
}
+ ///
+ /// Writes the model into the provided .
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static void Write(T model, Stream stream, ModelReaderWriterContext context, ModelReaderWriterOptions? options = default)
+ {
+ if (model is null)
+ {
+ throw new ArgumentNullException(nameof(model));
+ }
+
+ if (stream is null)
+ {
+ throw new ArgumentNullException(nameof(stream));
+ }
+
+ options ??= ModelReaderWriterOptions.Json;
+
+ WriteStreamModelOrEnumerable(model, stream, context, options);
+ }
+
+ ///
+ /// Writes the model into the provided .
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static void Write(object model, Stream stream, ModelReaderWriterContext context, ModelReaderWriterOptions? options = default)
+ {
+ if (model is null)
+ {
+ throw new ArgumentNullException(nameof(model));
+ }
+
+ if (stream is null)
+ {
+ throw new ArgumentNullException(nameof(stream));
+ }
+
+ options ??= ModelReaderWriterOptions.Json;
+
+ WriteStreamModelOrEnumerable(model, stream, context, options);
+ }
+
private static BinaryData WritePersistableOrEnumerable(T model, ModelReaderWriterContext context, ModelReaderWriterOptions options)
{
if (model is IPersistableModel iModel)
@@ -119,11 +229,9 @@ private static BinaryData WritePersistableOrEnumerable(T model, ModelReaderWr
}
else
{
- var enumerable = model as IEnumerable ?? context.GetModelInfoInternal(model!.GetType()).GetEnumerable(model);
- if (enumerable is not null)
+ if (TryWriteEnumerable(model, context, options, out BinaryData? data) && data != null)
{
- var collectionWriter = CollectionWriter.GetCollectionWriter(enumerable, options);
- return collectionWriter.Write(enumerable, options);
+ return data;
}
else
{
@@ -134,17 +242,103 @@ private static BinaryData WritePersistableOrEnumerable(T model, ModelReaderWr
private static BinaryData WritePersistable(IPersistableModel model, ModelReaderWriterOptions options)
{
- if (ShouldWriteAsJson(model, options, out IJsonModel? jsonModel))
+ if (TryWriteJson(model, options, out BinaryData? data) && data != null)
+ {
+ return data;
+ }
+ else
{
- using (UnsafeBufferSequence.Reader reader = new ModelWriter(jsonModel, options).ExtractReader())
+ return model.Write(options);
+ }
+ }
+
+ private static void WriteStreamModelOrEnumerable(
+ T model,
+ Stream stream,
+ ModelReaderWriterContext context,
+ ModelReaderWriterOptions options)
+ {
+ if (model is IStreamModel iModel)
+ {
+ WriteStreamModel(iModel, stream, options);
+ return;
+ }
+ else
+ {
+ if (TryWriteEnumerable(model, context, options, out _, stream))
{
- return reader.ToBinaryData();
+ return;
}
+ else
+ {
+ throw new InvalidOperationException($"{model!.GetType().Name} must implement IEnumerable or IStreamModel");
+ }
+ }
+ }
+
+ private static void WriteStreamModel(IStreamModel model, Stream stream, ModelReaderWriterOptions options)
+ {
+ if (TryWriteJson(model, options, out _, stream))
+ {
+ return;
}
else
{
- return model.Write(options);
+ model.Write(stream, options);
}
+
+ return;
+ }
+
+ private static bool TryWriteJson(
+ IPersistableModel model,
+ ModelReaderWriterOptions options,
+ out BinaryData? data,
+ Stream? stream = default)
+ {
+ data = null;
+
+ if (ShouldWriteAsJson(model, options, out IJsonModel? jsonModel))
+ {
+ var writer = new ModelWriter(jsonModel, options);
+ if (stream != null)
+ {
+ writer.WriteTo(stream);
+ return true;
+ }
+
+ using var reader = writer.ExtractReader();
+ data = reader.ToBinaryData();
+ return true;
+ }
+
+ return false;
+ }
+
+ private static bool TryWriteEnumerable(
+ T model,
+ ModelReaderWriterContext context,
+ ModelReaderWriterOptions options,
+ out BinaryData? data,
+ Stream? stream = default)
+ {
+ data = null;
+
+ var enumerable = model as IEnumerable ?? context.GetModelInfoInternal(model!.GetType()).GetEnumerable(model);
+ if (enumerable != null)
+ {
+ var collectionWriter = CollectionWriter.GetCollectionWriter(enumerable, options);
+ if (stream != null)
+ {
+ collectionWriter.WriteTo(enumerable, stream, options);
+ return true;
+ }
+
+ data = collectionWriter.Write(enumerable, options);
+ return true;
+ }
+
+ return false;
}
///
diff --git a/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelWriterOfT.cs b/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelWriterOfT.cs
index 7d8d5704cfaa..2018fde93a3a 100644
--- a/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelWriterOfT.cs
+++ b/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelWriterOfT.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
using System.ClientModel.Primitives;
+using System.IO;
using System.Text.Json;
namespace System.ClientModel.Internal;
@@ -30,8 +31,19 @@ public UnsafeBufferSequence.Reader ExtractReader()
{
using UnsafeBufferSequence sequenceWriter = new UnsafeBufferSequence();
using var jsonWriter = new Utf8JsonWriter(sequenceWriter);
+ WriteToInternal(jsonWriter);
+ return sequenceWriter.ExtractReader();
+ }
+
+ public void WriteTo(Stream stream)
+ {
+ using var jsonWriter = new Utf8JsonWriter(stream);
+ WriteToInternal(jsonWriter);
+ }
+
+ private void WriteToInternal(Utf8JsonWriter jsonWriter)
+ {
_model.Write(jsonWriter, _options);
jsonWriter.Flush();
- return sequenceWriter.ExtractReader();
}
}
diff --git a/sdk/core/System.ClientModel/tests/ModelReaderWriterTests/Models/ModelWithXmlAndJsonTests.cs b/sdk/core/System.ClientModel/tests/ModelReaderWriterTests/Models/ModelWithXmlAndJsonTests.cs
new file mode 100644
index 000000000000..cee6d6fe4ce9
--- /dev/null
+++ b/sdk/core/System.ClientModel/tests/ModelReaderWriterTests/Models/ModelWithXmlAndJsonTests.cs
@@ -0,0 +1,66 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System.ClientModel.Tests.Client.ModelReaderWriterTests.Models;
+using NUnit.Framework;
+using System.IO;
+using System.ClientModel.Tests.Client;
+using System.ClientModel.Primitives;
+
+namespace System.ClientModel.Tests.ModelReaderWriterTests.Models
+{
+ internal class ModelWithXmlAndJsonTests : StreamableModelTests
+ {
+ protected override string WirePayload => File.ReadAllText(TestData.GetLocation("ModelWithXmlAndJson/ModelWithXmlAndJson.xml")).TrimEnd();
+
+ protected override string JsonPayload => File.ReadAllText(TestData.GetLocation("ModelWithXmlAndJson/ModelWithXmlAndJson.json")).TrimEnd();
+ protected override ModelReaderWriterContext Context => new TestClientModelReaderWriterContext();
+
+ protected override void CompareModels(ModelWithXmlAndJson model, ModelWithXmlAndJson model2, string format)
+ {
+ Assert.AreEqual(model.Key, model2.Key);
+ Assert.AreEqual(model.Value, model2.Value);
+ Assert.AreEqual(model.ReadOnlyProperty, model2.ReadOnlyProperty);
+
+ var rawData1 = GetRawData(model);
+ Assert.IsNotNull(rawData1);
+ var rawData2 = GetRawData(model2);
+ Assert.IsNotNull(rawData2);
+
+ if (format != "W")
+ {
+ Assert.AreEqual(rawData1["extra"].ToObjectFromJson(), rawData2["extra"].ToObjectFromJson());
+ }
+ }
+
+ protected override string GetExpectedResult(string format)
+ {
+ if (format == "W")
+ {
+ return "\uFEFFColor Red "
+ + "ReadOnly ";
+ }
+
+ if (format == "J")
+ {
+ return "{\"key\":\"Color\",\"value\":\"Red\"" + ",\"readOnlyProperty\":\"ReadOnly\",\"extra\":\"stuff\"}";
+ }
+ throw new InvalidOperationException($"Unknown format used in test {format}");
+ }
+
+ protected override void VerifyModel(ModelWithXmlAndJson model, string format)
+ {
+ Assert.AreEqual("Color", model.Key);
+ Assert.AreEqual("Red", model.Value);
+ Assert.AreEqual("ReadOnly", model.ReadOnlyProperty);
+
+ var rawData = GetRawData(model);
+ Assert.IsNotNull(rawData);
+
+ if (format != "W")
+ {
+ Assert.AreEqual("stuff", rawData["extra"].ToObjectFromJson());
+ }
+ }
+ }
+}
diff --git a/sdk/core/System.ClientModel/tests/ModelReaderWriterTests/Models/StreamableModelTests.cs b/sdk/core/System.ClientModel/tests/ModelReaderWriterTests/Models/StreamableModelTests.cs
new file mode 100644
index 000000000000..703fff1ee8d6
--- /dev/null
+++ b/sdk/core/System.ClientModel/tests/ModelReaderWriterTests/Models/StreamableModelTests.cs
@@ -0,0 +1,61 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System.ClientModel.Primitives;
+using NUnit.Framework;
+
+namespace System.ClientModel.Tests.ModelReaderWriterTests.Models
+{
+ internal abstract class StreamableModelTests : ModelJsonTests where T : IStreamModel, IJsonModel
+ {
+ [TestCase("J")]
+ [TestCase("W")]
+ public void RoundTripWithStreamableModelReaderWriter(string format)
+ => RoundTripTest(format, new ModelReaderWriterStreamableStrategy());
+
+ [TestCase("J")]
+ [TestCase("W")]
+ public void RoundTripWithStreamableModelReaderWriterNonGeneric(string format)
+ => RoundTripTest(format, new ModelReaderWriterStreamableNonGenericStrategy());
+
+ [TestCase("J")]
+ [TestCase("W")]
+ public void RoundTripWithStreamableModelInterface(string format)
+ => RoundTripTest(format, new ModelInterfaceStreamableStrategy());
+
+ [TestCase("J")]
+ [TestCase("W")]
+ public void RoundTripWithStreamableModelInterfaceNonGeneric(string format)
+ => RoundTripTest(format, new ModelInterfaceAsObjectStreamableStrategy());
+
+ [TestCase("J")]
+ [TestCase("W")]
+ public void RoundTripWithJsonStreamableInterfaceOfT(string format)
+ => RoundTripTest(format, new JsonStreamableInterfaceStrategy());
+
+ [TestCase("J")]
+ [TestCase("W")]
+ public void RoundTripWithJsonStreamableInterfaceNonGeneric(string format)
+ => RoundTripTest(format, new JsonStreamableInterfaceAsObjectStrategy());
+
+ [TestCase("J")]
+ [TestCase("W")]
+ public void RoundTripWithJsonStreamableInterfaceUtf8Reader(string format)
+ => RoundTripTest(format, new JsonStreamableInterfaceUtf8ReaderStrategy());
+
+ [TestCase("J")]
+ [TestCase("W")]
+ public void RoundTripWithJsonStreamableInterfaceUtf8ReaderNonGeneric(string format)
+ => RoundTripTest(format, new JsonStreamableInterfaceUtf8ReaderAsObjectStrategy());
+
+ [TestCase("J")]
+ [TestCase("W")]
+ public void RoundTripWithModelReaderWriterStreamable_WithContext(string format)
+ => RoundTripTest(format, new ModelReaderWriterStreamableStrategy_WithContext(Context));
+
+ [TestCase("J")]
+ [TestCase("W")]
+ public void RoundTripWithModelReaderWriterStreamableNonGeneric_WithContext(string format)
+ => RoundTripTest(format, new ModelReaderWriterStreamableNonGenericStrategy_WithContext(Context));
+ }
+}
diff --git a/sdk/core/System.ClientModel/tests/ModelReaderWriterTests/MrwModelTests.cs b/sdk/core/System.ClientModel/tests/ModelReaderWriterTests/MrwModelTests.cs
index 79263e450c78..a91f983c477f 100644
--- a/sdk/core/System.ClientModel/tests/ModelReaderWriterTests/MrwModelTests.cs
+++ b/sdk/core/System.ClientModel/tests/ModelReaderWriterTests/MrwModelTests.cs
@@ -58,7 +58,22 @@ protected override void RoundTripTest(string format, RoundTripStrategy strate
T model = (T)strategy.Read(serviceResponse, Instance, options);
VerifyModel(model, format);
- var data = strategy.Write(model, options);
+ BinaryData data;
+ if (strategy.SupportsStreaming)
+ {
+ using MemoryStream stream = new MemoryStream();
+ strategy.Write(stream, model, options);
+ if (stream.CanSeek)
+ {
+ stream.Position = 0;
+ }
+ data = BinaryData.FromStream(stream);
+ }
+ else
+ {
+ data = strategy.Write(model, options);
+ }
+
string roundTrip = data.ToString();
Assert.That(roundTrip, Is.EqualTo(expectedSerializedString));
diff --git a/sdk/core/System.ClientModel/tests/ModelReaderWriterTests/RoundTripStrategy.cs b/sdk/core/System.ClientModel/tests/ModelReaderWriterTests/RoundTripStrategy.cs
index bc39859c5de6..aa3851c03266 100644
--- a/sdk/core/System.ClientModel/tests/ModelReaderWriterTests/RoundTripStrategy.cs
+++ b/sdk/core/System.ClientModel/tests/ModelReaderWriterTests/RoundTripStrategy.cs
@@ -20,15 +20,16 @@ public RoundTripStrategy(ModelReaderWriterContext? context)
public abstract object Read(string payload, object model, ModelReaderWriterOptions options);
public abstract BinaryData Write(T model, ModelReaderWriterOptions options);
+ public virtual void Write(Stream stream, T model, ModelReaderWriterOptions options)
+ => throw new NotImplementedException();
public abstract bool IsExplicitJsonWrite { get; }
public abstract bool IsExplicitJsonRead { get; }
+ public abstract bool SupportsStreaming { get; }
protected BinaryData WriteWithJsonInterface(IJsonModel model, ModelReaderWriterOptions options)
{
using MemoryStream stream = new MemoryStream();
- using Utf8JsonWriter writer = new Utf8JsonWriter(stream);
- model.Write(writer, options);
- writer.Flush();
+ WriteWithJsonInterface(model, stream, options);
if (stream.Position > int.MaxValue)
{
return BinaryData.FromStream(stream);
@@ -38,6 +39,13 @@ protected BinaryData WriteWithJsonInterface(IJsonModel model, ModelReaderW
return new BinaryData(stream.GetBuffer().AsMemory(0, (int)stream.Position));
}
}
+
+ protected void WriteWithJsonInterface(IJsonModel model, Stream stream, ModelReaderWriterOptions options)
+ {
+ using Utf8JsonWriter writer = new Utf8JsonWriter(stream);
+ model.Write(writer, options);
+ writer.Flush();
+ }
}
public class ModelReaderWriterStrategy_WithContext : RoundTripStrategy
@@ -48,6 +56,7 @@ public ModelReaderWriterStrategy_WithContext(ModelReaderWriterContext context) :
public override bool IsExplicitJsonWrite => false;
public override bool IsExplicitJsonRead => false;
+ public override bool SupportsStreaming => false;
public override BinaryData Write(T model, ModelReaderWriterOptions options)
{
@@ -67,6 +76,7 @@ public ModelReaderWriterStrategy() : base(null)
public override bool IsExplicitJsonWrite => false;
public override bool IsExplicitJsonRead => false;
+ public override bool SupportsStreaming => false;
public override BinaryData Write(T model, ModelReaderWriterOptions options)
{
@@ -86,6 +96,7 @@ public ModelReaderWriterNonGenericStrategy_WithContext(ModelReaderWriterContext
public override bool IsExplicitJsonWrite => false;
public override bool IsExplicitJsonRead => false;
+ public override bool SupportsStreaming => false;
public override BinaryData Write(T model, ModelReaderWriterOptions options)
{
@@ -106,6 +117,7 @@ public ModelReaderWriterNonGenericStrategy() : base(null)
public override bool IsExplicitJsonWrite => false;
public override bool IsExplicitJsonRead => false;
+ public override bool SupportsStreaming => false;
public override BinaryData Write(T model, ModelReaderWriterOptions options)
{
@@ -126,6 +138,7 @@ public ModelInterfaceStrategy() : base(null)
public override bool IsExplicitJsonWrite => false;
public override bool IsExplicitJsonRead => false;
+ public override bool SupportsStreaming => false;
public override BinaryData Write(T model, ModelReaderWriterOptions options)
{
@@ -146,6 +159,7 @@ public ModelInterfaceAsObjectStrategy() : base(null)
public override bool IsExplicitJsonWrite => false;
public override bool IsExplicitJsonRead => false;
+ public override bool SupportsStreaming => false;
public override BinaryData Write(T model, ModelReaderWriterOptions options)
{
@@ -166,6 +180,7 @@ public JsonInterfaceStrategy() : base(null)
public override bool IsExplicitJsonWrite => true;
public override bool IsExplicitJsonRead => false;
+ public override bool SupportsStreaming => false;
public override BinaryData Write(T model, ModelReaderWriterOptions options)
{
@@ -186,6 +201,7 @@ public JsonInterfaceAsObjectStrategy() : base(null)
public override bool IsExplicitJsonWrite => true;
public override bool IsExplicitJsonRead => false;
+ public override bool SupportsStreaming => false;
public override BinaryData Write(T model, ModelReaderWriterOptions options)
{
@@ -206,6 +222,7 @@ public JsonInterfaceUtf8ReaderStrategy() : base(null)
public override bool IsExplicitJsonWrite => true;
public override bool IsExplicitJsonRead => true;
+ public override bool SupportsStreaming => false;
public override BinaryData Write(T model, ModelReaderWriterOptions options)
{
@@ -227,6 +244,7 @@ public JsonInterfaceUtf8ReaderAsObjectStrategy() : base(null)
public override bool IsExplicitJsonWrite => true;
public override bool IsExplicitJsonRead => true;
+ public override bool SupportsStreaming => false;
public override BinaryData Write(T model, ModelReaderWriterOptions options)
{
@@ -239,5 +257,113 @@ public override object Read(string payload, object model, ModelReaderWriterOptio
return ((IJsonModel)model).Create(ref reader, options);
}
}
+
+ public class ModelReaderWriterStreamableStrategy_WithContext : ModelReaderWriterStrategy_WithContext
+ {
+ public ModelReaderWriterStreamableStrategy_WithContext(ModelReaderWriterContext context) : base(context)
+ {
+ }
+
+ public override bool SupportsStreaming => true;
+
+ public override void Write(Stream stream, T model, ModelReaderWriterOptions options)
+ {
+ ModelReaderWriter.Write(model, stream, _context!, options);
+ }
+ }
+
+ public class ModelReaderWriterStreamableStrategy : ModelReaderWriterStrategy where T : IStreamModel
+ {
+ public override bool SupportsStreaming => true;
+
+ public override void Write(Stream stream, T model, ModelReaderWriterOptions options)
+ {
+ ModelReaderWriter.Write(model, stream, options);
+ }
+ }
+
+ public class ModelReaderWriterStreamableNonGenericStrategy_WithContext : ModelReaderWriterNonGenericStrategy_WithContext
+ {
+ public ModelReaderWriterStreamableNonGenericStrategy_WithContext(ModelReaderWriterContext context) : base(context)
+ {
+ }
+
+ public override bool SupportsStreaming => true;
+
+ public override void Write(Stream stream, T model, ModelReaderWriterOptions options)
+ {
+ ModelReaderWriter.Write((object)model!, stream, _context!, options);
+ }
+ }
+
+ public class ModelReaderWriterStreamableNonGenericStrategy : ModelReaderWriterNonGenericStrategy where T : IStreamModel
+ {
+ public override bool SupportsStreaming => true;
+
+ public override void Write(Stream stream, T model, ModelReaderWriterOptions options)
+ {
+ ModelReaderWriter.Write((object)model, stream, options);
+ }
+ }
+
+ public class ModelInterfaceStreamableStrategy : ModelInterfaceStrategy where T : IStreamModel
+ {
+ public override bool SupportsStreaming => true;
+
+ public override void Write(Stream stream, T model, ModelReaderWriterOptions options)
+ {
+ model.Write(stream, options);
+ }
+ }
+
+ public class ModelInterfaceAsObjectStreamableStrategy : ModelInterfaceAsObjectStrategy where T : IStreamModel
+ {
+ public override bool SupportsStreaming => true;
+
+ public override void Write(Stream stream, T model, ModelReaderWriterOptions options)
+ {
+ ((IStreamModel)model).Write(stream, options);
+ }
+ }
+
+ public class JsonStreamableInterfaceStrategy : JsonInterfaceStrategy where T : IStreamModel, IJsonModel
+ {
+ public override bool SupportsStreaming => true;
+
+ public override void Write(Stream stream, T model, ModelReaderWriterOptions options)
+ {
+ model.Write(stream, options);
+ }
+ }
+
+ public class JsonStreamableInterfaceAsObjectStrategy : JsonInterfaceAsObjectStrategy where T : IStreamModel, IJsonModel
+ {
+ public override bool SupportsStreaming => true;
+
+ public override void Write(Stream stream, T model, ModelReaderWriterOptions options)
+ {
+ WriteWithJsonInterface((IJsonModel)model, stream, options);
+ }
+ }
+
+ public class JsonStreamableInterfaceUtf8ReaderStrategy : JsonInterfaceUtf8ReaderStrategy where T : IStreamModel, IJsonModel
+ {
+ public override bool SupportsStreaming => true;
+
+ public override void Write(Stream stream, T model, ModelReaderWriterOptions options)
+ {
+ model.Write(stream, options);
+ }
+ }
+
+ public class JsonStreamableInterfaceUtf8ReaderAsObjectStrategy : JsonInterfaceUtf8ReaderAsObjectStrategy where T : IStreamModel, IJsonModel
+ {
+ public override bool SupportsStreaming => true;
+
+ public override void Write(Stream stream, T model, ModelReaderWriterOptions options)
+ {
+ WriteWithJsonInterface((IJsonModel)model, stream, options);
+ }
+ }
}
#pragma warning restore SA1402 // File may only contain a single type
diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Models/ModelWithXmlAndJson.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Models/ModelWithXmlAndJson.cs
new file mode 100644
index 000000000000..cc0a3efd3638
--- /dev/null
+++ b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Models/ModelWithXmlAndJson.cs
@@ -0,0 +1,298 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System.ClientModel.Primitives;
+using System.Collections.Generic;
+using System.IO;
+using System.Text.Json;
+using System.Xml;
+using System.Xml.Linq;
+using System.Xml.Serialization;
+using ClientModel.Tests.ClientShared;
+using ClientModel.Tests.Collections;
+
+namespace System.ClientModel.Tests.Client.ModelReaderWriterTests.Models
+{
+ [XmlRoot("Tag")]
+ public class ModelWithXmlAndJson : IStreamModel, IJsonModel
+ {
+ private protected readonly IDictionary _rawData;
+ internal ModelWithXmlAndJson()
+ {
+ _rawData = new Dictionary();
+ }
+
+ internal ModelWithXmlAndJson(string? key, string? value, string? readonlyProperty, IDictionary additionalBinaryDataProperties)
+ {
+ Argument.AssertNotNull(key, nameof(key));
+ Argument.AssertNotNull(value, nameof(value));
+
+ Key = key;
+ Value = value;
+ ReadOnlyProperty = readonlyProperty;
+ _rawData = additionalBinaryDataProperties;
+ }
+
+ /// Initializes a new instance of ModelXml for testing.
+ ///
+ ///
+ /// or is null.
+ public ModelWithXmlAndJson(string? key, string? value, string? readonlyProperty)
+ {
+ Argument.AssertNotNull(key, nameof(key));
+ Argument.AssertNotNull(value, nameof(value));
+
+ Key = key;
+ Value = value;
+ ReadOnlyProperty = readonlyProperty;
+ _rawData = new Dictionary();
+ }
+
+ /// Gets or sets the key.
+ [XmlElement("Key")]
+ public string? Key { get; set; }
+ /// Gets or sets the value.
+ [XmlElement("Value")]
+ public string? Value { get; set; }
+ /// Gets or sets the value.
+ [XmlElement("ReadOnlyProperty")]
+ public string? ReadOnlyProperty { get; }
+
+ void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options)
+ {
+ writer.WriteStartObject();
+ JsonModelWriteCore(writer, options);
+ writer.WriteEndObject();
+ }
+
+ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWriterOptions options)
+ {
+ ModelReaderWriterHelper.ValidateFormat(this, options.Format);
+ string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format;
+ if (options.Format != "J")
+ {
+ throw new InvalidOperationException($"Must use 'J' format when calling the {nameof(IJsonModel)} interface");
+ }
+
+ writer.WritePropertyName("key"u8);
+ writer.WriteStringValue(Key);
+ writer.WritePropertyName("value"u8);
+ writer.WriteStringValue(Value);
+ writer.WritePropertyName("readOnlyProperty"u8);
+ writer.WriteStringValue(ReadOnlyProperty);
+ if (options.Format != "W" && _rawData != null)
+ {
+ foreach (var item in _rawData)
+ {
+ writer.WritePropertyName(item.Key);
+#if NET6_0_OR_GREATER
+ writer.WriteRawValue(item.Value);
+#else
+ using (JsonDocument document = JsonDocument.Parse(item.Value))
+ {
+ JsonSerializer.Serialize(writer, document.RootElement);
+ }
+#endif
+ }
+ }
+ }
+
+ ModelWithXmlAndJson IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options)
+ => JsonModelCreateCore(ref reader, options);
+
+ protected virtual ModelWithXmlAndJson JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options)
+ {
+ ModelReaderWriterHelper.ValidateFormat(this, options.Format);
+ string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format;
+ if (options.Format != "J")
+ {
+ throw new InvalidOperationException($"Must use 'J' format when calling the {nameof(IJsonModel)} interface");
+ }
+
+ using JsonDocument document = JsonDocument.ParseValue(ref reader);
+ return DeserializeModelXmlJson(document.RootElement, options);
+ }
+
+ BinaryData IPersistableModel.Write(ModelReaderWriterOptions options)
+ => PersistableModelWriteCore(options);
+
+ protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions options)
+ {
+ if (TryWriteModel(options, out BinaryData? data) && data != null)
+ {
+ return data;
+ }
+
+ throw new InvalidOperationException($"Unable to write the model {nameof(ModelWithXmlAndJson)} using '{options.Format}' format.");
+ }
+
+ protected virtual void PersistableModelWriteCore(Stream stream, ModelReaderWriterOptions options)
+ {
+ if (TryWriteModel(options, out _, stream))
+ {
+ return;
+ }
+
+ throw new InvalidOperationException($"Unable to write the model {nameof(ModelWithXmlAndJson)} using '{options.Format}' format.");
+ }
+
+ private bool TryWriteModel(ModelReaderWriterOptions options, out BinaryData? data, Stream? stream = null)
+ {
+ ModelReaderWriterHelper.ValidateFormat(this, options.Format);
+ string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format;
+
+ data = null;
+ switch (format)
+ {
+ case "J":
+ {
+ if (stream != null)
+ {
+ ModelReaderWriter.Write(this, stream, options);
+ return true;
+ }
+ else
+ {
+ data = ModelReaderWriter.Write(this, options);
+ return true;
+ }
+ }
+ case "X":
+ {
+ if (stream != null)
+ {
+ using XmlWriter writer = XmlWriter.Create(stream);
+ SerializeAsXml(writer, options, null);
+ writer.Flush();
+ return true;
+ }
+ else
+ {
+ using MemoryStream memoryStream = new MemoryStream();
+ using XmlWriter writer = XmlWriter.Create(memoryStream);
+ SerializeAsXml(writer, options, null);
+ writer.Flush();
+ if (memoryStream.Position > int.MaxValue)
+ {
+ data = BinaryData.FromStream(memoryStream);
+ }
+ else
+ {
+ data = new BinaryData(memoryStream.GetBuffer().AsMemory(0, (int)memoryStream.Position));
+ }
+
+ return true;
+ }
+ }
+ default:
+ throw new FormatException($"The model {nameof(ModelWithXmlAndJson)} does not support writing '{options.Format}' format.");
+ }
+ }
+
+ ModelWithXmlAndJson IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options)
+ => PersistableModelCreateCore(data, options);
+
+ protected virtual ModelWithXmlAndJson PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options)
+ {
+ string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format;
+ switch (format)
+ {
+ case "J":
+ using (JsonDocument document = JsonDocument.Parse(data))
+ {
+ return DeserializeModelXmlJson(document.RootElement, options);
+ }
+ case "X":
+ return DeserializeModelXmlJson(XElement.Load(data.ToStream()), options);
+ default:
+ throw new FormatException($"The model {nameof(ModelWithXmlAndJson)} does not support reading '{options.Format}' format.");
+ }
+ }
+
+ void IStreamModel.Write(Stream stream, ModelReaderWriterOptions options)
+ => PersistableModelWriteCore(stream, options);
+ string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "X";
+
+ private void SerializeAsXml(XmlWriter writer, ModelReaderWriterOptions options, string? nameHint)
+ {
+ writer.WriteStartElement(nameHint ?? "Tag");
+ writer.WriteStartElement("Key");
+ writer.WriteValue(Key);
+ writer.WriteEndElement();
+ writer.WriteStartElement("Value");
+ writer.WriteValue(Value);
+ writer.WriteEndElement();
+ writer.WriteStartElement("ReadOnlyProperty");
+ writer.WriteValue(ReadOnlyProperty);
+ writer.WriteEndElement();
+ if (options.Format != "W" && _rawData != null)
+ {
+ foreach (var item in _rawData)
+ {
+ writer.WriteStartElement(item.Key);
+ writer.WriteValue(item.Value);
+ writer.WriteEndElement();
+ }
+ }
+ writer.WriteEndElement();
+ }
+
+ internal static ModelWithXmlAndJson DeserializeModelXmlJson(XElement element, ModelReaderWriterOptions? options = default)
+ {
+ options ??= ModelReaderWriterHelper.WireOptions;
+
+ string? key = default;
+ string? value = default;
+ string? readonlyProperty = default;
+ IDictionary additionalBinaryDataProperties = new Dictionary();
+
+ if (element.Element("Key") is XElement keyElement)
+ {
+ key = (string)keyElement;
+ }
+ if (element.Element("Value") is XElement valueElement)
+ {
+ value = (string)valueElement;
+ }
+ if (element.Element("ReadOnlyProperty") is XElement readonlyPropertyElement)
+ {
+ readonlyProperty = (string)readonlyPropertyElement;
+ }
+
+ return new ModelWithXmlAndJson(key, value, readonlyProperty, additionalBinaryDataProperties);
+ }
+
+ internal static ModelWithXmlAndJson DeserializeModelXmlJson(JsonElement element, ModelReaderWriterOptions? options = default)
+ {
+ options ??= ModelReaderWriterHelper.WireOptions;
+
+ string? key = default;
+ string? value = default;
+ string? readOnlyProperty = default;
+ IDictionary additionalBinaryDataProperties = new Dictionary();
+ foreach (var property in element.EnumerateObject())
+ {
+ if (property.NameEquals("key"u8))
+ {
+ key = property.Value.GetString();
+ continue;
+ }
+ if (property.NameEquals("value"u8))
+ {
+ value = property.Value.GetString();
+ continue;
+ }
+ if (property.NameEquals("readOnlyProperty"u8))
+ {
+ readOnlyProperty = property.Value.GetString();
+ continue;
+ }
+ if (options.Format != "W")
+ {
+ additionalBinaryDataProperties.Add(property.Name, BinaryData.FromString(property.Value.GetRawText()));
+ }
+ }
+ return new ModelWithXmlAndJson(key, value, readOnlyProperty, additionalBinaryDataProperties);
+ }
+ }
+}
diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/TestClientModelReaderWriterContext.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/TestClientModelReaderWriterContext.cs
index 7f1bb9de491f..4c3a8f56e7bc 100644
--- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/TestClientModelReaderWriterContext.cs
+++ b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/TestClientModelReaderWriterContext.cs
@@ -5,7 +5,6 @@
using System.ClientModel.Tests.Client.ModelReaderWriterTests.Models;
using System.ClientModel.Tests.Client.Models.ResourceManager.Compute;
using System.ClientModel.Tests.Client.Models.ResourceManager.Resources;
-using System.Collections.Generic;
namespace System.ClientModel.Tests.ModelReaderWriterTests
{
@@ -19,6 +18,7 @@ public class TestClientModelReaderWriterContext : ModelReaderWriterContext
private ResourceProviderData_Info? _resourceProviderData_Info;
private UnknownBaseModel_Info? _unknownBaseModel_Info;
private ModelY_Info? _modelY_Info;
+ private ModelWithXmlAndJson_Info? _modelWithXmlAndJson_Info;
public override ModelInfo? GetModelInfo(Type type)
{
@@ -32,6 +32,7 @@ public class TestClientModelReaderWriterContext : ModelReaderWriterContext
Type t when t == typeof(ResourceProviderData) => _resourceProviderData_Info ??= new(),
Type t when t == typeof(UnknownBaseModel) => _unknownBaseModel_Info ??= new(),
Type t when t == typeof(ModelY) => _modelY_Info ??= new(),
+ Type t when t == typeof(ModelWithXmlAndJson) => _modelWithXmlAndJson_Info ??= new(),
_ => null
};
}
@@ -75,5 +76,10 @@ private class AvailabilitySetData_Info : ModelInfo
{
public override object CreateObject() => new AvailabilitySetData();
}
+
+ private class ModelWithXmlAndJson_Info : ModelInfo
+ {
+ public override object CreateObject() => new ModelWithXmlAndJson();
+ }
}
}
diff --git a/sdk/core/System.ClientModel/tests/client/System.ClientModel.Tests.Client.csproj b/sdk/core/System.ClientModel/tests/client/System.ClientModel.Tests.Client.csproj
index 30f59228282d..73fefacad5cb 100644
--- a/sdk/core/System.ClientModel/tests/client/System.ClientModel.Tests.Client.csproj
+++ b/sdk/core/System.ClientModel/tests/client/System.ClientModel.Tests.Client.csproj
@@ -23,6 +23,9 @@
Always
+
+ Always
+
Always
diff --git a/sdk/core/System.ClientModel/tests/client/TestData/ModelWithXmlAndJson/ModelWithXmlAndJson.json b/sdk/core/System.ClientModel/tests/client/TestData/ModelWithXmlAndJson/ModelWithXmlAndJson.json
new file mode 100644
index 000000000000..3d85aa191165
--- /dev/null
+++ b/sdk/core/System.ClientModel/tests/client/TestData/ModelWithXmlAndJson/ModelWithXmlAndJson.json
@@ -0,0 +1,6 @@
+{
+ "key": "Color",
+ "value": "Red",
+ "readOnlyProperty": "ReadOnly",
+ "extra": "stuff"
+}
diff --git a/sdk/core/System.ClientModel/tests/client/TestData/ModelWithXmlAndJson/ModelWithXmlAndJson.xml b/sdk/core/System.ClientModel/tests/client/TestData/ModelWithXmlAndJson/ModelWithXmlAndJson.xml
new file mode 100644
index 000000000000..1e5973092210
--- /dev/null
+++ b/sdk/core/System.ClientModel/tests/client/TestData/ModelWithXmlAndJson/ModelWithXmlAndJson.xml
@@ -0,0 +1,6 @@
+
+ Color
+ Red
+ ReadOnly
+ Stuff
+
diff --git a/sdk/core/System.ClientModel/tests/client/TestData/ModelWithXmlAndJson/ModelWithXmlAndJsonWireFormat.xml b/sdk/core/System.ClientModel/tests/client/TestData/ModelWithXmlAndJson/ModelWithXmlAndJsonWireFormat.xml
new file mode 100644
index 000000000000..1e5973092210
--- /dev/null
+++ b/sdk/core/System.ClientModel/tests/client/TestData/ModelWithXmlAndJson/ModelWithXmlAndJsonWireFormat.xml
@@ -0,0 +1,6 @@
+
+ Color
+ Red
+ ReadOnly
+ Stuff
+