Skip to content
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

[API Proposal] New Clipboard and DataObject APIs #12362

Open
Tanya-Solyanik opened this issue Oct 22, 2024 · 10 comments
Open

[API Proposal] New Clipboard and DataObject APIs #12362

Tanya-Solyanik opened this issue Oct 22, 2024 · 10 comments
Assignees
Labels
api-approved (4) API was approved in API review, it can be implemented 🚧 work in progress Work that is current in progress
Milestone

Comments

@Tanya-Solyanik
Copy link
Member

Tanya-Solyanik commented Oct 22, 2024

Background and motivation

OLE clipboard supports standard exchange formats and additionally allows users to register custom formats for data exchange. Although standard exchange formats do not use binary format as they are defined by Windows, custom format serialization of objects in .NET has used the BinaryFormatter (in both WinForms and WPF).
WinForms Clipboard falls back to the BinaryFormatter when consuming custom types stored as custom clipboard formats. We propose a set of Clipboard and DataObject APIs that make it easier to follow best practices when deserializing untrusted data. They restrict unbounded BinaryFormatter deserialization to known types and provide an alternative serialization method, JSON, for user types.

API Proposal

We propose the same API changes in WinForms and WPF assemblies. The new API surface will be backed by a shared implementation. The proposed APIs will support our updated Clipboard and Drag/Drop guidance which is:

  • To avoid serialization, use intrinsically handled types, which include primitive types, arrays or lists of primitives, exchange types from System.Drawing.Primitives.dll (PointF, RectangleF, Point, Rectangle, SizeF, Size, Color), and types commonly used in WinForms applications, such as System.Drawing.Bitmap. These types are handled for the user and continue to work without migration steps needed. We are open to adding more intrinsically handled types.
  • .NET types that contain only public properties or fields and avoid inheritance, are the recommended types for data exchange in general, including in the Clipboard scenarios. Such types do not require BinaryFormatter with the proposed APIs, instead they are JSON-serialized. This is the recommended way for new applications to use the Clipboard or Drag/Drop.
  • If this is not feasible to update the user's data transfer type, but the user controls both the reader and the writer, it is recommended to use JSON to format user type as byte[] or string. Adjustment will need to be made on the consuming side to handle receiving a JSON formatted type.
  • If exchange type and the writer can't be updated, the new consumption APIs will allow the user to follow the best practices when deserializing untrusted data. This usage is acceptable during migration off BinaryFormatter and is not secure.
  • Use the obsoleted APIs and opt into the BinaryFormatter only when other approaches are not applicable.

Clipboard

namespace System.Windows.Forms;
and 
namespace System.Windows;

