Better error handling for image loading errors on iOS/Android (#849)

* First run at removing async void image update methods

Consistent error logging and IsLoading on Android,iOS,UWP

Move error logging into image handlers for better messages

Add demo of custom ImageRenderer error handling

Update docs

Make the test smaller so the results don't get pushed offscreen

Fix namespace error

* Update error handling for fast image renderer

* Update 37625 test to use image we control

* Add java disposed check to avoid ObjectDisposedException in async operations

* Add disposed checks to legacy renderer; null check element before SetIsLoading

* Check disposed on GetDesiredSize for fast renderer
Use local disposed member where possible for disposed check

* Check for disposal after async handlers in iOS

* Add disposal checks after async methods in Windows

* Reset linker settings on project; reduce redundant casts in ImageViewExtensions
This commit is contained in:
E.Z. Hart 2017-04-25 12:16:25 -06:00 коммит произвёл Rui Marinho
Родитель 9631ec2d8b
Коммит cdc4055128
29 изменённых файлов: 696 добавлений и 199 удалений

Просмотреть файл

@ -0,0 +1,38 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Android.Content;
using Android.Graphics;
using Xamarin.Forms;
using Xamarin.Forms.ControlGallery.Android;
using Xamarin.Forms.Controls.Issues;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(_51173Image), typeof(_51173CustomImageRenderer))]
namespace Xamarin.Forms.ControlGallery.Android
{
public sealed class BrokenImageSourceHandler : IImageSourceHandler
{
public Task<Bitmap> LoadImageAsync(ImageSource imagesource, Context context, CancellationToken cancelationToken = default(CancellationToken))
{
throw new Exception("Fail");
}
}
public class _51173CustomImageRenderer : ImageRenderer
{
protected override async Task TryUpdateBitmap(Image previous = null)
{
try
{
await UpdateBitmap(previous).ConfigureAwait(false);
}
catch (Exception ex)
{
await Application.Current.MainPage.DisplayAlert("Image Error 51173", $"The image failed to load, here's why: {ex.Message}", "OK");
}
}
}
}

Просмотреть файл

@ -2,8 +2,11 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Android.App;
using Xamarin.Forms.Controls;
using Xamarin.Forms.Platform.Android;
using Xamarin.Forms;
using Xamarin.Forms.ControlGallery.Android;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
@ -37,6 +40,8 @@ using Xamarin.Forms;
[assembly: Android.App.MetaData("com.google.android.maps.v2.API_KEY", Value = "AIzaSyAdstcJQswxEjzX5YjLaMcu2aRVEBJw39Y")]
[assembly: Xamarin.Forms.ResolutionGroupName ("XamControl")]
// Deliberately broken image source and handler so we can test handling of image loading errors
[assembly: ExportImageSourceHandler(typeof(FailImageSource), typeof(BrokenImageSourceHandler))]
#if TEST_LEGACY_RENDERERS
[assembly: ExportRenderer(typeof(Button), typeof(Xamarin.Forms.Platform.Android.AppCompat.ButtonRenderer))]
[assembly: ExportRenderer(typeof(Image), typeof(Xamarin.Forms.Platform.Android.ImageRenderer))]

Просмотреть файл

@ -0,0 +1 @@
This is certainly not a real JPEG.

Просмотреть файл

@ -26,6 +26,9 @@
<RestorePackages>true</RestorePackages>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
<AndroidTlsProvider>
</AndroidTlsProvider>
<JavaMaximumHeapSize>1G</JavaMaximumHeapSize>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<AndroidKeyStore>True</AndroidKeyStore>
@ -46,20 +49,27 @@
<AndroidLinkMode>Full</AndroidLinkMode>
<JavaMaximumHeapSize>1G</JavaMaximumHeapSize>
<EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk>
<BundleAssemblies>False</BundleAssemblies>
<CustomCommands>
<CustomCommands>
<Command type="AfterBuild" command="xbuild /t:SignAndroidPackage Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj" workingdir="${SolutionDir}" />
<Command>
<type>AfterBuild</type>
<command>xbuild /t:SignAndroidPackage Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj</command>
<workingdir>${SolutionDir}</workingdir>
</Command>
</CustomCommands>
</CustomCommands>
<AndroidCreatePackagePerAbi>False</AndroidCreatePackagePerAbi>
<AndroidSupportedAbis>armeabi;armeabi-v7a;x86</AndroidSupportedAbis>
<Debugger>Xamarin</Debugger>
<AndroidEnableMultiDex>False</AndroidEnableMultiDex>
<Debugger>.Net (Xamarin)</Debugger>
<DevInstrumentationEnabled>True</DevInstrumentationEnabled>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NoWarn>
</NoWarn>
<BundleAssemblies>False</BundleAssemblies>
<AndroidCreatePackagePerAbi>False</AndroidCreatePackagePerAbi>
<AotAssemblies>False</AotAssemblies>
<EnableLLVM>False</EnableLLVM>
<AndroidEnableMultiDex>False</AndroidEnableMultiDex>
<EnableProguard>False</EnableProguard>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@ -73,8 +83,6 @@
<JavaMaximumHeapSize>1G</JavaMaximumHeapSize>
<AndroidLinkSkip />
<EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk>
<BundleAssemblies>False</BundleAssemblies>
<AndroidCreatePackagePerAbi>False</AndroidCreatePackagePerAbi>
<AndroidSupportedAbis>armeabi-v7a,x86</AndroidSupportedAbis>
<AndroidStoreUncompressedFileExtensions />
<MandroidI18n />
@ -97,14 +105,11 @@
<AndroidLinkSkip />
<AndroidLinkMode>SdkOnly</AndroidLinkMode>
<EmbedAssembliesIntoApk>False</EmbedAssembliesIntoApk>
<BundleAssemblies>False</BundleAssemblies>
<AndroidCreatePackagePerAbi>False</AndroidCreatePackagePerAbi>
<AndroidSupportedAbis>armeabi,armeabi-v7a,x86</AndroidSupportedAbis>
<AndroidStoreUncompressedFileExtensions />
<MandroidI18n />
<JavaMaximumHeapSize>1G</JavaMaximumHeapSize>
<Debugger>Xamarin</Debugger>
<AndroidEnableMultiDex>False</AndroidEnableMultiDex>
<DevInstrumentationEnabled>True</DevInstrumentationEnabled>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NoWarn>
@ -176,6 +181,7 @@
<Compile Include="CustomRenderers.cs" />
<Compile Include="ColorPicker.cs" />
<Compile Include="_38989CustomRenderer.cs" />
<Compile Include="BrokenImageSourceHandler.cs" />
</ItemGroup>
<ItemGroup>
<AndroidAsset Include="Assets\default.css" />
@ -204,6 +210,7 @@
<AndroidResource Include="Resources\drawable\coffee.png" />
<AndroidResource Include="Resources\drawable\toolbar_close.png" />
<AndroidResource Include="Resources\drawable\test.jpg" />
<AndroidResource Include="Resources\drawable\invalidimage.jpg" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\Icon.png" />

