diff --git a/src/Avalonia.Xaml.Interactions/StorageProvider/Converters/StorageFileToReadStreamConverter.cs b/src/Avalonia.Xaml.Interactions/StorageProvider/Converters/StorageFileToReadStreamConverter.cs
new file mode 100644
index 000000000..a28dd3d09
--- /dev/null
+++ b/src/Avalonia.Xaml.Interactions/StorageProvider/Converters/StorageFileToReadStreamConverter.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Globalization;
+using Avalonia.Data;
+using Avalonia.Data.Converters;
+using Avalonia.Platform.Storage;
+
+namespace Avalonia.Xaml.Interactions.Core;
+
+///
+/// Converts a to a read stream.
+///
+public class StorageFileToReadStreamConverter : IValueConverter
+{
+ ///
+ /// Gets a static instance of .
+ ///
+ public static StorageFileToReadStreamConverter Instance { get; } = new ();
+
+ ///
+ public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ if (value is IStorageFile storageFile)
+ {
+ return storageFile.OpenReadAsync();
+ }
+
+ return BindingOperations.DoNothing;
+ }
+
+ ///
+ public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ return BindingOperations.DoNothing;
+ }
+}
diff --git a/src/Avalonia.Xaml.Interactions/StorageProvider/Converters/StorageFileToWriteStreamConverter.cs b/src/Avalonia.Xaml.Interactions/StorageProvider/Converters/StorageFileToWriteStreamConverter.cs
new file mode 100644
index 000000000..8f91db6bf
--- /dev/null
+++ b/src/Avalonia.Xaml.Interactions/StorageProvider/Converters/StorageFileToWriteStreamConverter.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Globalization;
+using Avalonia.Data;
+using Avalonia.Data.Converters;
+using Avalonia.Platform.Storage;
+
+namespace Avalonia.Xaml.Interactions.Core;
+
+///
+/// Converts a to a path.
+///
+public class StorageFileToWriteStreamConverter : IValueConverter
+{
+ ///
+ /// Gets a static instance of .
+ ///
+ public static StorageFileToWriteStreamConverter Instance { get; } = new ();
+
+ ///
+ public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ if (value is IStorageFile storageFile)
+ {
+ return storageFile.OpenWriteAsync();
+ }
+
+ return BindingOperations.DoNothing;
+ }
+
+ ///
+ public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ return BindingOperations.DoNothing;
+ }
+}
diff --git a/src/Avalonia.Xaml.Interactions/StorageProvider/Converters/StorageItemToPathConverter.cs b/src/Avalonia.Xaml.Interactions/StorageProvider/Converters/StorageItemToPathConverter.cs
new file mode 100644
index 000000000..86f19e014
--- /dev/null
+++ b/src/Avalonia.Xaml.Interactions/StorageProvider/Converters/StorageItemToPathConverter.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Globalization;
+using Avalonia.Data;
+using Avalonia.Data.Converters;
+using Avalonia.Platform.Storage;
+
+namespace Avalonia.Xaml.Interactions.Core;
+
+///
+/// Converts a to a path.
+///
+public class StorageItemToPathConverter : IValueConverter
+{
+ ///
+ /// Gets a static instance of .
+ ///
+ public static StorageItemToPathConverter Instance { get; } = new ();
+
+ ///
+ public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ if (value is IStorageItem storageItem)
+ {
+ return storageItem.Path;
+ }
+
+ return BindingOperations.DoNothing;
+ }
+
+ ///
+ public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ return BindingOperations.DoNothing;
+ }
+}
diff --git a/src/Avalonia.Xaml.Interactions/StorageProvider/Core/PickerActionBase.cs b/src/Avalonia.Xaml.Interactions/StorageProvider/Core/PickerActionBase.cs
new file mode 100644
index 000000000..9912abc17
--- /dev/null
+++ b/src/Avalonia.Xaml.Interactions/StorageProvider/Core/PickerActionBase.cs
@@ -0,0 +1,54 @@
+using Avalonia.Platform.Storage;
+
+namespace Avalonia.Xaml.Interactions.Core;
+
+///
+/// Base class for picker actions.
+///
+public abstract class PickerActionBase : InvokeCommandActionBase
+{
+ ///
+ /// Identifies the avalonia property.
+ ///
+ public static readonly StyledProperty TitleProperty =
+ AvaloniaProperty.Register(nameof(Title));
+
+ ///
+ /// Identifies the avalonia property.
+ ///
+ public static readonly StyledProperty SuggestedStartLocationProperty =
+ AvaloniaProperty.Register(nameof(SuggestedStartLocation));
+
+ ///
+ /// Identifies the avalonia property.
+ ///
+ public static readonly StyledProperty SuggestedFileNameProperty =
+ AvaloniaProperty.Register(nameof(SuggestedFileName));
+
+ ///
+ /// Gets or sets the text that appears in the title bar of a picker. This is an avalonia property.
+ ///
+ public string? Title
+ {
+ get => GetValue(TitleProperty);
+ set => SetValue(TitleProperty, value);
+ }
+
+ ///
+ /// Gets or sets the initial location where the file open picker looks for files to present to the user. This is an avalonia property.
+ ///
+ public IStorageFolder? SuggestedStartLocation
+ {
+ get => GetValue(SuggestedStartLocationProperty);
+ set => SetValue(SuggestedStartLocationProperty, value);
+ }
+
+ ///
+ /// Gets or sets the file name that the file picker suggests to the user. This is an avalonia property.
+ ///
+ public string? SuggestedFileName
+ {
+ get => GetValue(SuggestedFileNameProperty);
+ set => SetValue(SuggestedFileNameProperty, value);
+ }
+}
diff --git a/src/Avalonia.Xaml.Interactions/StorageProvider/OpenFilePickerAction.cs b/src/Avalonia.Xaml.Interactions/StorageProvider/OpenFilePickerAction.cs
new file mode 100644
index 000000000..3aab6f2a3
--- /dev/null
+++ b/src/Avalonia.Xaml.Interactions/StorageProvider/OpenFilePickerAction.cs
@@ -0,0 +1,108 @@
+using System.Threading.Tasks;
+using Avalonia.Controls;
+using Avalonia.Platform.Storage;
+using Avalonia.Threading;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Xaml.Interactions.Core;
+
+///
+/// An action that will open a file picker dialog.
+///
+public class OpenFilePickerAction : PickerActionBase
+{
+ ///
+ /// Identifies the avalonia property.
+ ///
+ public static readonly StyledProperty AllowMultipleProperty =
+ AvaloniaProperty.Register(nameof(AllowMultiple));
+
+ ///
+ /// Identifies the avalonia property.
+ ///
+ public static readonly StyledProperty FileTypeFilterProperty =
+ AvaloniaProperty.Register(nameof(FileTypeFilter));
+
+ ///
+ /// Gets or sets an option indicating whether open picker allows users to select multiple files. This is an avalonia property.
+ ///
+ public bool AllowMultiple
+ {
+ get => GetValue(AllowMultipleProperty);
+ set => SetValue(AllowMultipleProperty, value);
+ }
+
+ ///
+ /// Gets or sets the collection of file types that the file open picker displays. This is an avalonia property.
+ ///
+ public string? FileTypeFilter
+ {
+ get => GetValue(FileTypeFilterProperty);
+ set => SetValue(FileTypeFilterProperty, value);
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public OpenFilePickerAction()
+ {
+ PassEventArgsToCommand = true;
+ }
+
+ ///
+ /// Executes the action.
+ ///
+ /// The that is passed to the action by the behavior. Generally this is or a target object.
+ /// The value of this parameter is determined by the caller.
+ /// True if the command is successfully executed; else false.
+ public override object Execute(object? sender, object? parameter)
+ {
+ if (sender is not Visual visual)
+ {
+ return false;
+ }
+
+ Dispatcher.UIThread.InvokeAsync(async () => await OpenFilePickerAsync(visual));
+
+ return true;
+ }
+
+ private async Task OpenFilePickerAsync(Visual visual)
+ {
+ if (IsEnabled != true || Command is null)
+ {
+ return;
+ }
+
+ var storageProvider = (visual.GetVisualRoot() as TopLevel)?.StorageProvider;
+ if (storageProvider is null)
+ {
+ return;
+ }
+
+ var files = await storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
+ {
+ Title = Title,
+ SuggestedStartLocation = SuggestedStartLocation,
+ SuggestedFileName = SuggestedFileName,
+ AllowMultiple = AllowMultiple,
+ FileTypeFilter = FileTypeFilter is not null
+ ? FileFilterParser.ConvertToFilePickerFileType(FileTypeFilter)
+ : null
+ });
+
+ if (files.Count <= 0)
+ {
+ return;
+ }
+
+ var resolvedParameter = ResolveParameter(files);
+
+ if (!Command.CanExecute(resolvedParameter))
+ {
+ return;
+ }
+
+ Command.Execute(resolvedParameter);
+ }
+}
diff --git a/src/Avalonia.Xaml.Interactions/StorageProvider/OpenFolderPickerAction.cs b/src/Avalonia.Xaml.Interactions/StorageProvider/OpenFolderPickerAction.cs
new file mode 100644
index 000000000..c3a06e940
--- /dev/null
+++ b/src/Avalonia.Xaml.Interactions/StorageProvider/OpenFolderPickerAction.cs
@@ -0,0 +1,90 @@
+using System.Threading.Tasks;
+using Avalonia.Controls;
+using Avalonia.Platform.Storage;
+using Avalonia.Threading;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Xaml.Interactions.Core;
+
+///
+/// An action that will open a folder picker dialog.
+///
+public class OpenFolderPickerAction : PickerActionBase
+{
+ ///
+ /// Identifies the avalonia property.
+ ///
+ public static readonly StyledProperty AllowMultipleProperty =
+ AvaloniaProperty.Register(nameof(AllowMultiple));
+
+ ///
+ /// Gets or sets an option indicating whether open picker allows users to select multiple folders. This is an avalonia property.
+ ///
+ public bool AllowMultiple
+ {
+ get => GetValue(AllowMultipleProperty);
+ set => SetValue(AllowMultipleProperty, value);
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public OpenFolderPickerAction()
+ {
+ PassEventArgsToCommand = true;
+ }
+
+ ///
+ /// Executes the action.
+ ///
+ /// The that is passed to the action by the behavior. Generally this is or a target object.
+ /// The value of this parameter is determined by the caller.
+ /// True if the command is successfully executed; else false.
+ public override object Execute(object? sender, object? parameter)
+ {
+ if (sender is not Visual visual)
+ {
+ return false;
+ }
+
+ Dispatcher.UIThread.InvokeAsync(async () => await OpenFolderPickerAsync(visual));
+
+ return true;
+ }
+
+ private async Task OpenFolderPickerAsync(Visual visual)
+ {
+ if (IsEnabled != true || Command is null)
+ {
+ return;
+ }
+
+ var storageProvider = (visual.GetVisualRoot() as TopLevel)?.StorageProvider;
+ if (storageProvider is null)
+ {
+ return;
+ }
+
+ var folders = await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
+ {
+ Title = Title,
+ SuggestedStartLocation = SuggestedStartLocation,
+ SuggestedFileName = SuggestedFileName,
+ AllowMultiple = AllowMultiple
+ });
+
+ if (folders.Count <= 0)
+ {
+ return;
+ }
+
+ var resolvedParameter = ResolveParameter(folders);
+
+ if (!Command.CanExecute(resolvedParameter))
+ {
+ return;
+ }
+
+ Command.Execute(resolvedParameter);
+ }
+}
diff --git a/src/Avalonia.Xaml.Interactions/StorageProvider/SaveFilePickerAction.cs b/src/Avalonia.Xaml.Interactions/StorageProvider/SaveFilePickerAction.cs
new file mode 100644
index 000000000..b8109bb01
--- /dev/null
+++ b/src/Avalonia.Xaml.Interactions/StorageProvider/SaveFilePickerAction.cs
@@ -0,0 +1,124 @@
+using System.Threading.Tasks;
+using Avalonia.Controls;
+using Avalonia.Platform.Storage;
+using Avalonia.Threading;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Xaml.Interactions.Core;
+
+///
+/// An action that will open a file picker dialog.
+///
+public class SaveFilePickerAction : PickerActionBase
+{
+ ///
+ /// Identifies the avalonia property.
+ ///
+ public static readonly StyledProperty DefaultExtensionProperty =
+ AvaloniaProperty.Register(nameof(DefaultExtension));
+
+ ///
+ /// Identifies the avalonia property.
+ ///
+ public static readonly StyledProperty FileTypeChoicesProperty =
+ AvaloniaProperty.Register(nameof(FileTypeChoices));
+
+ ///
+ /// Identifies the avalonia property.
+ ///
+ public static readonly StyledProperty ShowOverwritePromptProperty =
+ AvaloniaProperty.Register(nameof(ShowOverwritePrompt));
+
+ ///
+ /// Gets or sets the default extension to be used to save the file. This is an avalonia property.
+ ///
+ public string? DefaultExtension
+ {
+ get => GetValue(DefaultExtensionProperty);
+ set => SetValue(DefaultExtensionProperty, value);
+ }
+
+ ///
+ /// Gets or sets the collection of valid file types that the user can choose to assign to a file. This is an avalonia property.
+ ///
+ public string? FileTypeChoices
+ {
+ get => GetValue(FileTypeChoicesProperty);
+ set => SetValue(FileTypeChoicesProperty, value);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether file open picker displays a warning if the user specifies the name of a file that already exists. This is an avalonia property.
+ ///
+ public bool? ShowOverwritePrompt
+ {
+ get => GetValue(ShowOverwritePromptProperty);
+ set => SetValue(ShowOverwritePromptProperty, value);
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public SaveFilePickerAction()
+ {
+ PassEventArgsToCommand = true;
+ }
+
+ ///
+ /// Executes the action.
+ ///
+ /// The that is passed to the action by the behavior. Generally this is or a target object.
+ /// The value of this parameter is determined by the caller.
+ /// True if the command is successfully executed; else false.
+ public override object Execute(object? sender, object? parameter)
+ {
+ if (sender is not Visual visual)
+ {
+ return false;
+ }
+
+ Dispatcher.UIThread.InvokeAsync(async () => await SaveFilePickerAsync(visual));
+
+ return true;
+ }
+
+ private async Task SaveFilePickerAsync(Visual visual)
+ {
+ if (IsEnabled != true || Command is null)
+ {
+ return;
+ }
+
+ var storageProvider = (visual.GetVisualRoot() as TopLevel)?.StorageProvider;
+ if (storageProvider is null)
+ {
+ return;
+ }
+
+ var file = await storageProvider.SaveFilePickerAsync(new FilePickerSaveOptions
+ {
+ Title = Title,
+ SuggestedStartLocation = SuggestedStartLocation,
+ SuggestedFileName = SuggestedFileName,
+ DefaultExtension = DefaultExtension,
+ FileTypeChoices = FileTypeChoices is not null
+ ? FileFilterParser.ConvertToFilePickerFileType(FileTypeChoices)
+ : null,
+ ShowOverwritePrompt = ShowOverwritePrompt,
+ });
+
+ if (file is null)
+ {
+ return;
+ }
+
+ var resolvedParameter = ResolveParameter(file);
+
+ if (!Command.CanExecute(resolvedParameter))
+ {
+ return;
+ }
+
+ Command.Execute(resolvedParameter);
+ }
+}
diff --git a/src/Avalonia.Xaml.Interactions/StorageProvider/Utilities/FileFilterParser.cs b/src/Avalonia.Xaml.Interactions/StorageProvider/Utilities/FileFilterParser.cs
new file mode 100644
index 000000000..616d97ef2
--- /dev/null
+++ b/src/Avalonia.Xaml.Interactions/StorageProvider/Utilities/FileFilterParser.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Platform.Storage;
+
+namespace Avalonia.Xaml.Interactions.Core;
+
+internal static class FileFilterParser
+{
+ public static List? ConvertToFilePickerFileType(string filter)
+ {
+ if (string.IsNullOrWhiteSpace(filter))
+ {
+ return null;
+ }
+
+ var parts = filter.Split('|');
+ if (parts.Length % 2 != 0)
+ {
+ return null;
+ }
+
+ var fileTypes = new List();
+
+ for (var i = 0; i < parts.Length; i += 2)
+ {
+ var description = parts[i];
+ var patternPart = parts[i + 1];
+ var index = description.IndexOf(" (", StringComparison.Ordinal);
+ if (index > 0)
+ {
+ description = description.Substring(0, index);
+ }
+
+ description = description.Trim();
+
+ var patterns = patternPart
+ .Split([';'], StringSplitOptions.RemoveEmptyEntries)
+ .Select(p => p.Trim())
+ .ToList();
+
+ fileTypes.Add(new FilePickerFileType(description) { Patterns = patterns });
+ }
+
+ return fileTypes;
+ }
+}