public static partial class Clipboard
{
+   // Saves the data onto the clipboard in the specified format using System.Text.Json. Will throw InvalidOperationException if DataObject is passed as it is ambiguous what user is intending given DataObject cannot be meaningfully JSON serialized.
+   public static void SetDataAsJson<T>(string format, T data) { }

+   [Obsolete("`Clipboard.GetData(string)` method is obsolete. Use `Clipboard.TryGetData<T>` instead.", false, DiagnosticId = "WFDEV005", UrlFormat = "https://aka.ms/winforms-warnings/{0}")
+   [EditorBrowsable(EditorBrowsableState.Never)]
    public static object? GetData(string format) { }
    
+   // Verifies that payload contains type T and then attempts to read or deserialize it. 
+   public static bool TryGetData<T>(string format, out T data) { }
+   // Uses user-provided resolve to match the requested type to the payload content and to rehydrate the payload.
+   public static bool TryGetData<T>(string format, Func<Reflection.Metadata.TypeName, Type> resolver, out T data) { }
}

Managed implementation of OLE's IDataObject definition and is being used in all OLE operations for WinForms/WPF.

DataObject

namespace System.Windows.Forms;
and 
namespace System.Windows;

public partial class DataObject : IDataObject, Runtime.InteropServices.ComTypes.IDataObject
{
+   // Stores the specified data and its associated format in this instance using System.Text.Json. Will throw InvalidOperationException if DataObject is passed as it is ambiguous what user is intending given DataObject cannot be meaningfully JSON serialized.
+   public void SetDataAsJson<T>(T data) { }
+   public void SetDataAsJson<T>(string format, T data) { }
+   public void SetDataAsJson<T>(string format, bool autoConvert, T data) { }

+   [Obsolete("`DataObject.GetData` methods are obsolete. Use the corresponding `DataObject.TryGetData<T>` instead.", false, DiagnosticId = "WFDEV005", UrlFormat = "https://aka.ms/winforms-warnings/{0}")]
+   [EditorBrowsable(EditorBrowsableState.Never)]
    public virtual object? GetData(string format, bool autoConvert) { }

+   [Obsolete("`DataObject.GetData` methods are obsolete. Use the corresponding `DataObject.TryGetData<T>` instead.", false, DiagnosticId = "WFDEV005", UrlFormat = "https://aka.ms/winforms-warnings/{0}")]
+   [EditorBrowsable(EditorBrowsableState.Never)]
    public virtual object? GetData(string format) { }

+   [Obsolete("`DataObject.GetData` methods are obsolete. Use the corresponding `DataObject.TryGetData<T>` instead.", false, DiagnosticId = "WFDEV005", UrlFormat = "https://aka.ms/winforms-warnings/{0}")]
+   [EditorBrowsable(EditorBrowsableState.Never)]
    public virtual object? GetData(Type format) { }

+   public virtual bool TryGetData<T>(out T data) { }
+   public virtual bool TryGetData<T>(string format, out T data) { }
+   public virtual bool TryGetData<T>(string format, bool autoConvert, out T data) { }
+   public virtual bool TryGetData<T>(string format, Func<Reflection.Metadata.TypeName, Type> resolver, bool autoConvert, out T data) { }
}

IDataObject

namespace System.Windows.Forms;
and 
namespace System.Windows;

public partial interface IDataObject
{
    object? GetData(string format, bool autoConvert);
    object? GetData(string format);
    object? GetData(Type format);

+   // Default implementations of these interface methods are using the existing GetData methods.
+   bool TryGetData<T>(out T data);
+   bool TryGetData<T>(string format, out T data);
+   bool TryGetData<T>(string format, bool autoConvert, out T data);
+   bool TryGetData<T>(string format, Func<Reflection.Metadata.TypeName, Type> resolver, bool autoConvert, out T data);
}

Control

namespace System.Windows.Forms;
public class partial class Control
{
+   // Begins drag operation, storing the drag data using System.Text.Json. Will throw InvalidOperationException if DataObject is passed to have a better error reporting in a common scenario.
+   public DragDropEffects DoDragDropAsJson<T>(T data, DragDropEffects allowedEffects, Bitmap? dragImage, Point cursorOffset, bool useDefaultDragImage)
+   public DragDropEffects DoDragDropAsJson<T>(T data, DragDropEffects allowedEffects)
}

DragDrop

namespace System.Windows;
public static class DragDrop
{
+    // Begins drag operation, storing the drag data using System.Text.Json. Will throw InvalidOperationException if DataObject is passed to have a better error reporting in a common scenario.
+    public static DragDropEffects DoDragDropAsJson<T>(DependencyObject dragSource, T data, DragDropEffects allowedEffects)
}

ClipboardProxy

// VisualBasic wrapper for WinForms Clipboard.
namespace Microsoft.VisualBasic.MyServices
public partial class ClipboardProxy
{
+   public void SetDataAsJson<T>(T data) { }
+   public void SetDataAsJson<T>(string format, T data) { }

+   [Obsolete("`ClipboardProxy.GetData(As String)` method is obsolete. Use `ClipboardProxy.TryGetData(Of T)` instead.", false, DiagnosticId = "WFDEV005", UrlFormat = "https://aka.ms/winforms-warnings/{0}")
+   [EditorBrowsable(EditorBrowsableState.Never)]
    public object GetData(string format) { } 

+   public bool TryGetData<T>(string format, out T data) { }
+   public bool TryGetData<T>(string format, System.Func<System.Reflection.Metadata.TypeName, System.Type> resolver, out T data) { }
}

New configuration switch:
ClipboardDragDrop.EnableUnsafeBinaryFormatterSerialization - controls whether BinaryFormatter is enabled as a fallback for Clipboard and Drag/drop scenarios. By default, it is false.

API Usage

Clipboard API Examples

Before introduction of new APIs
  1. Users would need to enable BinaryFormatter to serialize/deserialize their types.
[Serializable]
public class WeatherForecastPOCO
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string? Summary { get; set; }
}

public void SetGetClipboardData()
{
    WeatherForecastPOCO weatherForecast = new()
    {
        Date = DateTime.Parse("2019-08-01"),
        TemperatureCelsius = 25,
        Summary = "Hot",
    };
    
    // BinaryFormatter must be enabled for this to be successful.
    Clipboard.SetData("myCustomFormat", weatherForecast);
#pragma warning disable WFDEV005 // Type or member is obsolete
    if (Clipboard.GetData("myCustomFormat") is WeatherForecast forecast)
#pragma warning restore WFDEV005
    {
        // Do things with forecast.
    }
}
  1. Users could use manual JSON serialization to avoid opting into the BinaryFormatter with the old APIs.
public class WeatherForecastPOCO
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string? Summary { get; set; }
}
 