Просмотреть файл

@ -30,6 +30,4 @@ namespace Xamarin.Forms.ControlGallery.Android
return nativeView;
}
}
}

Просмотреть файл

@ -1,16 +1,20 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Xamarin.Forms.Controls.Issues;
using WImageSource = Windows.UI.Xaml.Media.ImageSource;
#if WINDOWS_UWP
using Xamarin.Forms.Platform.UWP;
using Xamarin.Forms.ControlGallery.WindowsUniversal;
[assembly: ExportRenderer(typeof(_51173Image), typeof(_51173CustomImageRenderer))]
namespace Xamarin.Forms.ControlGallery.WindowsUniversal
#else
using Xamarin.Forms.Platform.WinRT;
using Xamarin.Forms.ControlGallery.WinRT;
[assembly: ExportRenderer(typeof(_51173Image), typeof(_51173CustomImageRenderer))]
namespace Xamarin.Forms.ControlGallery.WinRT
#endif
{
@ -21,4 +25,19 @@ namespace Xamarin.Forms.ControlGallery.WinRT
throw new Exception("Fail");
}
}
public class _51173CustomImageRenderer : ImageRenderer
{
protected override async Task TryUpdateSource()
{
try
{
await UpdateSource().ConfigureAwait(false);
}
catch (Exception ex)
{
await Xamarin.Forms.Application.Current.MainPage.DisplayAlert("Image Error 51173", $"The image failed to load, here's why: {ex.Message}", "OK");
}
}
}
}

Просмотреть файл

@ -0,0 +1,36 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.ControlGallery.iOS;
using Xamarin.Forms.Controls.Issues;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(_51173Image), typeof(_51173CustomImageRenderer))]
namespace Xamarin.Forms.ControlGallery.iOS
{
public sealed class BrokenImageSourceHandler : IImageSourceHandler
{
public Task<UIImage> LoadImageAsync(ImageSource imagesource, CancellationToken cancelationToken = new CancellationToken(),
float scale = 1)
{
throw new Exception("Fail");
}
}
public class _51173CustomImageRenderer : ImageRenderer
{
protected override async Task TrySetImage(Image previous = null)
{
try
{
await SetImage(previous).ConfigureAwait(false);
}
catch (Exception ex)
{
await Xamarin.Forms.Application.Current.MainPage.DisplayAlert("Image Error 51173", $"The image failed to load, here's why: {ex.Message}", "OK");
}
}
}
}

Просмотреть файл

@ -1,6 +1,9 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Xamarin.Forms;
using Xamarin.Forms.ControlGallery.iOS;
using Xamarin.Forms.Controls;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
@ -35,3 +38,6 @@ using System.Runtime.InteropServices;
[assembly: AssemblyVersion ("1.0.0.0")]
[assembly: AssemblyFileVersion ("1.0.0.0")]
[assembly: Xamarin.Forms.ResolutionGroupName("XamControl")]
// Deliberately broken image source and handler so we can test handling of image loading errors
[assembly: ExportImageSourceHandler(typeof(FailImageSource), typeof(BrokenImageSourceHandler))]

Просмотреть файл

@ -0,0 +1 @@
This is certainly not a real JPEG.

Просмотреть файл

@ -159,6 +159,7 @@
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<Compile Include="BrokenImageSourceHandler.cs" />
<Compile Include="BrokenNativeControl.cs" />
<Compile Include="CustomRenderer40251.cs" />
<Compile Include="Main.cs" />
@ -218,6 +219,7 @@
<Content Include="oasis.jpg" />
<BundleResource Include="oasissmall.jpg" />
<Content Include="coffee%402x.png" />
<BundleResource Include="Resources\invalidimage.jpg" />
<Content Include="settings%402x.png" />
<Content Include="menuIcon%402x.png" />
<Content Include="crimsonsmall.jpg" />

Просмотреть файл

