Migrating from PCLStorage to .NET Standard 2.0

If you’re a Xamarin Forms developer, you’ve likely used PCLStorage (or other Dependency Service) to interact with the target platform’s file system in the portable class library’s code. However, since November 2017, Xamarin.Forms now uses a .NET Standard 2.0 class library and PCLStorage is no longer supported.

System.IO.File

This isn’t a problem because in .NET Core 2.0 you now have access to System.IO.File’s GetFolderPath and SpecialFolder methods (see System.IO.File in .NET Core 2.0 docs).

Thus, you can get a path to the app’s local folder (the one that the app can save to) without having to write a Dependency Service (which is what PCLStorage does) by using:

var localFolder = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);

With a reference to the localFolder path, you can Combine it with the file name:

var notes = File.ReadAllText(Path.Combine(LocalFolder, "notes.txt"));

Functional Demo

As a very simple example, I created a couple extension methods for Stream and Byte[] in the below FileExtensions class.

To test it, I created a ContentPage that downloads an image, saves it using the extension method and set a FileImageSource to confirm that it’s a file (instead of just using a StreamImageSource).


public static class FileExtensions
{
private static readonly string LocalFolder;
static FileExtensions()
{
// Gets the target platform's valid save location
LocalFolder = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
}
// Byte[] extension methods
public static async Task<string> SaveToLocalFolderAsync(this byte[] dataBytes, string fileName)
{
return await Task.Run(() =>
{
// Use Combine so that the correct file path slashes are used
var filePath = Path.Combine(LocalFolder, fileName);
if (File.Exists(filePath))
File.Delete(filePath);
File.WriteAllBytes(filePath, dataBytes);
return filePath;
});
}
public static async Task<byte[]> LoadFileBytesAsync(string filePath)
{
return await Task.Run(() => File.ReadAllBytes(filePath));
}
// Stream extension methods
public static async Task<string> SaveToLocalFolderAsync(this Stream dataStream, string fileName)
{
// Use Combine so that the correct file path slashes are used
var filePath = Path.Combine(LocalFolder, fileName);
if (File.Exists(filePath))
File.Delete(filePath);
using (var fileStream = File.OpenWrite(filePath))
{
if (dataStream.CanSeek)
dataStream.Position = 0;
await dataStream.CopyToAsync(fileStream);
return filePath;
}
}
public static async Task<Stream> LoadFileStreamAsync(string filePath)
{
return await Task.Run(() =>
{
using (var fileStream = File.OpenRead(filePath))
{
return fileStream;
}
});
}
}


<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms&quot;
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml&quot;
xmlns:telerikDataControls="clr-namespace:Telerik.XamarinForms.DataControls;assembly=Telerik.XamarinForms.DataControls"
xmlns:viewModels="clr-namespace:NetStandardTest.UpdatedPortable.ViewModels;assembly=NetStandardTest.UpdatedPortable"
x:Class="NetStandardTest.Forms.StartPage">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image x:Name="BackgroundImage"
Grid.RowSpan="2"/>
<Frame BackgroundColor="White"
HorizontalOptions="Center"
VerticalOptions="Center"
Margin="20">
<Label x:Name="OutputLabel"
LineBreakMode="CharacterWrap"/>
</Frame>
<Button Text="Save File"
Clicked="Button_OnClicked"
Margin="20"
Grid.Row="1" />
</Grid>
</ContentPage>

view raw

StartPage.xaml

hosted with ❤ by GitHub


[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class StartPage : ContentPage
{
private readonly HttpClient client;
private string imageFileName = "downloadedImage.jpg";
public StartPage()
{
InitializeComponent();
client = new HttpClient();
}
private async void Button_OnClicked(object sender, EventArgs e)
{
await DownloadAndSaveImage();
}
private async Task DownloadAndSaveImage()
{
try
{
using (var response = await client.GetStreamAsync("https://picsum.photos/200/300/?random&blur&quot;))
{
var filePath = await response.SaveToLocalFolderAsync(imageFileName);
// Using FileImageSource just to confirm the image was saved as a file
BackgroundImage.Source = new FileImageSource { File = filePath };
OutputLabel.Text = $"Image file saved to\r\n\n{filePath}";
}
}
catch (Exception ex)
{
Debug.WriteLine($"DownloadAndSaveImage Exception: {ex}");
}
}
}

Note that the extension methods are very basic and shouldn’t be used in production as-is (i.e. no defensive programming code).

Here is the result at runtime on UWP:

2018-04-19_1134

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.