public void SetGetClipboardDataJson()
{
    WeatherForecastPOCO weatherForecast = new()
    {
        Date = DateTime.Parse("2019-08-01"),
        TemperatureCelsius = 25,
        Summary = "Hot",
    };
    byte[] serialized = JsonSerializer.SerializeToUtf8Bytes(weatherForecast);
    Clipboard.SetData("myCustomFormat", serialized);
#pragma warning disable WFDEV005 // Type or member is obsolete
    if (Clipboard.GetData("myCustomFormat") is byte[] byteData)
#pragma warning restore WFDEV005
    {
        if (JsonSerializer.Deserialize(byteData, typeof(WeatherForecast)) is WeatherForecast forecast)
        {
            // Do things with forecast.
        }
    }
}
After introduction of new APIs
  1. No need for users to manually JSON serialize their data and can specify directly in TryGetData what type they are expecting. This code does not require BinaryFormatter.
public class WeatherForecastPOCO
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string? Summary { get; set; }
}
 
public void SetGetClipboardData()
{
    WeatherForecastPOCO weatherForecast = new()
    {
        Date = DateTime.Parse("2019-08-01"),
        TemperatureCelsius = 25,
        Summary = "Hot",
    };
    Clipboard.SetDataAsJson("myCustomFormat", weatherForecast);
    if (Clipboard.TryGetData("myCustomFormat", WeatherForecast? forecast))
    {
        // Do things with forecast.
    }
}
  1. When exchange types can't be replaced by POCO types, application can restrict BinaryFormatter deserialization by providing a set of allowed types.
// Writer side
using Font value = new("Microsoft Sans Serif", emSize: 10);
Clipboard.SetData("font", value);

// Consumer side
if (Clipboard.TryGetData("font", FontResolver, out Font data))
{
   // Do things with data
}

private static Type FontResolver(TypeName typeName)
{
    (string name, Type type)[] allowedTypes =
    [
        (typeof(FontStyle).FullName!, typeof(FontStyle)),
        (typeof(FontFamily).FullName!, typeof(FontFamily)),
        (typeof(GraphicsUnit).FullName!, typeof(GraphicsUnit)),
    ];

    string fullName = typeName.FullName;
    foreach (var (name, type) in allowedTypes)
    {
        // Namespace-qualified type name.
        if (name == fullName)
        {
            return type;
        }
    }

    throw new NotSupportedException($"Can't resolve {fullName}");
}

Drag/Drop Usage Example

No BinaryFormatter is required.

private Form _form1 = new();
private Control _beingDrag = new();
_beingDrag.MouseDown += beingDrag_MouseDown;
_form1.DragDrop += Form1_DragDrop;

void beingDrag_MouseDown(object sender, MouseEventArgs e)
{
    WeatherForecast weatherForecast = new WeatherForecast
    {
        Date = DateTimeOffset.Now,
        TemperatureCelsius = 25,
        Summary = "Hot"
    };

    _beingDrag.DoDragDropAsJson(weatherForecast, DragDropEffects.Copy);    
}
 
void Form1_DragDrop(object sender, DragEventArgs e)
{
    DataObject dataObject = e.Data;
    if (dataObject.TryGetData(typeof(WeatherForecast), out WeatherForecast? deserialized))
    {
        // Do things with deserialized data.
    }
}

Alternative Designs

  1. Replace a Func<TypeName, Type> with a resolver interface that can be reused in ResX and ActiveX scenarios and potentially in other Runtime scenarios, move the IResolver interface into System.Runtime.Serialization namespace. That would be a replacement for the resolver Func<TypeName, Type>.
bool TryGetData<T>(string format, IResolver resolver, bool autoConvert, out T data);