@ -13,13 +13,13 @@ namespace Xamarin.Forms.Controls.Issues
{
[Preserve (AllMembers = true)]
[Issue (IssueTracker.Bugzilla, 37625, "App crashes when quickly adding/removing Image views (Windows UWP)")]
public class Bugzilla37625 : TestContentPage // or TestMasterDetailPage, etc ...
public class Bugzilla37625 : TestContentPage
{
protected override async void Init ()
{
int retry = 5;
while (retry-- >= 0) {
var imageUri = new Uri ("https://xamarin.com/content/images/pages/products/platform.png");
var imageUri = new Uri ("https://raw.githubusercontent.com/xamarin/Xamarin.Forms/master/Xamarin.Forms.ControlGallery.Android/Assets/WebImages/XamarinLogo.png");
Content = new Image () { Source = new UriImageSource () { Uri = imageUri }, BackgroundColor = Color.Black, AutomationId = "success" };
await Task.Delay (50);

Просмотреть файл

@ -0,0 +1,187 @@
using System;
using System.Globalization;
using Xamarin.Forms.CustomAttributes;
using Xamarin.Forms.Internals;
#if UITEST
using Xamarin.Forms.Core.UITests;
using Xamarin.UITest;
using NUnit.Framework;
#endif
namespace Xamarin.Forms.Controls.Issues
{
#if UITEST
[Category(UITestCategories.Image)]
#endif
[Preserve(AllMembers = true)]
[Issue(IssueTracker.Bugzilla, 51173, "ImageRenderer, async void SetImage - Cannot catch exceptions", PlatformAffected.All)]
public class Bugzilla51173 : TestContentPage
{
#if UITEST
[Test(Description = "Attempt to load an image from a URI which does not exist")]
public void Bugzilla51173_NonexistentUri()
{
RunningApp.WaitForElement(q => q.Marked(UriDoesNotExist));
RunningApp.Tap(UriDoesNotExist);
RunningApp.WaitForElement(q => q.Marked(ErrorLogged));
RunningApp.WaitForElement(q => q.Marked(NotLoading));
}
[Test(Description = "The IImageSourceHandler itself throws an exception")]
public void Bugzilla51173_HandlerThrowsException()
{
RunningApp.WaitForElement(q => q.Marked(HandlerThrows));
RunningApp.Tap(HandlerThrows);
RunningApp.WaitForElement(q => q.Marked(ErrorLogged));
RunningApp.WaitForElement(q => q.Marked(NotLoading));
}
[Test(Description = "The URI is valid, but the image data is not")]
public void Bugzilla51173_RealUriWithInvalidImageData()
{
RunningApp.WaitForElement(q => q.Marked(RealUriInvalidImage));
RunningApp.Tap(RealUriInvalidImage);
RunningApp.WaitForElement(q => q.Marked(ErrorLogged));
RunningApp.WaitForElement(q => q.Marked(NotLoading));
}
[Test(Description = "Attempt to load a local image which does not exist")]
public void Bugzilla51173_NonexistentImage()
{
RunningApp.WaitForElement(q => q.Marked(ImageDoesNotExist));
RunningApp.Tap(ImageDoesNotExist);
RunningApp.WaitForElement(q => q.Marked(ErrorLogged));
RunningApp.WaitForElement(q => q.Marked(NotLoading));
}
[Test(Description = "Attempt to load a local image which exists, but has corrupt data")]
public void Bugzilla51173_InvalidImage()
{
RunningApp.WaitForElement(q => q.Marked(ImageIsInvalid));
RunningApp.Tap(ImageIsInvalid);
RunningApp.WaitForElement(q => q.Marked(ErrorLogged));
RunningApp.WaitForElement(q => q.Marked(NotLoading));
}
[Test(Description = "Load a valid image")]
public void Bugzilla51173_ValidImage()
{
RunningApp.WaitForElement(q => q.Marked(ValidImage));
RunningApp.Tap(ValidImage);
RunningApp.WaitForElement(q => q.Marked(NotLoading));
}
#endif
const string ValidImage = "Valid Image";
const string ImageDoesNotExist = "Non-existent Image File";
const string ImageIsInvalid = "Invalid Image File (bad data)";
const string UriDoesNotExist = "Non-existent URI";
const string HandlerThrows = "Source Throws Exception";
const string RealUriInvalidImage = "Valid URI with invalid image file";
const string ErrorLogged = "Error logged";
const string Loading = "Loading";
const string NotLoading = "Not Loading";
Label _results;
Image _image;
class LoadingConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((bool)value)
{
return Loading;
}
return NotLoading;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
protected override void Init()
{
_results = new Label { Margin = 10, FontAttributes = FontAttributes.Bold, BackgroundColor = Color.Silver, HorizontalTextAlignment = TextAlignment.Center};
var errorMessage = new Label();
Log.Listeners.Add(
new DelegateLogListener((c, m) => Device.BeginInvokeOnMainThread(() =>
{
_results.Text = ErrorLogged;
errorMessage.Text = m;
})));
var instructions = new Label
{
Text =
"Pressing the 'Valid Image' button should display an image of a coffee cup. Every other button should cause the messager 'Error logged' to appear at the top of the page."
};
_image = new Image { BackgroundColor = Color.Bisque, HeightRequest = 20 };
var loadingState = new Label();
loadingState.SetBinding(Label.TextProperty, new Binding(Image.IsLoadingProperty.PropertyName, BindingMode.Default, new LoadingConverter()));
loadingState.BindingContext = _image;
var legit = CreateTest(() => _image.Source = ImageSource.FromFile("coffee.png"), ValidImage);
var invalidImageFileName = CreateTest(() => _image.Source = ImageSource.FromFile("fake.png"), ImageDoesNotExist);
var invalidImageFile = CreateTest(() => _image.Source = ImageSource.FromFile("invalidimage.jpg"), ImageIsInvalid);
var fakeUri = CreateTest(() => _image.Source = ImageSource.FromUri(new Uri("http://not.real")), UriDoesNotExist);
var crashImage = CreateTest(() => _image.Source = new FailImageSource(), HandlerThrows);
var uriInvalidImageData =
CreateTest(() => _image.Source = ImageSource.FromUri(new Uri("https://gist.githubusercontent.com/hartez/a2dda6b5c78852bcf4832af18f21a023/raw/39f4cd2e9fe8514694ac7fa0943017eb9308853d/corrupt.jpg")),
RealUriInvalidImage);
Content = new StackLayout
{
Margin = new Thickness(5, 20, 5, 0),
Children =
{
_image,
instructions,
legit,
invalidImageFileName,
invalidImageFile,
fakeUri,
crashImage,
uriInvalidImageData,
_results,
loadingState,
errorMessage
}
};
}
Button CreateTest(Action imageLoadAction, string title)
{
var button = new Button { Text = title };
button.Clicked += (sender, args) =>
{
_results.Text = "";
imageLoadAction();
};
return button;
}
}
}

Просмотреть файл

@ -0,0 +1,40 @@
using Xamarin.Forms.CustomAttributes;
using Xamarin.Forms.Internals;
namespace Xamarin.Forms.Controls.Issues
{
[Preserve(AllMembers = true)]
[Issue(IssueTracker.None, 51173, "Custom ImageRenderer error handling demo", PlatformAffected.All)]
public class CustomImageRendererErrorHandling : TestContentPage
{
protected override void Init()
{
var layout = new StackLayout { Margin = new Thickness(5, 40, 5, 0) };
var instructions = new Label
{
Text =
@"
Click 'Update Image Source'; it will update the coffee image with an image source which will throw an exception.
Instead of just logging an error, the custom renderer will display an alert dialog about the error.
"
};
var image = new _51173Image { Source = "coffee.png" };
var button = new Button { Text = "Update Image Source" };
button.Clicked += (sender, args) => image.Source = new FailImageSource();
layout.Children.Add(instructions);
layout.Children.Add(image);
layout.Children.Add(button);
Content = layout;
}
}
// custom image type for demonstrating custom error handling in a custom renderer
[Preserve(AllMembers = true)]
public class _51173Image : Image { }
}

Просмотреть файл

@ -1,100 +0,0 @@
using System;
using Xamarin.Forms.CustomAttributes;
using Xamarin.Forms.Internals;
namespace Xamarin.Forms.Controls
{
[Preserve(AllMembers = true)]
[Issue(IssueTracker.None, 0, "Image Loading Error Handling", PlatformAffected.WinRT | PlatformAffected.UWP)]
public class ImageLoadingErrorHandling : TestContentPage
{
protected override void Init()
{
Log.Listeners.Add(
new DelegateLogListener((c, m) => Device.BeginInvokeOnMainThread(() => DisplayAlert(c, m, "Cool, Thanks"))));
var image = new Image() {BackgroundColor = Color.White};
Grid legit = CreateTest(() => image.Source = ImageSource.FromFile("coffee.png"),
"Valid Image",
"Clicking this button should load an image at the top of the page.",
Color.Silver);
Grid invalidImageFileName = CreateTest(() => image.Source = ImageSource.FromFile("fake.png"),
"Non-existent Image File",
"Clicking this button should display an alert dialog with an error that the image failed to load.");
Grid invalidImageFile = CreateTest(() => image.Source = ImageSource.FromFile("invalidimage.jpg"),
"Invalid Image File (bad data)",
"Clicking this button should display an alert dialog with an error that the image failed to load.",
Color.Silver);
Grid fakeUri = CreateTest(() => image.Source = ImageSource.FromUri(new Uri("http://not.real")),
"Non-existent URI",
(Device.RuntimePlatform == Device.UWP || Device.RuntimePlatform == Device.WinRT) && Device.Idiom == TargetIdiom.Phone
? "Clicking this button should display an alert dialog. The error message should include the text 'NotFound'."
: "Clicking this button should display an alert dialog. The error message should include the text 'the server name or address could not be resolved'.");
// This used to crash the app with an uncatchable error; need to make sure it's not still doing that
Grid crashImage = CreateTest(() => image.Source = new FailImageSource(),
"Source Throws Exception",
"Clicking this button should display an alert dialog. The error messages hould include the test 'error updating image source'.",
Color.Silver);
Grid uriInvalidImageData =
CreateTest(() => image.Source = ImageSource.FromUri(new Uri("https://gist.githubusercontent.com/hartez/a2dda6b5c78852bcf4832af18f21a023/raw/39f4cd2e9fe8514694ac7fa0943017eb9308853d/corrupt.jpg")),
"Valid URI with invalid image file",
"Clicking this button should display an alert dialog. The error message should include the text 'UriImageSourceHandler could not load https://gist.githubusercontent.com/hartez/a2dda6b5c78852bcf4832af18f21a023/raw/39f4cd2e9fe8514694ac7fa0943017eb9308853d/corrupt.jpg'");
Content = new StackLayout
{
Children =
{
image,
legit,
invalidImageFileName,
invalidImageFile,
fakeUri,
crashImage,
uriInvalidImageData
}
};
}
static Grid CreateTest(Action imageLoadAction, string title, string instructions, Color? backgroundColor = null)
{
var button = new Button { Text = "Test" };
button.Clicked += (sender, args) => imageLoadAction();
var titleLabel = new Label
{
Text = title,
FontAttributes = FontAttributes.Bold
};
var label = new Label
{
Text = instructions
};
var grid = new Grid
{
ColumnDefinitions =
new ColumnDefinitionCollection { new ColumnDefinition(), new ColumnDefinition(), new ColumnDefinition() },
RowDefinitions = new RowDefinitionCollection { new RowDefinition { Height = 80 } }
};
if (backgroundColor.HasValue)
{
grid.BackgroundColor = backgroundColor.Value;
}
grid.AddChild(titleLabel, 0, 0);
grid.AddChild(label, 1, 0);
grid.AddChild(button, 2, 0);
return grid;
}
}
}

Просмотреть файл

@ -200,6 +200,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla34561.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla34727.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ComplexListView.cs" />
<Compile Include="$(MSBuildThisFileDirectory)CustomImageRendererErrorHandling.cs" />
<Compile Include="$(MSBuildThisFileDirectory)DefaultColorToggleTest.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla38416.xaml.cs">
<DependentUpon>Bugzilla38416.xaml</DependentUpon>
@ -253,7 +254,7 @@
<Compile Include="$(MSBuildThisFileDirectory)TestPages\ScreenshotConditionalApp.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla41842.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla42277.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ImageLoadingErrorHandling.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla51173.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla33561.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla43214.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla42602.cs" />

Просмотреть файл

@ -18,6 +18,11 @@ namespace Xamarin.Forms
return Task.FromResult(false);
}
public override string ToString()
{
return $"File: {File}";
}
public static implicit operator FileImageSource(string file)
{
return (FileImageSource)FromFile(file);

Просмотреть файл

@ -92,6 +92,11 @@ namespace Xamarin.Forms
return stream;
}
public override string ToString()
{
return $"Uri: {Uri}";
}
static string GetCacheKey(Uri uri)
{
return Device.PlatformServices.GetMD5Hash(uri.AbsoluteUri);

Просмотреть файл

@ -1,28 +1,33 @@
using System;
using System.Threading.Tasks;
using Android.Graphics;
using Java.IO;
using AImageView = Android.Widget.ImageView;
namespace Xamarin.Forms.Platform.Android
{
internal static class ImageViewExtensions
{
public static async void UpdateBitmap(this AImageView imageView, Image newImage, Image previousImage = null)
// TODO hartez 2017/04/07 09:33:03 Review this again, not sure it's handling the transition from previousImage to 'null' newImage correctly
public static async Task UpdateBitmap(this AImageView imageView, Image newImage, Image previousImage = null)
{
if (imageView == null || imageView.IsDisposed())
return;
if (Device.IsInvokeRequired)
throw new InvalidOperationException("Image Bitmap must not be updated from background thread");
if (previousImage != null && Equals(previousImage.Source, newImage.Source))
return;
((IImageController)newImage).SetIsLoading(true);
var imageController = newImage as IImageController;
(imageView as IImageRendererController).SkipInvalidate();
imageController?.SetIsLoading(true);
(imageView as IImageRendererController)?.SkipInvalidate();
imageView.SetImageResource(global::Android.Resource.Color.Transparent);
ImageSource source = newImage.Source;
ImageSource source = newImage?.Source;
Bitmap bitmap = null;
IImageSourceHandler handler;
@ -34,10 +39,7 @@ namespace Xamarin.Forms.Platform.Android
}
catch (TaskCanceledException)
{
}
catch (IOException ex)
{
Internals.Log.Warning("Xamarin.Forms.Platform.Android.ImageRenderer", "Error updating bitmap: {0}", ex);
imageController?.SetIsLoading(false);
}
}
@ -47,14 +49,19 @@ namespace Xamarin.Forms.Platform.Android
return;
}
if (bitmap == null && source is FileImageSource)
imageView.SetImageResource(ResourceManager.GetDrawableByName(((FileImageSource)source).File));
else
imageView.SetImageBitmap(bitmap);
if (!imageView.IsDisposed())
{
if (bitmap == null && source is FileImageSource)
imageView.SetImageResource(ResourceManager.GetDrawableByName(((FileImageSource)source).File));
else
{
imageView.SetImageBitmap(bitmap);
}
}
bitmap?.Dispose();
((IImageController)newImage).SetIsLoading(false);
imageController?.SetIsLoading(false);
((IVisualElementController)newImage).NativeSizeChanged();
}
}

Просмотреть файл

@ -0,0 +1,12 @@
using System;
namespace Xamarin.Forms.Platform.Android
{
internal static class JavaObjectExtensions
{
public static bool IsDisposed(this Java.Lang.Object obj)
{
return obj.Handle == IntPtr.Zero;
}
}
}

Просмотреть файл

@ -1,8 +1,10 @@
using System;
using System.ComponentModel;
using System.Threading.Tasks;
using AImageView = Android.Widget.ImageView;
using AView = Android.Views.View;
using Android.Views;
using Xamarin.Forms.Internals;
namespace Xamarin.Forms.Platform.Android.FastRenderers
{
@ -17,30 +19,32 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (_disposed)
return;
_disposed = true;
if (!disposing)
return;
if (_visualElementTracker != null)
if (disposing)
{
_visualElementTracker.Dispose();
_visualElementTracker = null;
if (_visualElementTracker != null)
{
_visualElementTracker.Dispose();
_visualElementTracker = null;
}
if (_visualElementRenderer != null)
{
_visualElementRenderer.Dispose();
_visualElementRenderer = null;
}
if (_element != null)
{
_element.PropertyChanged -= OnElementPropertyChanged;
}
}
if (_visualElementRenderer != null)
{
_visualElementRenderer.Dispose();
_visualElementRenderer = null;
}
if (_element != null)
_element.PropertyChanged -= OnElementPropertyChanged;
base.Dispose(disposing);
}
public override void Invalidate()
@ -54,9 +58,9 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
base.Invalidate();
}
protected virtual void OnElementChanged(ElementChangedEventArgs<Image> e)
protected virtual async void OnElementChanged(ElementChangedEventArgs<Image> e)
{
this.UpdateBitmap(e.NewElement, e.OldElement);
await TryUpdateBitmap(e.OldElement);
UpdateAspect();
ElementChanged?.Invoke(this, new VisualElementChangedEventArgs(e.OldElement, e.NewElement));
@ -77,6 +81,11 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
SizeRequest IVisualElementRenderer.GetDesiredSize(int widthConstraint, int heightConstraint)
{
if (_disposed)
{
return new SizeRequest();
}
Measure(widthConstraint, heightConstraint);
return new SizeRequest(new Size(MeasuredWidth, MeasuredHeight), MinimumSize());
}
@ -114,7 +123,7 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
_element?.SendViewInitialized(Control);
}
void IVisualElementRenderer.SetLabelFor(int? id)
{
if (_defaultLabelFor == null)
@ -144,18 +153,53 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
{
}
protected virtual void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
protected virtual async void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == Image.SourceProperty.PropertyName)
this.UpdateBitmap(_element);
await TryUpdateBitmap();
else if (e.PropertyName == Image.AspectProperty.PropertyName)
UpdateAspect();
ElementPropertyChanged?.Invoke(this, e);
}
protected virtual async Task TryUpdateBitmap(Image previous = null)
{
// By default we'll just catch and log any exceptions thrown by UpdateBitmap so they don't bring down
// the application; a custom renderer can override this method and handle exceptions from
// UpdateBitmap differently if it wants to
try
{
await UpdateBitmap(previous);
}
catch (Exception ex)
{
Log.Warning(nameof(ImageRenderer), "Error loading image: {0}", ex);
}
finally
{
((IImageController)_element)?.SetIsLoading(false);
}
}
protected async Task UpdateBitmap(Image previous = null)
{
if (_element == null || _disposed)
{
return;
}
await Control.UpdateBitmap(_element, previous);
}
void UpdateAspect()
{
if (_element == null || _disposed)
{
return;
}
ScaleType type = _element.Aspect.ToScaleType();
SetScaleType(type);
}

Просмотреть файл

@ -3,6 +3,7 @@ using System.Threading;
using System.Threading.Tasks;
using Android.Content;
using Android.Graphics;
using Xamarin.Forms.Internals;
namespace Xamarin.Forms.Platform.Android
{
@ -17,10 +18,18 @@ namespace Xamarin.Forms.Platform.Android
public async Task<Bitmap> LoadImageAsync(ImageSource imagesource, Context context, CancellationToken cancelationToken = default(CancellationToken))
{
string file = ((FileImageSource)imagesource).File;
Bitmap bitmap;
if (File.Exists (file))
return !DecodeSynchronously ? (await BitmapFactory.DecodeFileAsync (file).ConfigureAwait (false)) : BitmapFactory.DecodeFile (file);
bitmap = !DecodeSynchronously ? (await BitmapFactory.DecodeFileAsync (file).ConfigureAwait (false)) : BitmapFactory.DecodeFile (file);
else
return !DecodeSynchronously ? (await context.Resources.GetBitmapAsync (file).ConfigureAwait (false)) : context.Resources.GetBitmap (file);
bitmap = !DecodeSynchronously ? (await context.Resources.GetBitmapAsync (file).ConfigureAwait (false)) : context.Resources.GetBitmap (file);
if (bitmap == null)
{
Log.Warning(nameof(FileImageSourceHandler), "Could not find image or image file was invalid: {0}", imagesource);
}
return bitmap;
}
}
}