namespace System;
public interface IResolver
{
    Type TypeFromTypeName(TypeName typeName);
}
  1. Should we make the GetData methods in the managed IDataObject interface obsolete?
    These methods are implemented by the user, we don’t know if they are vulnerable or not. But they propagate a bad pattern by returning an unconstrained type (System.Object). There might be too many false positives if we obsolete them. The recommended way is for users to derive from the managed DataObject, not to implement the interface from scratch, and that scenario is covered.

  2. Should we make the SetData overloads obsolete?
    No, this scenario is not vulnerable, it propagates a bad pattern only when serializing more complex types. We will address this with an analyzer.

  3. Why take T for SetDataAsJson / DoDragDropAsJson APIs instead of object?
    We need to save the original type of the data that is being passed in so that we can rehydrate the type when the user asks for it, meaning that we will need to rely on Object.GetType to get the type of the passed in data in SetDataAsJson / DoDragDropAsJson APIs. This is not trim friendly because it might use reflection.

  4. Should an optional parameter to SetData and DoDragDrop APIs to indicate serializing with Json is desired instead of introducing a new API signature?
    This is an option, but it is again not trim friendly as we would need to rely on Object.GetType.

  5. Should we make the managed IDataObject interface obsolete?
    The shape of the managed interface matches that of the OLE interface, and the whole ecosystem depends on it, so any replacement would be very similar. We assume that most use cases override our DataObject instead of implementing the IDataObject from scratch.

  6. Should the consumption side APIs be T GetData<T>(..) or bool TryGetData<T>(… out T data).
    No strong preference, an API that returns a Boolean seems to be more convenient for the common use patterns observed in GitHUB.

  7. Naming for the configuration switch should indicate that it’s applicable to WPF as well when we share code with WPF.
    After the code is merged in the WPF repo, System.Windows.Forms.Clipboard and System.Windows.Clipboard
    will share the implementation. We want the configuration switch name to indicate that it's applicable to both. We could use the common namespace name portion:
    System.Windows.ClipboardDragDrop.EnableUnsafeBinaryFormatterSerialization
    Or
    System.ClipboardDragDrop.EnableUnsafeBinaryFormatterSerialization
    Or no namespace name
    ClipboardDragDrop.EnableUnsafeBinaryFormatterSerialization

Risks

• Users would have to implement the assembly loader and type resolver that works with TryGetData() to support types other than "T". If that resolver calls Type.GetType() they might lose control over assembly loading. Proposed APIs that do not accept the type resolver parameter, don't have this issue. When doing type matching, we rely on NrbfDecoder and type name matching API to be safe (threat model).
Sample user-provided resolver code:

internal static Type MyResolver(TypeName typeName)
{
    Type[] allowedTypes =
    [
         typeof(MyClass),
         typeof(MyClass1)
    ];

    foreach (Type type in allowedTypes)
    {
        // Namespace-qualified type name.
        if (typeName.FullName == type.FullName!)
        {
            return type;
        }
    }

     // Do not call Type.GetType(typeName.AssemblyQualifiedName), throw for the unexpected types instead.
    throw new NotSupportedException();
}

• When deserializing objects that have been JSON serialized, this carries the same risks as any JSON data going through System.Text.Json, so it is possible to misuse SetDataAsJson to do bad things during clipboard and drag/drop operation (System.Text.Json threat model). As with any data, users need to trust the JSON data they are trying to grab.

Risk mitigation

We are adding a new configuration switch that would block the fallback into BinaryFormatter use in Clipboard and DragDrop scenarios, the proposed APIs allow users to use JSON format instead. We will encourage users to use only primitive exchange types or POCOc.

Will this feature affect UI controls?

No

@Tanya-Solyanik Tanya-Solyanik added the 🚧 work in progress Work that is current in progress label Oct 22, 2024
@Tanya-Solyanik Tanya-Solyanik added this to the .NET 10.0 milestone Oct 22, 2024
@lonitra lonitra added the api-suggestion (1) Early API idea and discussion, it is NOT ready for implementation label Oct 22, 2024
@Tanya-Solyanik
Copy link
Member Author

Tanya-Solyanik commented Oct 23, 2024

@Tanya-Solyanik Tanya-Solyanik added the api-ready-for-review (2) API is ready for formal API review; applied by the issue owner label Oct 28, 2024
@h3xds1nz
Copy link