Просмотреть файл

@ -3,6 +3,7 @@ using System.Threading;
using System.Threading.Tasks;
using Android.Content;
using Android.Graphics;
using Xamarin.Forms.Internals;
namespace Xamarin.Forms.Platform.Android
{
@ -11,12 +12,19 @@ namespace Xamarin.Forms.Platform.Android
public async Task<Bitmap> LoadImageAsync(ImageSource imagesource, Context context, CancellationToken cancelationToken = default(CancellationToken))
{
var imageLoader = imagesource as UriImageSource;
if (imageLoader != null && imageLoader.Uri != null)
Bitmap bitmap = null;
if (imageLoader?.Uri != null)
{
using (Stream imageStream = await imageLoader.GetStreamAsync(cancelationToken).ConfigureAwait(false))
return await BitmapFactory.DecodeStreamAsync(imageStream).ConfigureAwait(false);
bitmap = await BitmapFactory.DecodeStreamAsync(imageStream).ConfigureAwait(false);
}
return null;
if (bitmap == null)
{
Log.Warning(nameof(ImageLoaderSourceHandler), "Could not retrieve image or image data was invalid: {0}", imageLoader);
}
return bitmap;
}
}
}

Просмотреть файл

@ -1,10 +1,10 @@
using System;
using System.ComponentModel;
using System.Threading.Tasks;
using Android.Graphics;
using Android.Views;
using AImageView = Android.Widget.ImageView;
using Xamarin.Forms.Internals;
using static Xamarin.Forms.Platform.Android.ImageViewExtensions;
namespace Xamarin.Forms.Platform.Android
{
@ -20,7 +20,6 @@ namespace Xamarin.Forms.Platform.Android
public ImageRenderer()
{
System.Diagnostics.Debug.WriteLine(">>>>> Old Image Renderer");
AutoPackage = false;
}
@ -39,7 +38,7 @@ namespace Xamarin.Forms.Platform.Android
return new FormsImageView(Context);
}
protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
protected override async void OnElementChanged(ElementChangedEventArgs<Image> e)
{
base.OnElementChanged(e);
@ -50,28 +49,63 @@ namespace Xamarin.Forms.Platform.Android
}
_motionEventHelper.UpdateElement(e.NewElement);
Control.UpdateBitmap(e.NewElement, e.OldElement);
await TryUpdateBitmap(e.OldElement);
UpdateAspect();
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
protected override async void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Image.SourceProperty.PropertyName)
Control.UpdateBitmap(Element);
await TryUpdateBitmap();
else if (e.PropertyName == Image.AspectProperty.PropertyName)
UpdateAspect();
}
void UpdateAspect()
{
if (Element == null || Control == null || Control.IsDisposed())
{
return;
}
AImageView.ScaleType type = Element.Aspect.ToScaleType();
Control.SetScaleType(type);
}
protected virtual async Task TryUpdateBitmap(Image previous = null)
{
// By default we'll just catch and log any exceptions thrown by UpdateBitmap so they don't bring down
// the application; a custom renderer can override this method and handle exceptions from
// UpdateBitmap differently if it wants to
try
{
await UpdateBitmap(previous);
}
catch (Exception ex)
{
Log.Warning(nameof(ImageRenderer), "Error loading image: {0}", ex);
}
finally
{
((IImageController)Element)?.SetIsLoading(false);
}
}
protected async Task UpdateBitmap(Image previous = null)
{
if (Element == null || Control == null || Control.IsDisposed())
{
return;
}
await Control.UpdateBitmap(Element, previous);
}
public override bool OnTouchEvent(MotionEvent e)
{
if (base.OnTouchEvent(e))

Просмотреть файл

@ -3,6 +3,7 @@ using System.Threading;
using System.Threading.Tasks;
using Android.Content;
using Android.Graphics;
using Xamarin.Forms.Internals;
namespace Xamarin.Forms.Platform.Android
{
@ -11,12 +12,19 @@ namespace Xamarin.Forms.Platform.Android
public async Task<Bitmap> LoadImageAsync(ImageSource imagesource, Context context, CancellationToken cancelationToken = default(CancellationToken))
{
var streamsource = imagesource as StreamImageSource;
if (streamsource != null && streamsource.Stream != null)
Bitmap bitmap = null;
if (streamsource?.Stream != null)
{
using (Stream stream = await ((IStreamImageSource)streamsource).GetStreamAsync(cancelationToken).ConfigureAwait(false))
return await BitmapFactory.DecodeStreamAsync(stream).ConfigureAwait(false);
bitmap = await BitmapFactory.DecodeStreamAsync(stream).ConfigureAwait(false);
}
return null;
if (bitmap == null)
{
Log.Warning(nameof(ImageLoaderSourceHandler), "Image data was invalid: {0}", streamsource);
}
return bitmap;
}
}
}

Просмотреть файл

@ -103,6 +103,7 @@
<Compile Include="AndroidApplicationLifecycleState.cs" />
<Compile Include="AndroidTitleBarVisibility.cs" />
<Compile Include="AppCompat\FrameRenderer.cs" />
<Compile Include="Extensions\JavaObjectExtensions.cs" />
<Compile Include="FastRenderers\AccessibilityProvider.cs" />
<Compile Include="FastRenderers\ButtonRenderer.cs" />
<Compile Include="AppCompat\FormsViewPager.cs" />

Просмотреть файл

@ -17,6 +17,7 @@ namespace Xamarin.Forms.Platform.WinRT
public class ImageRenderer : ViewRenderer<Image, Windows.UI.Xaml.Controls.Image>
{
bool _measured;
bool _disposed;
public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
{
@ -32,10 +33,20 @@ namespace Xamarin.Forms.Platform.WinRT
protected override void Dispose(bool disposing)
{
if (Control != null)
if (_disposed)
{
Control.ImageOpened -= OnImageOpened;
Control.ImageFailed -= OnImageFailed;
return;
}
_disposed = true;
if (disposing)
{
if (Control != null)
{
Control.ImageOpened -= OnImageOpened;
Control.ImageFailed -= OnImageFailed;
}
}
base.Dispose(disposing);
@ -55,7 +66,7 @@ namespace Xamarin.Forms.Platform.WinRT
SetNativeControl(image);
}
await UpdateSource();
await TryUpdateSource();
UpdateAspect();
}
}
@ -65,7 +76,7 @@ namespace Xamarin.Forms.Platform.WinRT
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Image.SourceProperty.PropertyName)
await UpdateSource();
await TryUpdateSource();
else if (e.PropertyName == Image.AspectProperty.PropertyName)
UpdateAspect();
}
@ -94,7 +105,7 @@ namespace Xamarin.Forms.Platform.WinRT
Element?.SetIsLoading(false);
}
void OnImageFailed(object sender, ExceptionRoutedEventArgs exceptionRoutedEventArgs)
protected virtual void OnImageFailed(object sender, ExceptionRoutedEventArgs exceptionRoutedEventArgs)
{
Log.Warning("Image Loading", $"Image failed to load: {exceptionRoutedEventArgs.ErrorMessage}" );
Element?.SetIsLoading(false);
@ -107,6 +118,11 @@ namespace Xamarin.Forms.Platform.WinRT
void UpdateAspect()
{
if (_disposed || Element == null || Control == null)
{
return;
}
Control.Stretch = GetStretch(Element.Aspect);
if (Element.Aspect == Aspect.AspectFill) // Then Center Crop
{
@ -120,15 +136,40 @@ namespace Xamarin.Forms.Platform.WinRT
}
}
async Task UpdateSource()
protected virtual async Task TryUpdateSource()
{
// By default we'll just catch and log any exceptions thrown by UpdateSource so we don't bring down
// the application; a custom renderer can override this method and handle exceptions from
// UpdateSource differently if it wants to
try
{
await UpdateSource().ConfigureAwait(false);
}
catch (Exception ex)
{
Log.Warning(nameof(ImageRenderer), "Error loading image: {0}", ex);
}
finally
{
((IImageController)Element)?.SetIsLoading(false);
}
}
protected async Task UpdateSource()
{
if (_disposed || Element == null || Control == null)
{
return;
}
Element.SetIsLoading(true);
ImageSource source = Element.Source;
IImageSourceHandler handler;
if (source != null && (handler = Registrar.Registered.GetHandler<IImageSourceHandler>(source.GetType())) != null)
{
Windows.UI.Xaml.Media.ImageSource imagesource = null;
Windows.UI.Xaml.Media.ImageSource imagesource;
try
{
@ -138,10 +179,6 @@ namespace Xamarin.Forms.Platform.WinRT
{
imagesource = null;
}
catch (Exception ex)
{
Log.Warning("Image Loading", $"Error updating image source: {ex}");
}
// In the time it takes to await the imagesource, some zippy little app
// might have disposed of this Image already.
@ -155,7 +192,7 @@ namespace Xamarin.Forms.Platform.WinRT
else
{
Control.Source = null;
Element?.SetIsLoading(false);
Element.SetIsLoading(false);
}
}
}

Просмотреть файл

@ -5,6 +5,7 @@ using System.Threading;
using System.Threading.Tasks;
using Foundation;
using UIKit;
using Xamarin.Forms.Internals;
using RectangleF = CoreGraphics.CGRect;
namespace Xamarin.Forms.Platform.iOS
@ -30,8 +31,6 @@ namespace Xamarin.Forms.Platform.iOS
{
bool _isDisposed;
IElementController ElementController => Element as IElementController;
protected override void Dispose(bool disposing)
{
if (_isDisposed)
@ -43,7 +42,6 @@ namespace Xamarin.Forms.Platform.iOS
if (Control != null && (oldUIImage = Control.Image) != null)
{
oldUIImage.Dispose();
oldUIImage = null;
}
}
@ -52,7 +50,7 @@ namespace Xamarin.Forms.Platform.iOS
base.Dispose(disposing);
}
protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
protected override async void OnElementChanged(ElementChangedEventArgs<Image> e)
{
if (Control == null)
{
@ -65,18 +63,18 @@ namespace Xamarin.Forms.Platform.iOS
if (e.NewElement != null)
{
SetAspect();
SetImage(e.OldElement);
await TrySetImage(e.OldElement);
SetOpacity();
}
base.OnElementChanged(e);
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
protected override async void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Image.SourceProperty.PropertyName)
SetImage();
await TrySetImage();
else if (e.PropertyName == Image.IsOpaqueProperty.PropertyName)
SetOpacity();
else if (e.PropertyName == Image.AspectProperty.PropertyName)
@ -85,11 +83,41 @@ namespace Xamarin.Forms.Platform.iOS
void SetAspect()
{
if (_isDisposed || Element == null || Control == null)
{
return;
}
Control.ContentMode = Element.Aspect.ToUIViewContentMode();
}
async void SetImage(Image oldElement = null)
protected virtual async Task TrySetImage(Image previous = null)
{
// By default we'll just catch and log any exceptions thrown by SetImage so they don't bring down
// the application; a custom renderer can override this method and handle exceptions from
// SetImage differently if it wants to
try
{
await SetImage(previous).ConfigureAwait(false);
}
catch (Exception ex)
{
Log.Warning(nameof(ImageRenderer), "Error loading image: {0}", ex);
}
finally
{
((IImageController)Element)?.SetIsLoading(false);
}
}
protected async Task SetImage(Image oldElement = null)
{
if (_isDisposed || Element == null || Control == null)
{
return;
}
var source = Element.Source;
if (oldElement != null)
@ -108,7 +136,8 @@ namespace Xamarin.Forms.Platform.iOS
Element.SetIsLoading(true);
if (source != null && (handler = Internals.Registrar.Registered.GetHandler<IImageSourceHandler>(source.GetType())) != null)
if (source != null &&
(handler = Internals.Registrar.Registered.GetHandler<IImageSourceHandler>(source.GetType())) != null)
{
UIImage uiimage;
try
@ -120,22 +149,30 @@ namespace Xamarin.Forms.Platform.iOS
uiimage = null;
}
if (_isDisposed)
return;
var imageView = Control;
if (imageView != null)
imageView.Image = uiimage;
if (!_isDisposed)
((IVisualElementController)Element).NativeSizeChanged();
((IVisualElementController)Element).NativeSizeChanged();
}
else
{
Control.Image = null;
}
if (!_isDisposed)
Element.SetIsLoading(false);
Element.SetIsLoading(false);
}
void SetOpacity()
{
if (_isDisposed || Element == null || Control == null)
{
return;
}
Control.Opaque = Element.IsOpaque;
}
}
@ -151,12 +188,15 @@ namespace Xamarin.Forms.Platform.iOS
{
UIImage image = null;
var filesource = imagesource as FileImageSource;
if (filesource != null)
var file = filesource?.File;
if (!string.IsNullOrEmpty(file))
image = File.Exists(file) ? new UIImage(file) : UIImage.FromBundle(file);
if (image == null)
{
var file = filesource.File;
if (!string.IsNullOrEmpty(file))
image = File.Exists(file) ? new UIImage(file) : UIImage.FromBundle(file);
Log.Warning(nameof(FileImageSourceHandler), "Could not find image: {0}", imagesource);
}
return Task.FromResult(image);
}
}
@ -167,7 +207,7 @@ namespace Xamarin.Forms.Platform.iOS
{
UIImage image = null;
var streamsource = imagesource as StreamImageSource;
if (streamsource != null && streamsource.Stream != null)
if (streamsource?.Stream != null)
{
using (var streamImage = await ((IStreamImageSource)streamsource).GetStreamAsync(cancelationToken).ConfigureAwait(false))
{
@ -175,6 +215,12 @@ namespace Xamarin.Forms.Platform.iOS
image = UIImage.LoadFromData(NSData.FromStream(streamImage), scale);
}
}
if (image == null)
{
Log.Warning(nameof(StreamImagesourceHandler), "Could not load image: {0}", streamsource);
}
return image;
}
}
@ -185,7 +231,7 @@ namespace Xamarin.Forms.Platform.iOS
{
UIImage image = null;
var imageLoader = imagesource as UriImageSource;
if (imageLoader != null && imageLoader.Uri != null)
if (imageLoader?.Uri != null)
{
using (var streamImage = await imageLoader.GetStreamAsync(cancelationToken).ConfigureAwait(false))
{
@ -193,6 +239,12 @@ namespace Xamarin.Forms.Platform.iOS
image = UIImage.LoadFromData(NSData.FromStream(streamImage), scale);
}
}
if (image == null)
{
Log.Warning(nameof(ImageLoaderSourceHandler), "Could not load image: {0}", imageLoader);
}
return image;
}
}