@miloush Look, new APIs!

@miloush
Copy link

miloush commented Oct 29, 2024

Thanks @h3xds1nz, would have missed this.

The API changes pertaining to WPF should be posted separately in the WPF repo.

In general I don't have anything against adding new methods, although I don't understand why they wouldn't have the index parameter. If people are happy to do breaking changes to the IDataObject interface, it should include methods taking index for both old and new methods.

Is it suggested that the shared implementation would support WPF primitive types as much as the System.Drawing ones? Because the link to intrinsically handled types does not list any WPF types.

I am not thrilled about obsoleting methods that don't have any equivalent (people can put whole controls in the DataObject now and it works even when not serializable, and you can test for reference equality), but hiding them completely is nefarious.

@bartonjs
Copy link
Member

  • There was discussion around a lot of the JSON-ness, in the end it was felt that the proposed API is sufficient for the initial version.
  • There was discussion around whether WinForms' Clipboard.GetData and WPF's Clipboard.GetData should use the same diagnostic ID, or two different diagnostic IDs. It was decided that using one common ID was better, even if it uses a WinForms prefix in a WPF app.
  • The GetData methods should be marked Obsolete, but not EditorBrowsable.Never
  • This space feels like it has to handle downlevel interop, but that feels lacking.
    • DIMs on IDataObject can't be utilized on .NET Framework (no DIMs, and can't replace the framework type)
    • .NET Framework extracting data written via SetData
    • So rather than adding DIMs on IDataObject, we invented a new interface, ITypedDataObject
  • The GetData methods on IDataObject should not be marked as Obsolete in this version
  • One AppContext switch seems good, the name we came to by committee is Windows.ClipboardDragDrop.EnableUnsafeBinaryFormatterSerialization
namespace System.Windows.Forms;
and 
namespace System.Windows;