Просмотреть файл

@ -183,5 +183,22 @@
<remarks>To be added.</remarks>
</Docs>
</Member>
<Member MemberName="ToString">
<MemberSignature Language="C#" Value="public override string ToString ();" />
<MemberSignature Language="ILAsm" Value=".method public hidebysig virtual instance string ToString() cil managed" />
<MemberType>Method</MemberType>
<AssemblyInfo>
<AssemblyVersion>2.0.0.0</AssemblyVersion>
</AssemblyInfo>
<ReturnValue>
<ReturnType>System.String</ReturnType>
</ReturnValue>
<Parameters />
<Docs>
<summary>To be added.</summary>
<returns>To be added.</returns>
<remarks>To be added.</remarks>
</Docs>
</Member>
</Members>
</Type>

Просмотреть файл

@ -114,6 +114,23 @@
<remarks>To be added.</remarks>
</Docs>
</Member>
<Member MemberName="ToString">
<MemberSignature Language="C#" Value="public override string ToString ();" />
<MemberSignature Language="ILAsm" Value=".method public hidebysig virtual instance string ToString() cil managed" />
<MemberType>Method</MemberType>
<AssemblyInfo>
<AssemblyVersion>2.0.0.0</AssemblyVersion>
</AssemblyInfo>
<ReturnValue>
<ReturnType>System.String</ReturnType>
</ReturnValue>
<Parameters />
<Docs>
<summary>To be added.</summary>
<returns>To be added.</returns>
<remarks>To be added.</remarks>
</Docs>
</Member>
<Member MemberName="Uri">
<MemberSignature Language="C#" Value="public Uri Uri { get; set; }" />
<MemberSignature Language="ILAsm" Value=".property instance class System.Uri Uri" />