public static partial class Clipboard
{
+   public static void SetDataAsJson<T>(string format, T data) { }

+   [Obsolete("`Clipboard.GetData(string)` method is obsolete. Use `Clipboard.TryGetData<T>` instead.", false, DiagnosticId = "WFDEV005", UrlFormat = "https://aka.ms/winforms-warnings/{0}")
    public static object? GetData(string format) { }
    
+   public static bool TryGetData<T>(string format, out T data) { }
+   public static bool TryGetData<T>(string format, Func<Reflection.Metadata.TypeName, Type> resolver, out T data) { }
}

public partial class DataObject : IDataObject, Runtime.InteropServices.ComTypes.IDataObject
+                                 ITypedDataObject
{
+   public void SetDataAsJson<T>(T data) { }
+   public void SetDataAsJson<T>(string format, T data) { }
+   public void SetDataAsJson<T>(string format, bool autoConvert, T data) { }

+   [Obsolete("`DataObject.GetData` methods are obsolete. Use the corresponding `DataObject.TryGetData<T>` instead.", false, DiagnosticId = "WFDEV005", UrlFormat = "https://aka.ms/winforms-warnings/{0}")]
    public virtual object? GetData(string format, bool autoConvert) { }

+   [Obsolete("`DataObject.GetData` methods are obsolete. Use the corresponding `DataObject.TryGetData<T>` instead.", false, DiagnosticId = "WFDEV005", UrlFormat = "https://aka.ms/winforms-warnings/{0}")]
    public virtual object? GetData(string format) { }

+   [Obsolete("`DataObject.GetData` methods are obsolete. Use the corresponding `DataObject.TryGetData<T>` instead.", false, DiagnosticId = "WFDEV005", UrlFormat = "https://aka.ms/winforms-warnings/{0}")]
    public virtual object? GetData(Type format) { }

+   public bool TryGetData<T>(out T data) { }
+   public bool TryGetData<T>(string format, out T data) { }
+   public bool TryGetData<T>(string format, bool autoConvert, out T data) { }
+   public bool TryGetData<T>(string format, Func<Reflection.Metadata.TypeName, Type> resolver, bool autoConvert, out T data) { }
+   protected virtual bool TryGetDataCore<T>(string format, Func<Reflection.Metadata.TypeName, Type> resolver, bool autoConvert, out T data) { }
}

+public interface ITypedDataObject
+{
+   bool TryGetData<T>(out T data);
+   bool TryGetData<T>(string format, out T data);
+   bool TryGetData<T>(string format, bool autoConvert, out T data);
+   bool TryGetData<T>(string format, Func<Reflection.Metadata.TypeName, Type> resolver, bool autoConvert, out T data);
+}

+public sealed class DataObjectExtensions
{
+   public static bool TryGetData<T>(this IDataObject dataObject, out T data);
+   public static bool TryGetData<T>(this IDataObject dataObject, string format, out T data);
+   public static bool TryGetData<T>(this IDataObject dataObject, string format, bool autoConvert, out T data);
+   public static bool TryGetData<T>(this IDataObject dataObject, string format, Func<Reflection.Metadata.TypeName, Type> resolver, bool autoConvert, out T data);
}
namespace System.Windows.Forms;
public class partial class Control
{
+   public DragDropEffects DoDragDropAsJson<T>(T data, DragDropEffects allowedEffects, Bitmap? dragImage, Point cursorOffset, bool useDefaultDragImage)
+   public DragDropEffects DoDragDropAsJson<T>(T data, DragDropEffects allowedEffects)
}
namespace System.Windows;
public static class DragDrop
{
+    public static DragDropEffects DoDragDropAsJson<T>(DependencyObject dragSource, T data, DragDropEffects allowedEffects)
}
// VisualBasic wrapper for WinForms Clipboard.
namespace Microsoft.VisualBasic.MyServices
public partial class ClipboardProxy
{
+   public void SetDataAsJson<T>(T data) { }
+   public void SetDataAsJson<T>(string format, T data) { }

+   [Obsolete("`ClipboardProxy.GetData(As String)` method is obsolete. Use `ClipboardProxy.TryGetData(Of T)` instead.", false, DiagnosticId = "WFDEV005", UrlFormat = "https://aka.ms/winforms-warnings/{0}")
    public object GetData(string format) { } 

+   public bool TryGetData<T>(string format, out T data) { }
+   public bool TryGetData<T>(string format, System.Func<System.Reflection.Metadata.TypeName, System.Type> resolver, out T data) { }
}

@bartonjs bartonjs added api-approved (4) API was approved in API review, it can be implemented and removed api-suggestion (1) Early API idea and discussion, it is NOT ready for implementation api-ready-for-review (2) API is ready for formal API review; applied by the issue owner labels Oct 29, 2024
@miloush
Copy link

miloush commented Oct 29, 2024

For the index parameter, we have an API proposal in WPF affecting data objects: dotnet/wpf#7744

It might be useful to do this as a part of unifying the codebase.

@eerhardt
Copy link
Member

public partial class DataObject : IDataObject, Runtime.InteropServices.ComTypes.IDataObject
ITypedDataObject
{
public void SetDataAsJson(T data) { }

Do we need overloads of these "Json" APIs that take a JsonTypeInfo, so the Json source generator can be used (to support trimming and native AOT)?

@bartonjs
Copy link
Member

Do we need overloads of these "Json" APIs that take a JsonTypeInfo, so the Json source generator can be used (to support trimming and native AOT)?

That's the first bullet in the meeting summary. The answer was "no, not at this time"

@miloush
Copy link

miloush commented Oct 31, 2024

Not to be too daring, but if it used XML, we wouldn't need the extra System.Text.Json dependency, especially if this is planned to be used in .NET Framework for interopability.

In general, the fact that it uses JSON seems to be a rather implementation detail of safety, especially if we don't allow reading and writing the JSON directly. Do I understand the idea correctly that the SetDataAsJson would still store the value as binary if it is one of the types on a safelist? If so, calling it AsJson seems a bit misleading.

@MichaeIDietrich
Copy link

My 2 Cents here. Just out of curiosity, why are the methods called SomethingAsJson, when they do not have any JSON-specific info in their signature? Isn't JSON here something like an implementation detail, like binary formatting is for the existing implementation?

SetDataAsJson feels more like a convenience method that should be provided as an extension method instead of being part of the public interface of the Clipboard class. But I guess that's not an option, since the class is static?

@miloush
Copy link

miloush commented Oct 31, 2024

@MichaeIDietrich one option to do that would be to have a separate JsonClipboard or SafeClipboard or similar that could be an in-place replacement and only deal with the safe types. People have to opt in using the new API anyway, and you get freedom to design the API the way you want.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-approved (4) API was approved in API review, it can be implemented 🚧 work in progress Work that is current in progress
Projects
None yet
Development

No branches or pull requests

7 participants