Merge branch 'master' into aleader/notifications-registry

This commit is contained in:
Andrew Leader 2020-09-24 10:02:07 -07:00
Родитель a57ba761dc ce296f99b5
Коммит 0b65a73b30
348 изменённых файлов: 4648 добавлений и 10380 удалений

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

@ -14,8 +14,8 @@
<IsTestProject>$(MSBuildProjectName.Contains('Test'))</IsTestProject>
<IsUwpProject Condition="'$(IsDesignProject)' != 'true'">$(MSBuildProjectName.Contains('Uwp'))</IsUwpProject>
<IsSampleProject>$(MSBuildProjectName.Contains('Sample'))</IsSampleProject>
<DefaultTargetPlatformVersion>18362</DefaultTargetPlatformVersion>
<DefaultTargetPlatformMinVersion>16299</DefaultTargetPlatformMinVersion>
<DefaultTargetPlatformVersion>19041</DefaultTargetPlatformVersion>
<DefaultTargetPlatformMinVersion>17763</DefaultTargetPlatformMinVersion>
<PackageOutputPath>$(MSBuildThisFileDirectory)bin\nupkg</PackageOutputPath>
</PropertyGroup>

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

@ -1,6 +1,6 @@
<Project>
<Choose>
<When Condition="'$(TargetFramework)' == 'uap10.0' or '$(TargetFramework)' == 'uap10.0.16299' or '$(TargetFramework)' == 'native' or '$(TargetFramework)' == 'net461'">
<When Condition="'$(TargetFramework)' == 'uap10.0' or '$(TargetFramework)' == 'uap10.0.17763' or '$(TargetFramework)' == 'native' or '$(TargetFramework)' == 'net461'">
<!-- UAP versions for uap10.0 where TPMV isn't implied -->
<PropertyGroup>
<TargetPlatformVersion>10.0.$(DefaultTargetPlatformVersion).0</TargetPlatformVersion>
@ -15,9 +15,6 @@
<SDKReference Condition="'$(UseWindowsDesktopSdk)' == 'true' " Include="WindowsDesktop, Version=$(TargetPlatformVersion)">
<Name>Windows Desktop Extensions for the UWP</Name>
</SDKReference>
<SDKReference Condition="'$(UseWindowsMobileSdk)' == 'true' " Include="WindowsMobile, Version=$(TargetPlatformVersion)">
<Name>Windows Mobile Extensions for the UWP</Name>
</SDKReference>
</ItemGroup>
</When>
</Choose>

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

@ -11,7 +11,7 @@
<AssemblyName>GazeInputTest</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
<TargetPlatformVersion Condition=" '$(TargetPlatformVersion)' == '' ">10.0.18362.0</TargetPlatformVersion>
<TargetPlatformVersion Condition=" '$(TargetPlatformVersion)' == '' ">10.0.19041.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.17134.0</TargetPlatformMinVersion>
<MinimumVisualStudioVersion>14</MinimumVisualStudioVersion>
<FileAlignment>512</FileAlignment>

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

@ -103,6 +103,68 @@ namespace Microsoft.Toolkit.HighPerformance.Helpers
return valid;
}
/// <summary>
/// Checks whether the given value has any bytes that are set to 0.
/// That is, given a <see cref="uint"/> value, which has a total of 4 bytes,
/// it checks whether any of those have all the bits set to 0.
/// </summary>
/// <param name="value">The input value to check.</param>
/// <returns>Whether <paramref name="value"/> has any bytes set to 0.</returns>
/// <remarks>
/// This method contains no branches.
/// For more background on this subject, see <see href="https://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord"/>.
/// </remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool HasZeroByte(uint value)
{
return ((value - 0x0101_0101u) & ~value & 0x8080_8080u) != 0;
}
/// <summary>
/// Checks whether the given value has any bytes that are set to 0.
/// This method mirrors <see cref="HasZeroByte(uint)"/>, but with <see cref="ulong"/> values.
/// </summary>
/// <param name="value">The input value to check.</param>
/// <returns>Whether <paramref name="value"/> has any bytes set to 0.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool HasZeroByte(ulong value)
{
return ((value - 0x0101_0101_0101_0101ul) & ~value & 0x8080_8080_8080_8080ul) != 0;
}
/// <summary>
/// Checks whether a byte in the input <see cref="uint"/> value matches a target value.
/// </summary>
/// <param name="value">The input value to check.</param>
/// <param name="target">The target byte to look for.</param>
/// <returns>Whether <paramref name="value"/> has any bytes set to <paramref name="target"/>.</returns>
/// <remarks>
/// This method contains no branches.
/// For more info, see <see href="https://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord"/>.
/// </remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool HasByteEqualTo(uint value, byte target)
{
return HasZeroByte(value ^ (0x0101_0101u * target));
}
/// <summary>
/// Checks whether a byte in the input <see cref="uint"/> value matches a target value.
/// This method mirrors <see cref="HasByteEqualTo(uint,byte)"/>, but with <see cref="ulong"/> values.
/// </summary>
/// <param name="value">The input value to check.</param>
/// <param name="target">The target byte to look for.</param>
/// <returns>Whether <paramref name="value"/> has any bytes set to <paramref name="target"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool HasByteEqualTo(ulong value, byte target)
{
return HasZeroByte(value ^ (0x0101_0101_0101_0101u * target));
}
/// <summary>
/// Sets a bit to a specified value.
/// </summary>

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

@ -10,8 +10,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
@ -123,6 +121,11 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
/// This overload is much less efficient than <see cref="SetProperty{T}(ref T,T,string)"/> and it
/// should only be used when the former is not viable (eg. when the target property being
/// updated does not directly expose a backing field that can be passed by reference).
/// For performance reasons, it is recommended to use a stateful callback if possible through
/// the <see cref="SetProperty{TModel,T}(T,T,TModel,Action{TModel,T},string?)"/> whenever possible
/// instead of this overload, as that will allow the C# compiler to cache the input callback and
/// reduce the memory allocations. More info on that overload are available in the related XML
/// docs. This overload is here for completeness and in cases where that is not applicable.
/// </summary>
/// <typeparam name="T">The type of the property that changed.</typeparam>
/// <param name="oldValue">The current property value.</param>
@ -136,7 +139,19 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
/// </remarks>
protected bool SetProperty<T>(T oldValue, T newValue, Action<T> callback, [CallerMemberName] string? propertyName = null)
{
return SetProperty(oldValue, newValue, EqualityComparer<T>.Default, callback, propertyName);
// We avoid calling the overload again to ensure the comparison is inlined
if (EqualityComparer<T>.Default.Equals(oldValue, newValue))
{
return false;
}
OnPropertyChanging(propertyName);
callback(newValue);
OnPropertyChanged(propertyName);
return true;
}
/// <summary>
@ -197,32 +212,43 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
/// public string Name
/// {
/// get => Model.Name;
/// set => Set(() => Model.Name, value);
/// set => Set(Model.Name, value, Model, (model, name) => model.Name = name);
/// }
/// }
/// </code>
/// This way we can then use the wrapping object in our application, and all those "proxy" properties will
/// also raise notifications when changed. Note that this method is not meant to be a replacement for
/// <see cref="SetProperty{T}(ref T,T,string)"/>, which offers better performance and less memory usage. Only use this
/// overload when relaying properties to a model that doesn't support notifications, and only if you can't
/// implement notifications to that model directly (eg. by having it inherit from <see cref="ObservableObject"/>).
/// <see cref="SetProperty{T}(ref T,T,string)"/>, and it should only be used when relaying properties to a model that
/// doesn't support notifications, and only if you can't implement notifications to that model directly (eg. by having
/// it inherit from <see cref="ObservableObject"/>). The syntax relies on passing the target model and a stateless callback
/// to allow the C# compiler to cache the function, which results in much better performance and no memory usage.
/// </summary>
/// <typeparam name="T">The type of property to set.</typeparam>
/// <param name="propertyExpression">An <see cref="Expression{TDelegate}"/> returning the property to update.</param>
/// <typeparam name="TModel">The type of model whose property (or field) to set.</typeparam>
/// <typeparam name="T">The type of property (or field) to set.</typeparam>
/// <param name="oldValue">The current property value.</param>
/// <param name="newValue">The property's value after the change occurred.</param>
/// <param name="model">The model </param>
/// <param name="callback">The callback to invoke to set the target property value, if a change has occurred.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
/// <remarks>
/// The <see cref="PropertyChanging"/> and <see cref="PropertyChanged"/> events are not raised
/// if the current and new value for the target property are the same. Additionally, <paramref name="propertyExpression"/>
/// must return a property from a model that is stored as another property in the current instance.
/// This method only supports one level of indirection: <paramref name="propertyExpression"/> can only
/// be used to access properties of a model that is directly stored as a property of the current instance.
/// Additionally, this method can only be used if the wrapped item is a reference type.
/// The <see cref="PropertyChanging"/> and <see cref="PropertyChanged"/> events are not
/// raised if the current and new value for the target property are the same.
/// </remarks>
protected bool SetProperty<T>(Expression<Func<T>> propertyExpression, T newValue, [CallerMemberName] string? propertyName = null)
protected bool SetProperty<TModel, T>(T oldValue, T newValue, TModel model, Action<TModel, T> callback, [CallerMemberName] string? propertyName = null)
{
return SetProperty(propertyExpression, newValue, EqualityComparer<T>.Default, out _, propertyName);
if (EqualityComparer<T>.Default.Equals(oldValue, newValue))
{
return false;
}
OnPropertyChanging(propertyName);
callback(model, newValue);
OnPropertyChanged(propertyName);
return true;
}
/// <summary>
@ -230,56 +256,19 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
/// raises the <see cref="PropertyChanging"/> event, updates the property and then raises the
/// <see cref="PropertyChanged"/> event. The behavior mirrors that of <see cref="SetProperty{T}(ref T,T,string)"/>,
/// with the difference being that this method is used to relay properties from a wrapped model in the
/// current instance. See additional notes about this overload in <see cref="SetProperty{T}(Expression{Func{T}},T,string)"/>.
/// current instance. See additional notes about this overload in <see cref="SetProperty{TModel,T}(T,T,TModel,Action{TModel,T},string)"/>.
/// </summary>
/// <typeparam name="T">The type of property to set.</typeparam>
/// <param name="propertyExpression">An <see cref="Expression{TDelegate}"/> returning the property to update.</param>
/// <typeparam name="TModel">The type of model whose property (or field) to set.</typeparam>
/// <typeparam name="T">The type of property (or field) to set.</typeparam>
/// <param name="oldValue">The current property value.</param>
/// <param name="newValue">The property's value after the change occurred.</param>
/// <param name="comparer">The <see cref="IEqualityComparer{T}"/> instance to use to compare the input values.</param>
/// <param name="model">The model </param>
/// <param name="callback">The callback to invoke to set the target property value, if a change has occurred.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
protected bool SetProperty<T>(Expression<Func<T>> propertyExpression, T newValue, IEqualityComparer<T> comparer, [CallerMemberName] string? propertyName = null)
protected bool SetProperty<TModel, T>(T oldValue, T newValue, IEqualityComparer<T> comparer, TModel model, Action<TModel, T> callback, [CallerMemberName] string? propertyName = null)
{
return SetProperty(propertyExpression, newValue, comparer, out _, propertyName);
}
/// <summary>
/// Implements the shared logic for <see cref="SetProperty{T}(Expression{Func{T}},T,IEqualityComparer{T},string)"/>
/// </summary>
/// <typeparam name="T">The type of property to set.</typeparam>
/// <param name="propertyExpression">An <see cref="Expression{TDelegate}"/> returning the property to update.</param>
/// <param name="newValue">The property's value after the change occurred.</param>
/// <param name="comparer">The <see cref="IEqualityComparer{T}"/> instance to use to compare the input values.</param>
/// <param name="oldValue">The resulting initial value for the target property.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
private protected bool SetProperty<T>(Expression<Func<T>> propertyExpression, T newValue, IEqualityComparer<T> comparer, out T oldValue, [CallerMemberName] string? propertyName = null)
{
PropertyInfo? parentPropertyInfo;
FieldInfo? parentFieldInfo = null;
// Get the target property info
if (!(propertyExpression.Body is MemberExpression targetExpression &&
targetExpression.Member is PropertyInfo targetPropertyInfo &&
targetExpression.Expression is MemberExpression parentExpression &&
(!((parentPropertyInfo = parentExpression.Member as PropertyInfo) is null) ||
!((parentFieldInfo = parentExpression.Member as FieldInfo) is null)) &&
parentExpression.Expression is ConstantExpression instanceExpression &&
instanceExpression.Value is object instance))
{
ThrowArgumentExceptionForInvalidPropertyExpression();
// This is never executed, as the method above always throws
oldValue = default!;
return false;
}
object parent = parentPropertyInfo is null
? parentFieldInfo!.GetValue(instance)
: parentPropertyInfo.GetValue(instance);
oldValue = (T)targetPropertyInfo.GetValue(parent);
if (comparer.Equals(oldValue, newValue))
{
return false;
@ -287,7 +276,7 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
OnPropertyChanging(propertyName);
targetPropertyInfo.SetValue(parent, newValue);
callback(model, newValue);
OnPropertyChanged(propertyName);
@ -298,38 +287,36 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
/// Compares the current and new values for a given field (which should be the backing
/// field for a property). If the value has changed, raises the <see cref="PropertyChanging"/>
/// event, updates the field and then raises the <see cref="PropertyChanged"/> event.
/// The behavior mirrors that of <see cref="SetProperty{T}(ref T,T,string)"/>, with the difference being that this method
/// will also monitor the new value of the property (a generic <see cref="Task"/>) and will also
/// The behavior mirrors that of <see cref="SetProperty{T}(ref T,T,string)"/>, with the difference being that
/// this method will also monitor the new value of the property (a generic <see cref="Task"/>) and will also
/// raise the <see cref="PropertyChanged"/> again for the target property when it completes.
/// This can be used to update bindings observing that <see cref="Task"/> or any of its properties.
/// This method and its overload specifically rely on the <see cref="TaskNotifier"/> type, which needs
/// to be used in the backing field for the target <see cref="Task"/> property. The field doesn't need to be
/// initialized, as this method will take care of doing that automatically. The <see cref="TaskNotifier"/>
/// type also includes an implicit operator, so it can be assigned to any <see cref="Task"/> instance directly.
/// Here is a sample property declaration using this method:
/// <code>
/// private Task myTask;
/// private TaskNotifier myTask;
///
/// public Task MyTask
/// {
/// get => myTask;
/// private set => SetAndNotifyOnCompletion(ref myTask, () => myTask, value);
/// private set => SetAndNotifyOnCompletion(ref myTask, value);
/// }
/// </code>
/// </summary>
/// <typeparam name="TTask">The type of <see cref="Task"/> to set and monitor.</typeparam>
/// <param name="field">The field storing the property's value.</param>
/// <param name="fieldExpression">
/// An <see cref="Expression{TDelegate}"/> returning the field to update. This is needed to be
/// able to raise the <see cref="PropertyChanged"/> to notify the completion of the input task.
/// </param>
/// <param name="taskNotifier">The field notifier to modify.</param>
/// <param name="newValue">The property's value after the change occurred.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
/// <remarks>
/// The <see cref="PropertyChanging"/> and <see cref="PropertyChanged"/> events are not raised if the current
/// and new value for the target property are the same. The return value being <see langword="true"/> only
/// indicates that the new value being assigned to <paramref name="field"/> is different than the previous one,
/// and it does not mean the new <typeparamref name="TTask"/> instance passed as argument is in any particular state.
/// indicates that the new value being assigned to <paramref name="taskNotifier"/> is different than the previous one,
/// and it does not mean the new <see cref="Task"/> instance passed as argument is in any particular state.
/// </remarks>
protected bool SetPropertyAndNotifyOnCompletion<TTask>(ref TTask? field, Expression<Func<TTask?>> fieldExpression, TTask? newValue, [CallerMemberName] string? propertyName = null)
where TTask : Task
protected bool SetPropertyAndNotifyOnCompletion(ref TaskNotifier? taskNotifier, Task? newValue, [CallerMemberName] string? propertyName = null)
{
// We invoke the overload with a callback here to avoid code duplication, and simply pass an empty callback.
// The lambda expression here is transformed by the C# compiler into an empty closure class with a
@ -337,21 +324,18 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
// instance. This will result in no further allocations after the first time this method is called for a given
// generic type. We only pay the cost of the virtual call to the delegate, but this is not performance critical
// code and that overhead would still be much lower than the rest of the method anyway, so that's fine.
return SetPropertyAndNotifyOnCompletion(ref field, fieldExpression, newValue, _ => { }, propertyName);
return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new TaskNotifier(), newValue, _ => { }, propertyName);
}
/// <summary>
/// Compares the current and new values for a given field (which should be the backing
/// field for a property). If the value has changed, raises the <see cref="PropertyChanging"/>
/// event, updates the field and then raises the <see cref="PropertyChanged"/> event.
/// This method is just like <see cref="SetPropertyAndNotifyOnCompletion{TTask}(ref TTask,Expression{Func{TTask}},TTask,string)"/>,
/// This method is just like <see cref="SetPropertyAndNotifyOnCompletion(ref TaskNotifier,Task,string)"/>,
/// with the difference being an extra <see cref="Action{T}"/> parameter with a callback being invoked
/// either immediately, if the new task has already completed or is <see langword="null"/>, or upon completion.
/// </summary>
/// <typeparam name="TTask">The type of <see cref="Task"/> to set and monitor.</typeparam>
/// <param name="field">The field storing the property's value.</param>
/// <param name="fieldExpression">
/// An <see cref="Expression{TDelegate}"/> returning the field to update.</param>
/// <param name="taskNotifier">The field notifier to modify.</param>
/// <param name="newValue">The property's value after the change occurred.</param>
/// <param name="callback">A callback to invoke to update the property value.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
@ -360,10 +344,86 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
/// The <see cref="PropertyChanging"/> and <see cref="PropertyChanged"/> events are not raised
/// if the current and new value for the target property are the same.
/// </remarks>
protected bool SetPropertyAndNotifyOnCompletion<TTask>(ref TTask? field, Expression<Func<TTask?>> fieldExpression, TTask? newValue, Action<TTask?> callback, [CallerMemberName] string? propertyName = null)
protected bool SetPropertyAndNotifyOnCompletion(ref TaskNotifier? taskNotifier, Task? newValue, Action<Task?> callback, [CallerMemberName] string? propertyName = null)
{
return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new TaskNotifier(), newValue, callback, propertyName);
}
/// <summary>
/// Compares the current and new values for a given field (which should be the backing
/// field for a property). If the value has changed, raises the <see cref="PropertyChanging"/>
/// event, updates the field and then raises the <see cref="PropertyChanged"/> event.
/// The behavior mirrors that of <see cref="SetProperty{T}(ref T,T,string)"/>, with the difference being that
/// this method will also monitor the new value of the property (a generic <see cref="Task"/>) and will also
/// raise the <see cref="PropertyChanged"/> again for the target property when it completes.
/// This can be used to update bindings observing that <see cref="Task"/> or any of its properties.
/// This method and its overload specifically rely on the <see cref="TaskNotifier{T}"/> type, which needs
/// to be used in the backing field for the target <see cref="Task"/> property. The field doesn't need to be
/// initialized, as this method will take care of doing that automatically. The <see cref="TaskNotifier{T}"/>
/// type also includes an implicit operator, so it can be assigned to any <see cref="Task"/> instance directly.
/// Here is a sample property declaration using this method:
/// <code>
/// private TaskNotifier&lt;int&gt; myTask;
///
/// public Task&lt;int&gt; MyTask
/// {
/// get => myTask;
/// private set => SetAndNotifyOnCompletion(ref myTask, value);
/// }
/// </code>
/// </summary>
/// <typeparam name="T">The type of result for the <see cref="Task{TResult}"/> to set and monitor.</typeparam>
/// <param name="taskNotifier">The field notifier to modify.</param>
/// <param name="newValue">The property's value after the change occurred.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
/// <remarks>
/// The <see cref="PropertyChanging"/> and <see cref="PropertyChanged"/> events are not raised if the current
/// and new value for the target property are the same. The return value being <see langword="true"/> only
/// indicates that the new value being assigned to <paramref name="taskNotifier"/> is different than the previous one,
/// and it does not mean the new <see cref="Task{TResult}"/> instance passed as argument is in any particular state.
/// </remarks>
protected bool SetPropertyAndNotifyOnCompletion<T>(ref TaskNotifier<T>? taskNotifier, Task<T>? newValue, [CallerMemberName] string? propertyName = null)
{
return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new TaskNotifier<T>(), newValue, _ => { }, propertyName);
}
/// <summary>
/// Compares the current and new values for a given field (which should be the backing
/// field for a property). If the value has changed, raises the <see cref="PropertyChanging"/>
/// event, updates the field and then raises the <see cref="PropertyChanged"/> event.
/// This method is just like <see cref="SetPropertyAndNotifyOnCompletion{T}(ref TaskNotifier{T},Task{T},string)"/>,
/// with the difference being an extra <see cref="Action{T}"/> parameter with a callback being invoked
/// either immediately, if the new task has already completed or is <see langword="null"/>, or upon completion.
/// </summary>
/// <typeparam name="T">The type of result for the <see cref="Task{TResult}"/> to set and monitor.</typeparam>
/// <param name="taskNotifier">The field notifier to modify.</param>
/// <param name="newValue">The property's value after the change occurred.</param>
/// <param name="callback">A callback to invoke to update the property value.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
/// <remarks>
/// The <see cref="PropertyChanging"/> and <see cref="PropertyChanged"/> events are not raised
/// if the current and new value for the target property are the same.
/// </remarks>
protected bool SetPropertyAndNotifyOnCompletion<T>(ref TaskNotifier<T>? taskNotifier, Task<T>? newValue, Action<Task<T>?> callback, [CallerMemberName] string? propertyName = null)
{
return SetPropertyAndNotifyOnCompletion(taskNotifier ??= new TaskNotifier<T>(), newValue, callback, propertyName);
}
/// <summary>
/// Implements the notification logic for the related methods.
/// </summary>
/// <typeparam name="TTask">The type of <see cref="Task"/> to set and monitor.</typeparam>
/// <param name="taskNotifier">The field notifier.</param>
/// <param name="newValue">The property's value after the change occurred.</param>
/// <param name="callback">A callback to invoke to update the property value.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
private bool SetPropertyAndNotifyOnCompletion<TTask>(ITaskNotifier<TTask> taskNotifier, TTask? newValue, Action<TTask?> callback, [CallerMemberName] string? propertyName = null)
where TTask : Task
{
if (ReferenceEquals(field, newValue))
if (ReferenceEquals(taskNotifier.Task, newValue))
{
return false;
}
@ -376,7 +436,7 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
OnPropertyChanging(propertyName);
field = newValue;
taskNotifier.Task = newValue;
OnPropertyChanged(propertyName);
@ -393,16 +453,6 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
return true;
}
// Get the target field to set. This is needed because we can't
// capture the ref field in a closure (for the async method).
if (!((fieldExpression.Body as MemberExpression)?.Member is FieldInfo fieldInfo))
{
ThrowArgumentExceptionForInvalidFieldExpression();
// This is never executed, as the method above always throws
return false;
}
// We use a local async function here so that the main method can
// remain synchronous and return a value that can be immediately
// used by the caller. This mirrors Set<T>(ref T, T, string).
@ -422,10 +472,8 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
{
}
TTask? currentTask = (TTask?)fieldInfo.GetValue(this);
// Only notify if the property hasn't changed
if (ReferenceEquals(newValue, currentTask))
if (ReferenceEquals(taskNotifier.Task, newValue))
{
OnPropertyChanged(propertyName);
}
@ -439,19 +487,79 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
}
/// <summary>
/// Throws an <see cref="ArgumentException"/> when a given <see cref="Expression{TDelegate}"/> is invalid for a property.
/// An interface for task notifiers of a specified type.
/// </summary>
private static void ThrowArgumentExceptionForInvalidPropertyExpression()
/// <typeparam name="TTask">The type of value to store.</typeparam>
private interface ITaskNotifier<TTask>
where TTask : Task
{
throw new ArgumentException("The given expression must be in the form () => MyModel.MyProperty");
/// <summary>
/// Gets or sets the wrapped <typeparamref name="TTask"/> value.
/// </summary>
TTask? Task { get; set; }
}
/// <summary>
/// Throws an <see cref="ArgumentException"/> when a given <see cref="Expression{TDelegate}"/> is invalid for a property field.
/// A wrapping class that can hold a <see cref="Task"/> value.
/// </summary>
private static void ThrowArgumentExceptionForInvalidFieldExpression()
protected sealed class TaskNotifier : ITaskNotifier<Task>
{
throw new ArgumentException("The given expression must be in the form () => field");
/// <summary>
/// Initializes a new instance of the <see cref="TaskNotifier"/> class.
/// </summary>
internal TaskNotifier()
{
}
private Task? task;
/// <inheritdoc/>
Task? ITaskNotifier<Task>.Task
{
get => this.task;
set => this.task = value;
}
/// <summary>
/// Unwraps the <see cref="Task"/> value stored in the current instance.
/// </summary>
/// <param name="notifier">The input <see cref="TaskNotifier{TTask}"/> instance.</param>
public static implicit operator Task?(TaskNotifier? notifier)
{
return notifier?.task;
}
}
/// <summary>
/// A wrapping class that can hold a <see cref="Task{T}"/> value.
/// </summary>
/// <typeparam name="T">The type of value for the wrapped <see cref="Task{T}"/> instance.</typeparam>
protected sealed class TaskNotifier<T> : ITaskNotifier<Task<T>>
{
/// <summary>
/// Initializes a new instance of the <see cref="TaskNotifier{TTask}"/> class.
/// </summary>
internal TaskNotifier()
{
}
private Task<T>? task;
/// <inheritdoc/>
Task<T>? ITaskNotifier<Task<T>>.Task
{
get => this.task;
set => this.task = value;
}
/// <summary>
/// Unwraps the <see cref="Task{T}"/> value stored in the current instance.
/// </summary>
/// <param name="notifier">The input <see cref="TaskNotifier{TTask}"/> instance.</param>
public static implicit operator Task<T>?(TaskNotifier<T>? notifier)
{
return notifier?.task;
}
}
}
}

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

@ -9,7 +9,6 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.Mvvm.Messaging;
using Microsoft.Toolkit.Mvvm.Messaging.Messages;
@ -204,7 +203,14 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
/// </remarks>
protected bool SetProperty<T>(T oldValue, T newValue, Action<T> callback, bool broadcast, [CallerMemberName] string? propertyName = null)
{
return SetProperty(oldValue, newValue, EqualityComparer<T>.Default, callback, broadcast, propertyName);
bool propertyChanged = SetProperty(oldValue, newValue, callback, propertyName);
if (propertyChanged && broadcast)
{
Broadcast(oldValue, newValue, propertyName);
}
return propertyChanged;
}
/// <summary>
@ -237,40 +243,53 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
/// Compares the current and new values for a given nested property. If the value has changed,
/// raises the <see cref="ObservableObject.PropertyChanging"/> event, updates the property and then raises the
/// <see cref="ObservableObject.PropertyChanged"/> event. The behavior mirrors that of
/// <see cref="ObservableObject.SetProperty{T}(Expression{Func{T}},T,string)"/>, with the difference being that this
/// <see cref="ObservableObject.SetProperty{TModel,T}(T,T,TModel,Action{TModel,T},string)"/>, with the difference being that this
/// method is used to relay properties from a wrapped model in the current instance. For more info, see the docs for
/// <see cref="ObservableObject.SetProperty{T}(Expression{Func{T}},T,string)"/>.
/// <see cref="ObservableObject.SetProperty{TModel,T}(T,T,TModel,Action{TModel,T},string)"/>.
/// </summary>
/// <typeparam name="T">The type of property to set.</typeparam>
/// <param name="propertyExpression">An <see cref="Expression{TDelegate}"/> returning the property to update.</param>
/// <typeparam name="TModel">The type of model whose property (or field) to set.</typeparam>
/// <typeparam name="T">The type of property (or field) to set.</typeparam>
/// <param name="oldValue">The current property value.</param>
/// <param name="newValue">The property's value after the change occurred.</param>
/// <param name="model">The model </param>
/// <param name="callback">The callback to invoke to set the target property value, if a change has occurred.</param>
/// <param name="broadcast">If <see langword="true"/>, <see cref="Broadcast{T}"/> will also be invoked.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
protected bool SetProperty<T>(Expression<Func<T>> propertyExpression, T newValue, bool broadcast, [CallerMemberName] string? propertyName = null)
protected bool SetProperty<TModel, T>(T oldValue, T newValue, TModel model, Action<TModel, T> callback, bool broadcast, [CallerMemberName] string? propertyName = null)
{
return SetProperty(propertyExpression, newValue, EqualityComparer<T>.Default, broadcast, propertyName);
bool propertyChanged = SetProperty(oldValue, newValue, model, callback, propertyName);
if (propertyChanged && broadcast)
{
Broadcast(oldValue, newValue, propertyName);
}
return propertyChanged;
}
/// <summary>
/// Compares the current and new values for a given nested property. If the value has changed,
/// raises the <see cref="ObservableObject.PropertyChanging"/> event, updates the property and then raises the
/// <see cref="ObservableObject.PropertyChanged"/> event. The behavior mirrors that of
/// <see cref="ObservableObject.SetProperty{T}(Expression{Func{T}},T,IEqualityComparer{T},string)"/>,
/// <see cref="ObservableObject.SetProperty{TModel,T}(T,T,IEqualityComparer{T},TModel,Action{TModel,T},string)"/>,
/// with the difference being that this method is used to relay properties from a wrapped model in the
/// current instance. For more info, see the docs for
/// <see cref="ObservableObject.SetProperty{T}(Expression{Func{T}},T,IEqualityComparer{T},string)"/>.
/// <see cref="ObservableObject.SetProperty{TModel,T}(T,T,IEqualityComparer{T},TModel,Action{TModel,T},string)"/>.
/// </summary>
/// <typeparam name="T">The type of property to set.</typeparam>
/// <param name="propertyExpression">An <see cref="Expression{TDelegate}"/> returning the property to update.</param>
/// <typeparam name="TModel">The type of model whose property (or field) to set.</typeparam>
/// <typeparam name="T">The type of property (or field) to set.</typeparam>
/// <param name="oldValue">The current property value.</param>
/// <param name="newValue">The property's value after the change occurred.</param>
/// <param name="comparer">The <see cref="IEqualityComparer{T}"/> instance to use to compare the input values.</param>
/// <param name="model">The model </param>
/// <param name="callback">The callback to invoke to set the target property value, if a change has occurred.</param>
/// <param name="broadcast">If <see langword="true"/>, <see cref="Broadcast{T}"/> will also be invoked.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
protected bool SetProperty<T>(Expression<Func<T>> propertyExpression, T newValue, IEqualityComparer<T> comparer, bool broadcast, [CallerMemberName] string? propertyName = null)
protected bool SetProperty<TModel, T>(T oldValue, T newValue, IEqualityComparer<T> comparer, TModel model, Action<TModel, T> callback, bool broadcast, [CallerMemberName] string? propertyName = null)
{
bool propertyChanged = SetProperty(propertyExpression, newValue, comparer, out T oldValue, propertyName);
bool propertyChanged = SetProperty(oldValue, newValue, comparer, model, callback, propertyName);
if (propertyChanged && broadcast)
{

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

@ -0,0 +1,305 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Runtime.CompilerServices;
namespace Microsoft.Toolkit.Mvvm.ComponentModel
{
/// <summary>
/// A base class for objects implementing the <see cref="INotifyDataErrorInfo"/> interface. This class
/// also inherits from <see cref="ObservableObject"/>, so it can be used for observable items too.
/// </summary>
public abstract class ObservableValidator : ObservableObject, INotifyDataErrorInfo
{
/// <summary>
/// The <see cref="Dictionary{TKey,TValue}"/> instance used to store previous validation results.
/// </summary>
private readonly Dictionary<string, List<ValidationResult>> errors = new Dictionary<string, List<ValidationResult>>();
/// <inheritdoc/>
public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged;
/// <inheritdoc/>
public bool HasErrors
{
get
{
// This uses the value enumerator for Dictionary<TKey, TValue>.ValueCollection, so it doesn't
// allocate. Accessing this property is O(n), but we can stop as soon as we find at least one
// error in the whole entity, and doing this saves 8 bytes in the object size (no fields needed).
foreach (var value in this.errors.Values)
{
if (value.Count > 0)
{
return true;
}
}
return false;
}
}
/// <summary>
/// Compares the current and new values for a given property. If the value has changed,
/// raises the <see cref="ObservableObject.PropertyChanging"/> event, updates the property with
/// the new value, then raises the <see cref="ObservableObject.PropertyChanged"/> event.
/// </summary>
/// <typeparam name="T">The type of the property that changed.</typeparam>
/// <param name="field">The field storing the property's value.</param>
/// <param name="newValue">The property's value after the change occurred.</param>
/// <param name="validate">If <see langword="true"/>, <paramref name="newValue"/> will also be validated.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
/// <remarks>
/// This method is just like <see cref="ObservableObject.SetProperty{T}(ref T,T,string)"/>, just with the addition
/// of the <paramref name="validate"/> parameter. If that is set to <see langword="true"/>, the new value will be
/// validated and <see cref="ErrorsChanged"/> will be raised if needed. Following the behavior of the base method,
/// the <see cref="ObservableObject.PropertyChanging"/> and <see cref="ObservableObject.PropertyChanged"/> events
/// are not raised if the current and new value for the target property are the same.
/// </remarks>
protected bool SetProperty<T>(ref T field, T newValue, bool validate, [CallerMemberName] string? propertyName = null)
{
if (validate)
{
ValidateProperty(newValue, propertyName);
}
return SetProperty(ref field, newValue, propertyName);
}
/// <summary>
/// Compares the current and new values for a given property. If the value has changed,
/// raises the <see cref="ObservableObject.PropertyChanging"/> event, updates the property with
/// the new value, then raises the <see cref="ObservableObject.PropertyChanged"/> event.
/// See additional notes about this overload in <see cref="SetProperty{T}(ref T,T,bool,string)"/>.
/// </summary>
/// <typeparam name="T">The type of the property that changed.</typeparam>
/// <param name="field">The field storing the property's value.</param>
/// <param name="newValue">The property's value after the change occurred.</param>
/// <param name="comparer">The <see cref="IEqualityComparer{T}"/> instance to use to compare the input values.</param>
/// <param name="validate">If <see langword="true"/>, <paramref name="newValue"/> will also be validated.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
protected bool SetProperty<T>(ref T field, T newValue, IEqualityComparer<T> comparer, bool validate, [CallerMemberName] string? propertyName = null)
{
if (validate)
{
ValidateProperty(newValue, propertyName);
}
return SetProperty(ref field, newValue, comparer, propertyName);
}
/// <summary>
/// Compares the current and new values for a given property. If the value has changed,
/// raises the <see cref="ObservableObject.PropertyChanging"/> event, updates the property with
/// the new value, then raises the <see cref="ObservableObject.PropertyChanged"/> event. Similarly to
/// the <see cref="ObservableObject.SetProperty{T}(T,T,Action{T},string)"/> method, this overload should only be
/// used when <see cref="ObservableObject.SetProperty{T}(ref T,T,string)"/> can't be used directly.
/// </summary>
/// <typeparam name="T">The type of the property that changed.</typeparam>
/// <param name="oldValue">The current property value.</param>
/// <param name="newValue">The property's value after the change occurred.</param>
/// <param name="callback">A callback to invoke to update the property value.</param>
/// <param name="validate">If <see langword="true"/>, <paramref name="newValue"/> will also be validated.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
/// <remarks>
/// This method is just like <see cref="ObservableObject.SetProperty{T}(T,T,Action{T},string)"/>, just with the addition
/// of the <paramref name="validate"/> parameter. As such, following the behavior of the base method,
/// the <see cref="ObservableObject.PropertyChanging"/> and <see cref="ObservableObject.PropertyChanged"/> events
/// are not raised if the current and new value for the target property are the same.
/// </remarks>
protected bool SetProperty<T>(T oldValue, T newValue, Action<T> callback, bool validate, [CallerMemberName] string? propertyName = null)
{
if (validate)
{
ValidateProperty(newValue, propertyName);
}
return SetProperty(oldValue, newValue, callback, propertyName);
}
/// <summary>
/// Compares the current and new values for a given property. If the value has changed,
/// raises the <see cref="ObservableObject.PropertyChanging"/> event, updates the property with
/// the new value, then raises the <see cref="ObservableObject.PropertyChanged"/> event.
/// See additional notes about this overload in <see cref="SetProperty{T}(T,T,Action{T},bool,string)"/>.
/// </summary>
/// <typeparam name="T">The type of the property that changed.</typeparam>
/// <param name="oldValue">The current property value.</param>
/// <param name="newValue">The property's value after the change occurred.</param>
/// <param name="comparer">The <see cref="IEqualityComparer{T}"/> instance to use to compare the input values.</param>
/// <param name="callback">A callback to invoke to update the property value.</param>
/// <param name="validate">If <see langword="true"/>, <paramref name="newValue"/> will also be validated.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
protected bool SetProperty<T>(T oldValue, T newValue, IEqualityComparer<T> comparer, Action<T> callback, bool validate, [CallerMemberName] string? propertyName = null)
{
if (validate)
{
ValidateProperty(newValue, propertyName);
}
return SetProperty(oldValue, newValue, comparer, callback, propertyName);
}
/// <summary>
/// Compares the current and new values for a given nested property. If the value has changed,
/// raises the <see cref="ObservableObject.PropertyChanging"/> event, updates the property and then raises the
/// <see cref="ObservableObject.PropertyChanged"/> event. The behavior mirrors that of
/// <see cref="ObservableObject.SetProperty{TModel,T}(T,T,TModel,Action{TModel,T},string)"/>, with the difference being that this
/// method is used to relay properties from a wrapped model in the current instance. For more info, see the docs for
/// <see cref="ObservableObject.SetProperty{TModel,T}(T,T,TModel,Action{TModel,T},string)"/>.
/// </summary>
/// <typeparam name="TModel">The type of model whose property (or field) to set.</typeparam>
/// <typeparam name="T">The type of property (or field) to set.</typeparam>
/// <param name="oldValue">The current property value.</param>
/// <param name="newValue">The property's value after the change occurred.</param>
/// <param name="model">The model </param>
/// <param name="callback">The callback to invoke to set the target property value, if a change has occurred.</param>
/// <param name="validate">If <see langword="true"/>, <paramref name="newValue"/> will also be validated.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
protected bool SetProperty<TModel, T>(T oldValue, T newValue, TModel model, Action<TModel, T> callback, bool validate, [CallerMemberName] string? propertyName = null)
{
if (validate)
{
ValidateProperty(newValue, propertyName);
}
return SetProperty(oldValue, newValue, model, callback, propertyName);
}
/// <summary>
/// Compares the current and new values for a given nested property. If the value has changed,
/// raises the <see cref="ObservableObject.PropertyChanging"/> event, updates the property and then raises the
/// <see cref="ObservableObject.PropertyChanged"/> event. The behavior mirrors that of
/// <see cref="ObservableObject.SetProperty{TModel,T}(T,T,IEqualityComparer{T},TModel,Action{TModel,T},string)"/>,
/// with the difference being that this method is used to relay properties from a wrapped model in the
/// current instance. For more info, see the docs for
/// <see cref="ObservableObject.SetProperty{TModel,T}(T,T,IEqualityComparer{T},TModel,Action{TModel,T},string)"/>.
/// </summary>
/// <typeparam name="TModel">The type of model whose property (or field) to set.</typeparam>
/// <typeparam name="T">The type of property (or field) to set.</typeparam>
/// <param name="oldValue">The current property value.</param>
/// <param name="newValue">The property's value after the change occurred.</param>
/// <param name="comparer">The <see cref="IEqualityComparer{T}"/> instance to use to compare the input values.</param>
/// <param name="model">The model </param>
/// <param name="callback">The callback to invoke to set the target property value, if a change has occurred.</param>
/// <param name="validate">If <see langword="true"/>, <paramref name="newValue"/> will also be validated.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
protected bool SetProperty<TModel, T>(T oldValue, T newValue, IEqualityComparer<T> comparer, TModel model, Action<TModel, T> callback, bool validate, [CallerMemberName] string? propertyName = null)
{
if (validate)
{
ValidateProperty(newValue, propertyName);
}
return SetProperty(oldValue, newValue, comparer, model, callback, propertyName);
}
/// <inheritdoc/>
[Pure]
public IEnumerable GetErrors(string? propertyName)
{
// Entity-level errors when the target property is null or empty
if (string.IsNullOrEmpty(propertyName))
{
return this.GetAllErrors();
}
// Property-level errors, if any
if (this.errors.TryGetValue(propertyName!, out List<ValidationResult> errors))
{
return errors;
}
// The INotifyDataErrorInfo.GetErrors method doesn't specify exactly what to
// return when the input property name is invalid, but given that the return
// type is marked as a non-nullable reference type, here we're returning an
// empty array to respect the contract. This also matches the behavior of
// this method whenever errors for a valid properties are retrieved.
return Array.Empty<ValidationResult>();
}
/// <summary>
/// Implements the logic for entity-level errors gathering for <see cref="GetErrors"/>.
/// </summary>
/// <returns>An <see cref="IEnumerable"/> instance with all the errors in <see cref="errors"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.NoInlining)]
private IEnumerable GetAllErrors()
{
return this.errors.Values.SelectMany(errors => errors);
}
/// <summary>
/// Validates a property with a specified name and a given input value.
/// </summary>
/// <param name="value">The value to test for the specified property.</param>
/// <param name="propertyName">The name of the property to validate.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="propertyName"/> is <see langword="null"/>.</exception>
private void ValidateProperty(object? value, string? propertyName)
{
if (propertyName is null)
{
ThrowArgumentNullExceptionForNullPropertyName();
}
// Check if the property had already been previously validated, and if so retrieve
// the reusable list of validation errors from the errors dictionary. This list is
// used to add new validation errors below, if any are produced by the validator.
// If the property isn't present in the dictionary, add it now to avoid allocations.
if (!this.errors.TryGetValue(propertyName!, out List<ValidationResult>? propertyErrors))
{
propertyErrors = new List<ValidationResult>();
this.errors.Add(propertyName!, propertyErrors);
}
bool errorsChanged = false;
// Clear the errors for the specified property, if any
if (propertyErrors.Count > 0)
{
propertyErrors.Clear();
errorsChanged = true;
}
// Validate the property, by adding new errors to the existing list
bool isValid = Validator.TryValidateProperty(
value,
new ValidationContext(this, null, null) { MemberName = propertyName },
propertyErrors);
// Only raise the event once if needed. This happens either when the target property
// had existing errors and is now valid, or if the validation has failed and there are
// new errors to broadcast, regardless of the previous validation state for the property.
if (errorsChanged || !isValid)
{
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
}
#pragma warning disable SA1204
/// <summary>
/// Throws an <see cref="ArgumentNullException"/> when a property name given as input is <see langword="null"/>.
/// </summary>
private static void ThrowArgumentNullExceptionForNullPropertyName()
{
throw new ArgumentNullException("propertyName", "The input property name cannot be null when validating a property");
}
#pragma warning restore SA1204
}
}

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

@ -1,145 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading;
using Microsoft.Extensions.DependencyInjection;
#nullable enable
namespace Microsoft.Toolkit.Mvvm.DependencyInjection
{
/// <summary>
/// A type that facilitates the use of the <see cref="IServiceProvider"/> type.
/// The <see cref="Ioc"/> provides the ability to configure services in a singleton, thread-safe
/// service provider instance, which can then be used to resolve service instances.
/// The first step to use this feature is to declare some services, for instance:
/// <code>
/// public interface ILogger
/// {
/// void Log(string text);
/// }
/// </code>
/// <code>
/// public class ConsoleLogger : ILogger
/// {
/// void Log(string text) => Console.WriteLine(text);
/// }
/// </code>
/// Then the services configuration should then be done at startup, by calling one of
/// the available <see cref="ConfigureServices(IServiceCollection)"/> overloads, like so:
/// <code>
/// Ioc.Default.ConfigureServices(services =>
/// {
/// services.AddSingleton&lt;ILogger, Logger&gt;();
/// });
/// </code>
/// Finally, you can use the <see cref="Ioc"/> instance (which implements <see cref="IServiceProvider"/>)
/// to retrieve the service instances from anywhere in your application, by doing as follows:
/// <code>
/// Ioc.Default.GetService&lt;ILogger&gt;().Log("Hello world!");
/// </code>
/// </summary>
public sealed class Ioc : IServiceProvider
{
/// <summary>
/// Gets the default <see cref="Ioc"/> instance.
/// </summary>
public static Ioc Default { get; } = new Ioc();
/// <summary>
/// The <see cref="ServiceProvider"/> instance to use, if initialized.
/// </summary>
private volatile ServiceProvider? serviceProvider;
/// <inheritdoc/>
object? IServiceProvider.GetService(Type serviceType)
{
// As per section I.12.6.6 of the official CLI ECMA-335 spec:
// "[...] read and write access to properly aligned memory locations no larger than the native
// word size is atomic when all the write accesses to a location are the same size. Atomic writes
// shall alter no bits other than those written. Unless explicit layout control is used [...],
// data elements no larger than the natural word size [...] shall be properly aligned.
// Object references shall be treated as though they are stored in the native word size."
// The field being accessed here is of native int size (reference type), and is only ever accessed
// directly and atomically by a compare exchange instruction (see below), or here. We can therefore
// assume this read is thread safe with respect to accesses to this property or to invocations to one
// of the available configuration methods. So we can just read the field directly and make the necessary
// check with our local copy, without the need of paying the locking overhead from this get accessor.
ServiceProvider? provider = this.serviceProvider;
if (provider is null)
{
ThrowInvalidOperationExceptionForMissingInitialization();
}
return provider!.GetService(serviceType);
}
/// <summary>
/// Initializes the shared <see cref="IServiceProvider"/> instance.
/// </summary>
/// <param name="setup">The configuration delegate to use to add services.</param>
public void ConfigureServices(Action<IServiceCollection> setup)
{
ConfigureServices(setup, new ServiceProviderOptions());
}
/// <summary>
/// Initializes the shared <see cref="IServiceProvider"/> instance.
/// </summary>
/// <param name="setup">The configuration delegate to use to add services.</param>
/// <param name="options">The <see cref="ServiceProviderOptions"/> instance to configure the service provider behaviors.</param>
public void ConfigureServices(Action<IServiceCollection> setup, ServiceProviderOptions options)
{
var collection = new ServiceCollection();
setup(collection);
ConfigureServices(collection, options);
}
/// <summary>
/// Initializes the shared <see cref="IServiceProvider"/> instance.
/// </summary>
/// <param name="services">The input <see cref="IServiceCollection"/> instance to use.</param>
public void ConfigureServices(IServiceCollection services)
{
ConfigureServices(services, new ServiceProviderOptions());
}
/// <summary>
/// Initializes the shared <see cref="IServiceProvider"/> instance.
/// </summary>
/// <param name="services">The input <see cref="IServiceCollection"/> instance to use.</param>
/// <param name="options">The <see cref="ServiceProviderOptions"/> instance to configure the service provider behaviors.</param>
public void ConfigureServices(IServiceCollection services, ServiceProviderOptions options)
{
ServiceProvider newServices = services.BuildServiceProvider(options);
ServiceProvider? oldServices = Interlocked.CompareExchange(ref this.serviceProvider, newServices, null);
if (!(oldServices is null))
{
ThrowInvalidOperationExceptionForRepeatedConfiguration();
}
}
/// <summary>
/// Throws an <see cref="InvalidOperationException"/> when the <see cref="ServiceProvider"/> property is used before initialization.
/// </summary>
private static void ThrowInvalidOperationExceptionForMissingInitialization()
{
throw new InvalidOperationException("The service provider has not been configured yet");
}
/// <summary>
/// Throws an <see cref="InvalidOperationException"/> when a configuration is attempted more than once.
/// </summary>
private static void ThrowInvalidOperationExceptionForRepeatedConfiguration()
{
throw new InvalidOperationException("The default service provider has already been configured");
}
}
}

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

@ -4,6 +4,7 @@
using System;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Toolkit.Mvvm.ComponentModel;
@ -20,13 +21,25 @@ namespace Microsoft.Toolkit.Mvvm.Input
/// <summary>
/// The <see cref="Func{TResult}"/> to invoke when <see cref="Execute"/> is used.
/// </summary>
private readonly Func<Task> execute;
private readonly Func<Task>? execute;
/// <summary>
/// The cancelable <see cref="Func{T,TResult}"/> to invoke when <see cref="Execute"/> is used.
/// </summary>
/// <remarks>Only one between this and <see cref="execute"/> is not <see langword="null"/>.</remarks>
private readonly Func<CancellationToken, Task>? cancelableExecute;
/// <summary>
/// The optional action to invoke when <see cref="CanExecute"/> is used.
/// </summary>
private readonly Func<bool>? canExecute;
/// <summary>
/// The <see cref="CancellationTokenSource"/> instance to use to cancel <see cref="cancelableExecute"/>.
/// </summary>
/// <remarks>This is only used when <see cref="cancelableExecute"/> is not <see langword="null"/>.</remarks>
private CancellationTokenSource? cancellationTokenSource;
/// <inheritdoc/>
public event EventHandler? CanExecuteChanged;
@ -42,6 +55,15 @@ namespace Microsoft.Toolkit.Mvvm.Input
/// <summary>
/// Initializes a new instance of the <see cref="AsyncRelayCommand"/> class that can always execute.
/// </summary>
/// <param name="cancelableExecute">The cancelable execution logic.</param>
public AsyncRelayCommand(Func<CancellationToken, Task> cancelableExecute)
{
this.cancelableExecute = cancelableExecute;
}
/// <summary>
/// Initializes a new instance of the <see cref="AsyncRelayCommand"/> class.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public AsyncRelayCommand(Func<Task> execute, Func<bool> canExecute)
@ -50,7 +72,18 @@ namespace Microsoft.Toolkit.Mvvm.Input
this.canExecute = canExecute;
}
private Task? executionTask;
/// <summary>
/// Initializes a new instance of the <see cref="AsyncRelayCommand"/> class.
/// </summary>
/// <param name="cancelableExecute">The cancelable execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public AsyncRelayCommand(Func<CancellationToken, Task> cancelableExecute, Func<bool> canExecute)
{
this.cancelableExecute = cancelableExecute;
this.canExecute = canExecute;
}
private TaskNotifier? executionTask;
/// <inheritdoc/>
public Task? ExecutionTask
@ -58,13 +91,19 @@ namespace Microsoft.Toolkit.Mvvm.Input
get => this.executionTask;
private set
{
if (SetPropertyAndNotifyOnCompletion(ref this.executionTask, () => this.executionTask, value, _ => OnPropertyChanged(nameof(IsRunning))))
if (SetPropertyAndNotifyOnCompletion(ref this.executionTask, value, _ => OnPropertyChanged(nameof(IsRunning))))
{
OnPropertyChanged(nameof(IsRunning));
}
}
}
/// <inheritdoc/>
public bool CanBeCanceled => !(this.cancelableExecute is null);
/// <inheritdoc/>
public bool IsCancellationRequested => this.cancellationTokenSource?.IsCancellationRequested == true;
/// <inheritdoc/>
public bool IsRunning => ExecutionTask?.IsCompleted == false;
@ -92,10 +131,32 @@ namespace Microsoft.Toolkit.Mvvm.Input
{
if (CanExecute(parameter))
{
return ExecutionTask = this.execute();
// Non cancelable command delegate
if (!(this.execute is null))
{
return ExecutionTask = this.execute();
}
// Cancel the previous operation, if one is pending
this.cancellationTokenSource?.Cancel();
var cancellationTokenSource = this.cancellationTokenSource = new CancellationTokenSource();
OnPropertyChanged(nameof(IsCancellationRequested));
// Invoke the cancelable command delegate with a new linked token
return ExecutionTask = this.cancelableExecute!(cancellationTokenSource.Token);
}
return Task.CompletedTask;
}
/// <inheritdoc/>
public void Cancel()
{
this.cancellationTokenSource?.Cancel();
OnPropertyChanged(nameof(IsCancellationRequested));
}
}
}

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

@ -4,6 +4,7 @@
using System;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Toolkit.Mvvm.ComponentModel;
@ -18,13 +19,23 @@ namespace Microsoft.Toolkit.Mvvm.Input
/// <summary>
/// The <see cref="Func{TResult}"/> to invoke when <see cref="Execute(T)"/> is used.
/// </summary>
private readonly Func<T, Task> execute;
private readonly Func<T, Task>? execute;
/// <summary>
/// The cancelable <see cref="Func{T1,T2,TResult}"/> to invoke when <see cref="Execute(object?)"/> is used.
/// </summary>
private readonly Func<T, CancellationToken, Task>? cancelableExecute;
/// <summary>
/// The optional action to invoke when <see cref="CanExecute(T)"/> is used.
/// </summary>
private readonly Func<T, bool>? canExecute;
/// <summary>
/// The <see cref="CancellationTokenSource"/> instance to use to cancel <see cref="cancelableExecute"/>.
/// </summary>
private CancellationTokenSource? cancellationTokenSource;
/// <inheritdoc/>
public event EventHandler? CanExecuteChanged;
@ -41,6 +52,16 @@ namespace Microsoft.Toolkit.Mvvm.Input
/// <summary>
/// Initializes a new instance of the <see cref="AsyncRelayCommand{T}"/> class that can always execute.
/// </summary>
/// <param name="cancelableExecute">The cancelable execution logic.</param>
/// <remarks>See notes in <see cref="RelayCommand{T}(Action{T})"/>.</remarks>
public AsyncRelayCommand(Func<T, CancellationToken, Task> cancelableExecute)
{
this.cancelableExecute = cancelableExecute;
}
/// <summary>
/// Initializes a new instance of the <see cref="AsyncRelayCommand{T}"/> class.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
/// <remarks>See notes in <see cref="RelayCommand{T}(Action{T})"/>.</remarks>
@ -50,7 +71,19 @@ namespace Microsoft.Toolkit.Mvvm.Input
this.canExecute = canExecute;
}
private Task? executionTask;
/// <summary>
/// Initializes a new instance of the <see cref="AsyncRelayCommand{T}"/> class.
/// </summary>
/// <param name="cancelableExecute">The cancelable execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
/// <remarks>See notes in <see cref="RelayCommand{T}(Action{T})"/>.</remarks>
public AsyncRelayCommand(Func<T, CancellationToken, Task> cancelableExecute, Func<T, bool> canExecute)
{
this.cancelableExecute = cancelableExecute;
this.canExecute = canExecute;
}
private TaskNotifier? executionTask;
/// <inheritdoc/>
public Task? ExecutionTask
@ -58,13 +91,19 @@ namespace Microsoft.Toolkit.Mvvm.Input
get => this.executionTask;
private set
{
if (SetPropertyAndNotifyOnCompletion(ref this.executionTask, () => this.executionTask, value, _ => OnPropertyChanged(nameof(IsRunning))))
if (SetPropertyAndNotifyOnCompletion(ref this.executionTask, value, _ => OnPropertyChanged(nameof(IsRunning))))
{
OnPropertyChanged(nameof(IsRunning));
}
}
}
/// <inheritdoc/>
public bool CanBeCanceled => !(this.cancelableExecute is null);
/// <inheritdoc/>
public bool IsCancellationRequested => this.cancellationTokenSource?.IsCancellationRequested == true;
/// <inheritdoc/>
public bool IsRunning => ExecutionTask?.IsCompleted == false;
@ -113,7 +152,21 @@ namespace Microsoft.Toolkit.Mvvm.Input
{
if (CanExecute(parameter))
{
return ExecutionTask = this.execute(parameter);
// Non cancelable command delegate
if (!(this.execute is null))
{
return ExecutionTask = this.execute(parameter);
}
// Cancel the previous operation, if one is pending
this.cancellationTokenSource?.Cancel();
var cancellationTokenSource = this.cancellationTokenSource = new CancellationTokenSource();
OnPropertyChanged(nameof(IsCancellationRequested));
// Invoke the cancelable command delegate with a new linked token
return ExecutionTask = this.cancelableExecute!(parameter, cancellationTokenSource.Token);
}
return Task.CompletedTask;
@ -124,5 +177,13 @@ namespace Microsoft.Toolkit.Mvvm.Input
{
return ExecuteAsync((T)parameter!);
}
/// <inheritdoc/>
public void Cancel()
{
this.cancellationTokenSource?.Cancel();
OnPropertyChanged(nameof(IsCancellationRequested));
}
}
}

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

@ -18,6 +18,16 @@ namespace Microsoft.Toolkit.Mvvm.Input
/// </summary>
Task? ExecutionTask { get; }
/// <summary>
/// Gets a value indicating whether running operations for this command can be canceled.
/// </summary>
bool CanBeCanceled { get; }
/// <summary>
/// Gets a value indicating whether a cancelation request has been issued for the current operation.
/// </summary>
bool IsCancellationRequested { get; }
/// <summary>
/// Gets a value indicating whether the command currently has a pending operation being executed.
/// </summary>
@ -30,5 +40,14 @@ namespace Microsoft.Toolkit.Mvvm.Input
/// <param name="parameter">The input parameter.</param>
/// <returns>The <see cref="Task"/> representing the async operation being executed.</returns>
Task ExecuteAsync(object? parameter);
/// <summary>
/// Communicates a request for cancelation.
/// </summary>
/// <remarks>
/// If the underlying command is not running, or if it does not support cancelation, this method will perform no action.
/// Note that even with a successful cancelation, the completion of the current operation might not be immediate.
/// </remarks>
void Cancel();
}
}

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

@ -16,8 +16,9 @@
<PackageTags>UWP Toolkit Windows MVVM MVVMToolkit observable Ioc dependency injection services extensions helpers</PackageTags>
</PropertyGroup>
<!-- .NET Standard 2.0 doesn't have the Span<T> type -->
<!-- .NET Standard 2.0 doesn't have the Span<T> and IAsyncEnumerable<T> types -->
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="1.1.1" />
<PackageReference Include="System.Memory" Version="4.5.4" />
</ItemGroup>
@ -27,7 +28,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.5" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" />
</ItemGroup>
</Project>

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

@ -8,10 +8,8 @@
Markdown: Allows you to parse a Markdown String into a Markdown Document, and then Render it with a Markdown Renderer.
RSS: Allows you to parse an RSS content String into an RSS Schema.
</Description>
<PackageTags>UWP Toolkit Windows Parsers Parsing Markdown RSS</PackageTags>
<PackageTags>UWP Toolkit Windows Parsers Parsing Markdown</PackageTags>
</PropertyGroup>
<ItemGroup>

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

@ -1,136 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Xml.Linq;
using Microsoft.Toolkit.Extensions;
namespace Microsoft.Toolkit.Parsers.Rss
{
/// <summary>
/// Parser for Atom endpoints.
/// </summary>
internal class AtomParser : BaseRssParser
{
/// <summary>
/// Atom reader implementation to parse Atom content.
/// </summary>
/// <param name="doc">XDocument to parse.</param>
/// <returns>Strong typed response.</returns>
public override IEnumerable<RssSchema> LoadFeed(XDocument doc)
{
Collection<RssSchema> feed = new Collection<RssSchema>();
if (doc.Root == null)
{
return feed;
}
var items = doc.Root.Elements(doc.Root.GetDefaultNamespace() + "entry").Select(item => GetRssSchema(item)).ToList<RssSchema>();
feed = new Collection<RssSchema>(items);
return feed;
}
/// <summary>
/// Retrieves strong type for passed item.
/// </summary>
/// <param name="item">XElement to parse.</param>
/// <returns>Strong typed object.</returns>
private static RssSchema GetRssSchema(XElement item)
{
RssSchema rssItem = new RssSchema
{
Author = GetItemAuthor(item),
Title = item.GetSafeElementString("title").Trim().DecodeHtml(),
ImageUrl = GetItemImage(item),
PublishDate = item.GetSafeElementDate("published"),
FeedUrl = item.GetLink("alternate"),
};
var content = GetItemContent(item);
// Removes scripts from html
if (!string.IsNullOrEmpty(content))
{
rssItem.Summary = ProcessHtmlSummary(content);
rssItem.Content = ProcessHtmlContent(content);
}
string id = item.GetSafeElementString("guid").Trim();
if (string.IsNullOrEmpty(id))
{
id = item.GetSafeElementString("id").Trim();
if (string.IsNullOrEmpty(id))
{
id = rssItem.FeedUrl;
}
}
rssItem.InternalID = id;
return rssItem;
}
/// <summary>
/// Retrieves item author from XElement.
/// </summary>
/// <param name="item">XElement item.</param>
/// <returns>String of Item Author.</returns>
private static string GetItemAuthor(XElement item)
{
var content = string.Empty;
if (item != null && item.Element(item.GetDefaultNamespace() + "author") != null)
{
content = item.Element(item.GetDefaultNamespace() + "author").GetSafeElementString("name");
}
if (string.IsNullOrEmpty(content))
{
content = item.GetSafeElementString("author");
}
return content;
}
/// <summary>
/// Returns item image from XElement item.
/// </summary>
/// <param name="item">XElement item.</param>
/// <returns>String pointing to item image.</returns>
private static string GetItemImage(XElement item)
{
if (!string.IsNullOrEmpty(item.GetSafeElementString("image")))
{
return item.GetSafeElementString("image");
}
return item.GetImage();
}
/// <summary>
/// Returns item content from XElement item.
/// </summary>
/// <param name="item">XElement item.</param>
/// <returns>String of item content.</returns>
private static string GetItemContent(XElement item)
{
var content = item.GetSafeElementString("description");
if (string.IsNullOrEmpty(content))
{
content = item.GetSafeElementString("content");
}
if (string.IsNullOrEmpty(content))
{
content = item.GetSafeElementString("summary");
}
return content;
}
}
}

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

@ -1,59 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Xml.Linq;
using Microsoft.Toolkit.Extensions;
namespace Microsoft.Toolkit.Parsers.Rss
{
/// <summary>
/// Base class for RSS Parser(s).
/// </summary>
internal abstract class BaseRssParser
{
/// <summary>
/// Retrieve feed type from XDocument.
/// </summary>
/// <param name="doc">XDocument doc.</param>
/// <returns>Return feed type.</returns>
public static RssType GetFeedType(XDocument doc)
{
if (doc.Root == null)
{
return RssType.Unknown;
}
XNamespace defaultNamespace = doc.Root.GetDefaultNamespace();
return defaultNamespace.NamespaceName.EndsWith("Atom") ? RssType.Atom : RssType.Rss;
}
/// <summary>
/// Abstract method to be override by specific implementations of the reader.
/// </summary>
/// <param name="doc">XDocument doc.</param>
/// <returns>Returns list of strongly typed results.</returns>
public abstract IEnumerable<RssSchema> LoadFeed(XDocument doc);
/// <summary>
/// Fix up the HTML content.
/// </summary>
/// <param name="htmlContent">Content to be fixed up.</param>
/// <returns>Fixed up content.</returns>
protected internal static string ProcessHtmlContent(string htmlContent)
{
return htmlContent.FixHtml().SanitizeString();
}
/// <summary>
/// Create a summary of the HTML content.
/// </summary>
/// <param name="htmlContent">Content to be processed.</param>
/// <returns>Summary of the content.</returns>
protected internal static string ProcessHtmlSummary(string htmlContent)
{
return htmlContent.DecodeHtml().Trim().Truncate(500).SanitizeString();
}
}
}

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

@ -1,27 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.Toolkit.Parsers.Rss
{
/// <summary>
/// Type of RSS.
/// </summary>
internal enum RssType
{
/// <summary>
/// Atom
/// </summary>
Atom,
/// <summary>
/// RSS
/// </summary>
Rss,
/// <summary>
/// Unknown
/// </summary>
Unknown
}
}

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

@ -1,214 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Xml.Linq;
using Microsoft.Toolkit.Extensions;
namespace Microsoft.Toolkit.Parsers.Rss
{
/// <summary>
/// RSS reader implementation to parse RSS content.
/// </summary>
internal class Rss2Parser : BaseRssParser
{
/// <summary>
/// RDF Namespace Uri.
/// </summary>
private static readonly XNamespace NsRdfNamespaceUri = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
/// <summary>
/// RDF Elements Namespace Uri.
/// </summary>
private static readonly XNamespace NsRdfElementsNamespaceUri = "http://purl.org/dc/elements/1.1/";
/// <summary>
/// RDF Content Namespace Uri.
/// </summary>
private static readonly XNamespace NsRdfContentNamespaceUri = "http://purl.org/rss/1.0/modules/content/";
/// <summary>
/// This override load and parses the document and return a list of RssSchema values.
/// </summary>
/// <param name="doc">XDocument to be loaded.</param>
/// <returns>Strongly typed list of feeds.</returns>
public override IEnumerable<RssSchema> LoadFeed(XDocument doc)
{
bool isRDF = false;
var feed = new Collection<RssSchema>();
XNamespace defaultNamespace = string.Empty;
if (doc.Root != null)
{
isRDF = doc.Root.Name == (NsRdfNamespaceUri + "RDF");
defaultNamespace = doc.Root.GetDefaultNamespace();
}
foreach (var item in doc.Descendants(defaultNamespace + "item"))
{
var rssItem = isRDF ? ParseRDFItem(item) : ParseRssItem(item);
feed.Add(rssItem);
}
return feed;
}
/// <summary>
/// Parses XElement item into strong typed object.
/// </summary>
/// <param name="item">XElement item to parse.</param>
/// <returns>Strong typed object.</returns>
private static RssSchema ParseItem(XElement item)
{
var rssItem = new RssSchema();
rssItem.Title = item.GetSafeElementString("title").Trim().DecodeHtml();
rssItem.FeedUrl = item.GetSafeElementString("link");
rssItem.Author = GetItemAuthor(item);
string content = item.GetSafeElementString("encoded", NsRdfContentNamespaceUri);
if (string.IsNullOrEmpty(content))
{
content = item.GetSafeElementString("description");
if (string.IsNullOrEmpty(content))
{
content = item.GetSafeElementString("content");
}
}
var summary = item.GetSafeElementString("description");
if (string.IsNullOrEmpty(summary))
{
summary = item.GetSafeElementString("encoded", NsRdfContentNamespaceUri);
}
// Removes scripts from html
if (!string.IsNullOrEmpty(summary))
{
rssItem.Summary = ProcessHtmlSummary(summary);
}
if (!string.IsNullOrEmpty(content))
{
rssItem.Content = ProcessHtmlContent(content);
}
string id = item.GetSafeElementString("guid").Trim();
if (string.IsNullOrEmpty(id))
{
id = item.GetSafeElementString("id").Trim();
if (string.IsNullOrEmpty(id))
{
id = rssItem.FeedUrl;
}
}
rssItem.InternalID = id;
return rssItem;
}
/// <summary>
/// Parses RSS version 1.0 objects.
/// </summary>
/// <param name="item">XElement item.</param>
/// <returns>Strong typed object.</returns>
private static RssSchema ParseRDFItem(XElement item)
{
XNamespace ns = "http://search.yahoo.com/mrss/";
var rssItem = ParseItem(item);
rssItem.PublishDate = item.GetSafeElementDate("date", NsRdfElementsNamespaceUri);
string image = item.GetSafeElementString("image");
if (string.IsNullOrEmpty(image) && item.Elements(ns + "thumbnail").LastOrDefault() != null)
{
var element = item.Elements(ns + "thumbnail").Last();
image = element.Attribute("url").Value;
}
if (string.IsNullOrEmpty(image) && item.ToString().Contains("thumbnail"))
{
image = item.GetSafeElementString("thumbnail");
}
if (string.IsNullOrEmpty(image))
{
image = item.GetImage();
}
rssItem.ImageUrl = image;
return rssItem;
}
/// <summary>
/// Parses RSS version 2.0 objects.
/// </summary>
/// <param name="item">XElement item.</param>
/// <returns>Strong typed object.</returns>
private static RssSchema ParseRssItem(XElement item)
{
XNamespace ns = "http://search.yahoo.com/mrss/";
var rssItem = ParseItem(item);
rssItem.PublishDate = item.GetSafeElementDate("pubDate");
string image = item.GetSafeElementString("image");
if (string.IsNullOrEmpty(image))
{
image = item.GetImageFromEnclosure();
}
if (string.IsNullOrEmpty(image) && item.Elements(ns + "content").LastOrDefault() != null)
{
var element = item.Elements(ns + "content").Last();
if (element.Attribute("type") != null && element.Attribute("type").Value.Contains("image/"))
{
image = element.Attribute("url").Value;
}
}
if (string.IsNullOrEmpty(image) && item.Elements(ns + "thumbnail").LastOrDefault() != null)
{
var element = item.Elements(ns + "thumbnail").Last();
image = element.Attribute("url").Value;
}
if (string.IsNullOrEmpty(image) && item.ToString().Contains("thumbnail"))
{
image = item.GetSafeElementString("thumbnail");
}
if (string.IsNullOrEmpty(image))
{
image = item.GetImage();
}
rssItem.Categories = item.GetSafeElementsString("category");
rssItem.ImageUrl = image;
return rssItem;
}
/// <summary>
/// Retrieve item author from item.
/// </summary>
/// <param name="item">XElement item.</param>
/// <returns>String of item author.</returns>
private static string GetItemAuthor(XElement item)
{
var content = item.GetSafeElementString("creator", NsRdfElementsNamespaceUri).Trim();
if (string.IsNullOrEmpty(content))
{
content = item.GetSafeElementString("author");
}
return content;
}
}
}

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

@ -1,460 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml.Linq;
namespace Microsoft.Toolkit.Parsers.Rss
{
/// <summary>
/// Class with utilities for RSS related works.
/// </summary>
internal static class RssHelper
{
/// <summary>
/// String for regular expression for image pattern.
/// </summary>
private const string ImagePattern = @"<img.*?src=[\""'](.+?)[\""'].*?>";
/// <summary>
/// String for regular expression for hyperlink pattern.
/// </summary>
private const string HyperlinkPattern = @"<a\s+(?:[^>]*?\s+)?href=""([^ ""]*)""";
/// <summary>
/// String for regular expression for height pattern.
/// </summary>
private const string HeightPattern = @"height=(?:(['""])(?<height>(?:(?!\1).)*)\1|(?<height>\S+))";
/// <summary>
/// String for regular expression for width pattern.
/// </summary>
private const string WidthPattern = @"width=(?:(['""])(?<width>(?:(?!\1).)*)\1|(?<width>\S+))";
/// <summary>
/// Regular expression for image pattern.
/// </summary>
private static readonly Regex RegexImages = new Regex(ImagePattern, RegexOptions.IgnoreCase);
/// <summary>
/// Regular expression for hyperlink pattern.
/// </summary>
private static readonly Regex RegexLinks = new Regex(HyperlinkPattern, RegexOptions.IgnoreCase);
/// <summary>
/// Regular expression for height pattern.
/// </summary>
private static readonly Regex RegexHeight = new Regex(HeightPattern, RegexOptions.IgnoreCase | RegexOptions.Singleline);
/// <summary>
/// Regular expression for width pattern.
/// </summary>
private static readonly Regex RegexWidth = new Regex(WidthPattern, RegexOptions.IgnoreCase | RegexOptions.Singleline);
/// <summary>
/// Removes \t characters in the string and trim additional space and carriage returns.
/// </summary>
/// <param name="text">Text string.</param>
/// <returns>Sanitized string.</returns>
public static string SanitizeString(this string text)
{
if (string.IsNullOrEmpty(text))
{
return string.Empty;
}
var textArray = text.Split(new[] { "\t" }, StringSplitOptions.RemoveEmptyEntries);
string sanitizedText = string.Empty;
foreach (var item in textArray.ToList())
{
sanitizedText += item.Trim();
}
sanitizedText = string.Join(" ", Regex.Split(sanitizedText, @"(?:\r\n|\n|\r)"));
return sanitizedText;
}
/// <summary>
/// Get item date from <see cref="XElement"/> and element name.
/// </summary>
/// <param name="item">XElement item.</param>
/// <param name="elementName">Name of element.</param>
/// <returns>Item date.</returns>
public static DateTime GetSafeElementDate(this XElement item, string elementName)
{
return GetSafeElementDate(item, elementName, item.GetDefaultNamespace());
}
/// <summary>
/// Get item date from <see cref="XElement"/>, element name and <see cref="XNamespace"/>.
/// </summary>
/// <param name="item">XElement item.</param>
/// <param name="elementName">Name of element.</param>
/// <param name="xNamespace">XNamespace namespace.</param>
/// <returns>Item date.</returns>
public static DateTime GetSafeElementDate(this XElement item, string elementName, XNamespace xNamespace)
{
DateTime date;
XElement element = item.Element(xNamespace + elementName);
if (element == null)
{
return DateTime.Now;
}
if (TryParseDateTime(element.Value, out date))
{
return date;
}
return DateTime.Now;
}
/// <summary>
/// Get item string value for <see cref="XElement"/> and element name.
/// </summary>
/// <param name="item">XElement item.</param>
/// <param name="elementName">Name of element.</param>
/// <returns>Safe string.</returns>
public static string GetSafeElementString(this XElement item, string elementName)
{
if (item == null)
{
return string.Empty;
}
return GetSafeElementString(item, elementName, item.GetDefaultNamespace());
}
/// <summary>
/// Get item string values for <see cref="XElement"/> and element name.
/// </summary>
/// <param name="item">XElement item.</param>
/// <param name="elementName">Name of the element.</param>
/// <returns>Safe list of string values.</returns>
public static IEnumerable<string> GetSafeElementsString(this XElement item, string elementName)
{
return GetSafeElementsString(item, elementName, item.GetDefaultNamespace());
}
/// <summary>
/// Get item string values for <see cref="XElement"/>, element name and namespace.
/// </summary>
/// <param name="item">XElement item.</param>
/// <param name="elementName">Name of element.</param>
/// <param name="xNamespace">XNamespace namespace.</param>
/// <returns>Safe list of string values.</returns>
public static IEnumerable<string> GetSafeElementsString(this XElement item, string elementName, XNamespace xNamespace)
{
if (item != null)
{
IEnumerable<XElement> values = item.Elements(xNamespace + elementName);
return values.Where(f => !string.IsNullOrEmpty(f.Value))
.Select(f => f.Value);
}
return Enumerable.Empty<string>();
}
/// <summary>
/// Get item string value for <see cref="XElement"/>, element name and namespace.
/// </summary>
/// <param name="item">XElement item.</param>
/// <param name="elementName">Name of element.</param>
/// <param name="xNamespace">XNamespace namespace.</param>
/// <returns>Safe string.</returns>
public static string GetSafeElementString(this XElement item, string elementName, XNamespace xNamespace)
{
if (item == null)
{
return string.Empty;
}
XElement value = item.Element(xNamespace + elementName);
if (value != null)
{
return value.Value;
}
return string.Empty;
}
/// <summary>
/// Get feed url to see full original information.
/// </summary>
/// <param name="item">XElement item.</param>
/// <param name="rel">rel attribute value.</param>
/// <returns>String link.</returns>
public static string GetLink(this XElement item, string rel)
{
IEnumerable<XElement> links = item.Elements(item.GetDefaultNamespace() + "link");
var xElements = links as XElement[] ?? links.ToArray();
IEnumerable<string> link = from l in xElements
let xAttribute = l.Attribute("rel")
where xAttribute != null && xAttribute.Value == rel
let attribute = l.Attribute("href")
where attribute != null
select attribute.Value;
var enumerable = link as string[] ?? link.ToArray();
if (!enumerable.Any() && xElements.Any())
{
return xElements.FirstOrDefault().Attributes().First().Value;
}
return enumerable.FirstOrDefault();
}
/// <summary>
/// Get feed image.
/// </summary>
/// <param name="item">XElement item.</param>
/// <returns>Feed data image.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The general catch is intended to avoid breaking the Data Provider by a Html decode exception")]
public static string GetImage(this XElement item)
{
string feedDataImage = null;
try
{
feedDataImage = GetImagesInHTMLString(item.Value).FirstOrDefault();
if (!string.IsNullOrEmpty(feedDataImage) && feedDataImage.EndsWith("'"))
{
feedDataImage = feedDataImage.Remove(feedDataImage.Length - 1);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
return feedDataImage;
}
/// <summary>
/// Get the item image from the enclosure element http://www.w3schools.com/rss/rss_tag_enclosure.asp
/// </summary>
/// <param name="item">XElement item.</param>
/// <returns>Feed data image.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The general catch is intended to avoid breaking the Data Provider by a Html decode exception")]
public static string GetImageFromEnclosure(this XElement item)
{
string feedDataImage = null;
try
{
XElement element = item.Element(item.GetDefaultNamespace() + "enclosure");
if (element == null)
{
return string.Empty;
}
var typeAttribute = element.Attribute("type");
if (!string.IsNullOrEmpty(typeAttribute?.Value) && typeAttribute.Value.StartsWith("image"))
{
var urlAttribute = element.Attribute("url");
feedDataImage = (!string.IsNullOrEmpty(urlAttribute?.Value)) ?
urlAttribute.Value : string.Empty;
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
return feedDataImage;
}
/// <summary>
/// Tries to parse the original string to a datetime format.
/// </summary>
/// <param name="s">Input string.</param>
/// <param name="result">Parsed datetime.</param>
/// <returns>True if success</returns>
public static bool TryParseDateTime(string s, out DateTime result)
{
if (DateTime.TryParse(s, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AllowWhiteSpaces, out result))
{
return true;
}
int tzIndex = s.LastIndexOf(" ");
if (tzIndex >= 0)
{
string tz = s.Substring(tzIndex, s.Length - tzIndex);
string offset = TimeZoneToOffset(tz);
if (offset != null)
{
string offsetDate = string.Format("{0} {1}", s.Substring(0, tzIndex), offset);
return TryParseDateTime(offsetDate, out result);
}
}
result = default(DateTime);
return false;
}
/// <summary>
/// Calculate and return timezone.
/// </summary>
/// <param name="tz">Input string.</param>
/// <returns>Parsed timezone.</returns>
public static string TimeZoneToOffset(string tz)
{
if (tz == null)
{
return null;
}
tz = tz.ToUpper().Trim();
if (TimeZones.ContainsKey(tz))
{
return TimeZones[tz].First();
}
return null;
}
/// <summary>
/// Retrieve images from HTML string.
/// </summary>
/// <param name="htmlString">String of HTML.</param>
/// <returns>List of images.</returns>
private static IEnumerable<string> GetImagesInHTMLString(string htmlString)
{
var images = new List<string>();
foreach (Match match in RegexImages.Matches(htmlString))
{
bool include = true;
string tag = match.Value;
// Ignores images with low size
var matchHeight = RegexHeight.Match(tag);
if (matchHeight.Success)
{
var heightValue = matchHeight.Groups["height"].Value;
if (int.TryParse(heightValue, out var heightIntValue) && heightIntValue < 10)
{
include = false;
}
}
var matchWidth = RegexWidth.Match(tag);
if (matchWidth.Success)
{
var widthValue = matchWidth.Groups["width"].Value;
if (int.TryParse(widthValue, out var widthIntValue) && widthIntValue < 10)
{
include = false;
}
}
if (include)
{
images.Add(match.Groups[1].Value);
}
}
foreach (Match match in RegexLinks.Matches(htmlString))
{
var value = match.Groups[1].Value;
if (value.Contains(".jpg") || value.Contains(".png"))
{
images.Add(value);
}
}
return images;
}
/// <summary>
/// Dictionary of timezones.
/// </summary>
private static readonly Dictionary<string, string[]> TimeZones = new Dictionary<string, string[]>
{
{ "ACDT", new[] { "-1030", "Australian Central Daylight" } },
{ "ACST", new[] { "-0930", "Australian Central Standard" } },
{ "ADT", new[] { "+0300", "(US) Atlantic Daylight" } },
{ "AEDT", new[] { "-1100", "Australian East Daylight" } },
{ "AEST", new[] { "-1000", "Australian East Standard" } },
{ "AHDT", new[] { "+0900", string.Empty } },
{ "AHST", new[] { "+1000", string.Empty } },
{ "AST", new[] { "+0400", "(US) Atlantic Standard" } },
{ "AT", new[] { "+0200", "Azores" } },
{ "AWDT", new[] { "-0900", "Australian West Daylight" } },
{ "AWST", new[] { "-0800", "Australian West Standard" } },
{ "BAT", new[] { "-0300", "Baghdad" } },
{ "BDST", new[] { "-0200", "British Double Summer" } },
{ "BET", new[] { "+1100", "Bering Standard" } },
{ "BST", new[] { "+0300", "Brazil Standard" } },
{ "BT", new[] { "-0300", "Baghdad" } },
{ "BZT2", new[] { "+0300", "Brazil Zone 2" } },
{ "CADT", new[] { "-1030", "Central Australian Daylight" } },
{ "CAST", new[] { "-0930", "Central Australian Standard" } },
{ "CAT", new[] { "+1000", "Central Alaska" } },
{ "CCT", new[] { "-0800", "China Coast" } },
{ "CDT", new[] { "+0500", "(US) Central Daylight" } },
{ "CED", new[] { "-0200", "Central European Daylight" } },
{ "CET", new[] { "-0100", "Central European" } },
{ "CST", new[] { "+0600", "(US) Central Standard" } },
{ "EAST", new[] { "-1000", "Eastern Australian Standard" } },
{ "EDT", new[] { "+0400", "(US) Eastern Daylight" } },
{ "EED", new[] { "-0300", "Eastern European Daylight" } },
{ "EET", new[] { "-0200", "Eastern Europe" } },
{ "EEST", new[] { "-0300", "Eastern Europe Summer" } },
{ "EST", new[] { "+0500", "(US) Eastern Standard" } },
{ "FST", new[] { "-0200", "French Summer" } },
{ "FWT", new[] { "-0100", "French Winter" } },
{ "GMT", new[] { "+0000", "Greenwich Mean" } },
{ "GST", new[] { "-1000", "Guam Standard" } },
{ "HDT", new[] { "+0900", "Hawaii Daylight" } },
{ "HST", new[] { "+1000", "Hawaii Standard" } },
{ "IDLE", new[] { "-1200", "International Date Line East" } },
{ "IDLW", new[] { "+1200", "International Date Line West" } },
{ "IST", new[] { "-0530", "Indian Standard" } },
{ "IT", new[] { "-0330", "Iran" } },
{ "JST", new[] { "-0900", "Japan Standard" } },
{ "JT", new[] { "-0700", "Java" } },
{ "MDT", new[] { "+0600", "(US) Mountain Daylight" } },
{ "MED", new[] { "-0200", "Middle European Daylight" } },
{ "MET", new[] { "-0100", "Middle European" } },
{ "MEST", new[] { "-0200", "Middle European Summer" } },
{ "MEWT", new[] { "-0100", "Middle European Winter" } },
{ "MST", new[] { "+0700", "(US) Mountain Standard" } },
{ "MT", new[] { "-0800", "Moluccas" } },
{ "NDT", new[] { "+0230", "Newfoundland Daylight" } },
{ "NFT", new[] { "+0330", "Newfoundland" } },
{ "NT", new[] { "+1100", "Nome" } },
{ "NST", new[] { "-0630", "North Sumatra" } },
{ "NZ", new[] { "-1100", "New Zealand " } },
{ "NZST", new[] { "-1200", "New Zealand Standard" } },
{ "NZDT", new[] { "-1300", "New Zealand Daylight" } },
{ "NZT", new[] { "-1200", "New Zealand" } },
{ "PDT", new[] { "+0700", "(US) Pacific Daylight" } },
{ "PST", new[] { "+0800", "(US) Pacific Standard" } },
{ "ROK", new[] { "-0900", "Republic of Korea" } },
{ "SAD", new[] { "-1000", "South Australia Daylight" } },
{ "SAST", new[] { "-0900", "South Australia Standard" } },
{ "SAT", new[] { "-0900", "South Australia Standard" } },
{ "SDT", new[] { "-1000", "South Australia Daylight" } },
{ "SST", new[] { "-0200", "Swedish Summer" } },
{ "SWT", new[] { "-0100", "Swedish Winter" } },
{ "USZ3", new[] { "-0400", "Volga Time (Russia)" } },
{ "USZ4", new[] { "-0500", "Ural Time (Russia)" } },
{ "USZ5", new[] { "-0600", "West-Siberian Time (Russia) " } },
{ "USZ6", new[] { "-0700", "Yenisei Time (Russia)" } },
{ "UT", new[] { "+0000", "Universal Coordinated" } },
{ "UTC", new[] { "+0000", "Universal Coordinated" } },
{ "UZ10", new[] { "-1100", "Okhotsk Time (Russia)" } },
{ "WAT", new[] { "+0100", "West Africa" } },
{ "WET", new[] { "+0000", "West European" } },
{ "WST", new[] { "-0800", "West Australian Standard" } },
{ "YDT", new[] { "+0800", "Yukon Daylight" } },
{ "YST", new[] { "+0900", "Yukon Standard" } }
};
}
}

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

@ -1,43 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Xml.Linq;
namespace Microsoft.Toolkit.Parsers.Rss
{
/// <summary>
/// The RSS Parser allows you to parse an RSS content String into RSS Schema.
/// </summary>
public class RssParser : IParser<RssSchema>
{
/// <summary>
/// Parse an RSS content string into RSS Schema.
/// </summary>
/// <param name="data">Input string.</param>
/// <returns>Strong type.</returns>
public IEnumerable<RssSchema> Parse(string data)
{
if (string.IsNullOrEmpty(data))
{
return null;
}
var doc = XDocument.Parse(data);
var type = BaseRssParser.GetFeedType(doc);
BaseRssParser rssParser;
if (type == RssType.Rss)
{
rssParser = new Rss2Parser();
}
else
{
rssParser = new AtomParser();
}
return rssParser.LoadFeed(doc);
}
}
}

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

@ -1,65 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
namespace Microsoft.Toolkit.Parsers.Rss
{
/// <summary>
/// Implementation of the RssSchema class.
/// </summary>
public class RssSchema : SchemaBase
{
/// <summary>
/// Gets or sets title.
/// </summary>
public string Title { get; set; }
/// <summary>
/// Gets or sets summary.
/// </summary>
public string Summary { get; set; }
/// <summary>
/// Gets or sets content.
/// </summary>
public string Content { get; set; }
/// <summary>
/// Gets or sets image Url.
/// </summary>
public string ImageUrl { get; set; }
/// <summary>
/// Gets or sets extra Image Url.
/// </summary>
public string ExtraImageUrl { get; set; }
/// <summary>
/// Gets or sets media Url.
/// </summary>
public string MediaUrl { get; set; }
/// <summary>
/// Gets or sets feed Url.
/// </summary>
public string FeedUrl { get; set; }
/// <summary>
/// Gets or sets author.
/// </summary>
public string Author { get; set; }
/// <summary>
/// Gets or sets publish Date.
/// </summary>
public DateTime PublishDate { get; set; }
/// <summary>
/// Gets or sets item's categories.
/// </summary>
public IEnumerable<string> Categories { get; set; }
}
}

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

@ -1,21 +1,18 @@
<Project Sdk="MSBuild.Sdk.Extras">
<PropertyGroup>
<TargetFrameworks>uap10.0.16299;netstandard2.0;NET462</TargetFrameworks>
<TargetFrameworks>uap10.0.17763;netstandard2.0;NET462</TargetFrameworks>
<Title>Windows Community Toolkit .NET Standard Services</Title>
<Description>
This .NET standard library enables access to different data sources such as Microsoft Graph, OneDrive, Twitter, Microsoft Translator, and LinkedIn. It is part of the Windows Community Toolkit.
Namespace:
- Facebook: Album, DataConfig, DataHost, OAuthTokens, Permissions, Photo, Picture, PictureData, PlatformImageSource, Post, RequestSource, Service.
</Description>
<PackageTags>UWP Community Toolkit Windows Microsoft Graph OneDrive Twitter Translator LinkedIn service login OAuth</PackageTags>
<LangVersion>8.0</LangVersion>
<NoWarn>CS8002;CS0618</NoWarn>
<DeterministicSourcePaths Condition="'$(EnableSourceLink)' == ''">false</DeterministicSourcePaths>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0.16299'">
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0.17763'">
<DefineConstants Condition="'$(DisableImplicitFrameworkDefines)' != 'true'">$(DefineConstants);WINRT</DefineConstants>
</PropertyGroup>
@ -30,11 +27,11 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
<PackageReference Include="System.Text.Json" Version="4.7.2" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='uap10.0.16299'">
<ItemGroup Condition="'$(TargetFramework)'=='uap10.0.17763'">
<ProjectReference Include="..\Microsoft.Toolkit.Uwp\Microsoft.Toolkit.Uwp.csproj" />
</ItemGroup>
@ -50,7 +47,7 @@
<PackageReference Include="Microsoft.Toolkit.Forms.UI.Controls.WebView" Version="[5.0.0-preview.gb86cb1c4cb,)" />
</ItemGroup>
<ItemGroup Condition="!('$(TargetFramework)'=='uap10.0.16299')">
<ItemGroup Condition="!('$(TargetFramework)'=='uap10.0.17763')">
<Compile Remove="PlatformSpecific\Uwp\**\*" />
</ItemGroup>
</Project>
</Project>

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

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
<Library Name="Microsoft.Toolkit.Services">
<Namespace Name="System.Text.Json.Serialization.Converters" Dynamic="Required All"/>
</Library>
</Directives>

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

@ -7,9 +7,9 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Toolkit.Services.Core;
using Newtonsoft.Json.Linq;
#if WINRT
using Microsoft.Toolkit.Services.PlatformSpecific.Uwp;
@ -81,7 +81,8 @@ namespace Microsoft.Toolkit.Services.LinkedIn
throw new ArgumentException("Missing callback uri");
}
if (!Enum.IsDefined(typeof(LinkedInPermissions), requiredPermissions))
// Check if its a valid combination of LinkedInPermissions
if ((~(int)LinkedInPermissionsHelpers.AllPermissions & (int)requiredPermissions) != 0)
{
throw new ArgumentException("Error retrieving required permissions");
}
@ -154,17 +155,6 @@ namespace Microsoft.Toolkit.Services.LinkedIn
return false;
}
/// <summary>
/// Log user out of LinkedIn.
/// </summary>
[Obsolete("Logout is deprecated, please use LogoutAsync instead.", true)]
public void Logout()
{
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
LogoutAsync();
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
}
/// <summary>
/// Log user out of LinkedIn.
/// </summary>
@ -197,22 +187,18 @@ namespace Microsoft.Toolkit.Services.LinkedIn
var url = $"{_baseUrl}{config.Query}/~:({fields})?oauth2_access_token={Tokens.AccessToken}&format=json&count={maxRecords}&start={startRecord}";
using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, new Uri(url)))
using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, new Uri(url));
request.Headers.Connection.TryParseAdd("Keep-Alive");
using var response = await client.SendAsync(request).ConfigureAwait(false);
var data = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
if (response.IsSuccessStatusCode && !string.IsNullOrEmpty(data))
{
request.Headers.Connection.TryParseAdd("Keep-Alive");
using (var response = await client.SendAsync(request).ConfigureAwait(false))
{
var data = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
if (response.IsSuccessStatusCode && !string.IsNullOrEmpty(data))
{
return parser.Parse(data);
}
throw new RequestFailedException((System.Net.HttpStatusCode)response.StatusCode, data);
}
return parser.Parse(data);
}
throw new RequestFailedException((System.Net.HttpStatusCode)response.StatusCode, data);
}
/// <summary>
@ -233,22 +219,18 @@ namespace Microsoft.Toolkit.Services.LinkedIn
var url = $"{_baseUrl}/people/~/shares?oauth2_access_token={Tokens.AccessToken}&format=json";
using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, new Uri(url)))
{
request.Headers.Add("x-li-format", "json");
var stringContent = requestParser.Parse(shareRequest);
request.Content = new StringContent(stringContent, Encoding.UTF8, "application/json");
using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, new Uri(url));
request.Headers.Add("x-li-format", "json");
var stringContent = requestParser.Parse(shareRequest);
request.Content = new StringContent(stringContent, Encoding.UTF8, "application/json");
using (var response = await client.SendAsync(request).ConfigureAwait(false))
{
var data = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
using var response = await client.SendAsync(request).ConfigureAwait(false);
var data = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var responseParser = new LinkedInParser<U>();
var responseParser = new LinkedInParser<U>();
var listResults = responseParser.Parse(data) as List<U>;
return listResults[0];
}
}
var listResults = responseParser.Parse(data) as List<U>;
return listResults[0];
}
return default(U);
@ -274,15 +256,13 @@ namespace Microsoft.Toolkit.Services.LinkedIn
+ "&client_id=" + tokens.ClientId
+ "&client_secret=" + tokens.ClientSecret;
using (var request = new HttpRequestMessage(HttpMethod.Post, new Uri(url)))
{
using (var response = await client.SendAsync(request).ConfigureAwait(false))
{
var jsonString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var json = JObject.Parse(jsonString);
return json.GetValue("access_token").Value<string>();
}
}
using var request = new HttpRequestMessage(HttpMethod.Post, new Uri(url));
using var response = await client.SendAsync(request).ConfigureAwait(false);
using var jsonStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
using var jsonDoc = await JsonDocument.ParseAsync(jsonStream).ConfigureAwait(false);
var value = jsonDoc.RootElement.GetProperty("access_token");
return value.GetString();
}
private async Task<string> GetAuthorizeCodeAsync(LinkedInOAuthTokens tokens, LinkedInPermissions permissions)

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

@ -3,7 +3,7 @@
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using Newtonsoft.Json;
using System.Text.Json;
namespace Microsoft.Toolkit.Services.LinkedIn
{
@ -24,11 +24,11 @@ namespace Microsoft.Toolkit.Services.LinkedIn
try
{
results = JsonConvert.DeserializeObject<List<T>>(data);
results = JsonSerializer.Deserialize<List<T>>(data);
}
catch (JsonSerializationException)
catch (JsonException)
{
T linkedInResult = JsonConvert.DeserializeObject<T>(data);
T linkedInResult = JsonSerializer.Deserialize<T>(data);
results = new List<T> { linkedInResult };
}
@ -42,7 +42,7 @@ namespace Microsoft.Toolkit.Services.LinkedIn
/// <returns>Returns string data.</returns>
public string Parse(T dataToShare)
{
return JsonConvert.SerializeObject(dataToShare, Formatting.None, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
return JsonSerializer.Serialize(dataToShare, typeof(T), new JsonSerializerOptions { IgnoreNullValues = true });
}
}
}

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

@ -37,4 +37,18 @@ namespace Microsoft.Toolkit.Services.LinkedIn
/// </summary>
WriteShare = 8
}
#pragma warning disable SA1649 // File name should match first type name
internal static class LinkedInPermissionsHelpers
{
/// <summary>
/// Internal AllPermissions for LinkedInPermissions, so we don't expose it. Keep it in sync with <see cref="LinkedInPermissions"/>
/// </summary>
internal const LinkedInPermissions AllPermissions =
LinkedInPermissions.ReadBasicProfile |
LinkedInPermissions.ReadEmailAddress |
LinkedInPermissions.ReadWriteCompanyAdmin |
LinkedInPermissions.WriteShare;
}
#pragma warning restore SA1649 // File name should match first type name
}

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

@ -92,15 +92,6 @@ namespace Microsoft.Toolkit.Services.LinkedIn
return Provider.ShareDataAsync<LinkedInShareRequest, LinkedInShareResponse>(shareRequest);
}
/// <summary>
/// Log user out of LinkedIn.
/// </summary>
[Obsolete("Logout is deprecated, please use LogoutAsync instead.", true)]
public void Logout()
{
Provider.Logout();
}
/// <summary>
/// Log user out of LinkedIn.
/// </summary>

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

@ -4,8 +4,8 @@
using System;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace Microsoft.Toolkit.Services.MicrosoftTranslator
{
@ -24,6 +24,9 @@ namespace Microsoft.Toolkit.Services.MicrosoftTranslator
/// </summary>
private static readonly Uri ServiceUrl = new Uri("https://api.cognitive.microsoft.com/sts/v1.0/issueToken");
// TODO
// private static readonly Uri ServiceUrl = new Uri(THIS SHOULD BE A PARAMETER NOW);
/// <summary>
/// After obtaining a valid token, this class will cache it for this duration.
/// Use a duration of 8 minutes, which is less than the actual token lifetime of 10 minutes.
@ -90,24 +93,22 @@ namespace Microsoft.Toolkit.Services.MicrosoftTranslator
return _storedTokenValue;
}
using (var request = new HttpRequestMessage(HttpMethod.Post, ServiceUrl))
using var request = new HttpRequestMessage(HttpMethod.Post, ServiceUrl);
request.Headers.Add(OcpApimSubscriptionKeyHeader, SubscriptionKey);
var response = await client.SendAsync(request).ConfigureAwait(false);
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
if (!response.IsSuccessStatusCode)
{
request.Headers.Add(OcpApimSubscriptionKeyHeader, SubscriptionKey);
var response = await client.SendAsync(request).ConfigureAwait(false);
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
if (!response.IsSuccessStatusCode)
{
var error = JsonConvert.DeserializeObject<ErrorResponse>(content);
throw new TranslatorServiceException(error.Message);
}
_storedTokenTime = DateTime.Now;
_storedTokenValue = $"Bearer {content}";
return _storedTokenValue;
var error = JsonSerializer.Deserialize<ErrorResponse>(content);
throw new TranslatorServiceException(error?.Error?.Message);
}
_storedTokenTime = DateTime.Now;
_storedTokenValue = $"Bearer {content}";
return _storedTokenValue;
}
}
}

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

@ -2,21 +2,27 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Text.Json.Serialization;
namespace Microsoft.Toolkit.Services.MicrosoftTranslator
{
#pragma warning disable SA1402 // File may only contain a single type
/// <summary>
/// Holds information about an error occurred while accessing Microsoft Translator Service.
/// </summary>
internal class ErrorResponse
{
[JsonPropertyName("error")]
public Error Error { get; set; }
}
internal class Error
{
/// <summary>
/// Gets or sets the error message.
/// </summary>
[JsonPropertyName("message")]
public string Message { get; set; }
/// <summary>
/// Gets or sets the HTTP status code.
/// </summary>
public int StatusCode { get; set; }
}
#pragma warning restore SA1402 // File may only contain a single type
}

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

@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Toolkit.Services.MicrosoftTranslator
{
@ -30,7 +30,7 @@ namespace Microsoft.Toolkit.Services.MicrosoftTranslator
/// <summary>
/// Gets the directionality, which is rtl for right-to-left languages or ltr for left-to-right languages.
/// </summary>
[JsonProperty("dir")]
[JsonPropertyName("dir")]
public string Directionality { get; }
/// <summary>

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

@ -8,9 +8,8 @@ using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Microsoft.Toolkit.Services.MicrosoftTranslator
{
@ -108,14 +107,13 @@ namespace Microsoft.Toolkit.Services.MicrosoftTranslator
await CheckUpdateTokenAsync().ConfigureAwait(false);
var uriString = $"{BaseUrl}detect?{ApiVersion}";
using (var request = CreateHttpRequest(uriString, HttpMethod.Post, input.Select(t => new { Text = t.Substring(0, Math.Min(t.Length, _MaxTextLengthForDetection)) })))
{
var response = await client.SendAsync(request).ConfigureAwait(false);
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
using var request = CreateHttpRequest(uriString, HttpMethod.Post, input.Select(t => new { Text = t.Substring(0, Math.Min(t.Length, _MaxTextLengthForDetection)) }));
var responseContent = JsonConvert.DeserializeObject<IEnumerable<DetectedLanguageResponse>>(content);
return responseContent;
}
var response = await client.SendAsync(request).ConfigureAwait(false);
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var responseContent = JsonSerializer.Deserialize<IEnumerable<DetectedLanguageResponse>>(content);
return responseContent;
}
/// <inheritdoc/>
@ -132,24 +130,23 @@ namespace Microsoft.Toolkit.Services.MicrosoftTranslator
await CheckUpdateTokenAsync().ConfigureAwait(false);
var uriString = $"{BaseUrl}languages?scope=translation&{ApiVersion}";
using (var request = CreateHttpRequest(uriString))
using var request = CreateHttpRequest(uriString);
language = language ?? Language;
if (!string.IsNullOrWhiteSpace(language))
{
language = language ?? Language;
if (!string.IsNullOrWhiteSpace(language))
{
// If necessary, adds the Accept-Language header in order to get localized language names.
request.Headers.AcceptLanguage.Add(new StringWithQualityHeaderValue(language));
}
var response = await client.SendAsync(request).ConfigureAwait(false);
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var jsonContent = JToken.Parse(content)["translation"];
var responseContent = JsonConvert.DeserializeObject<Dictionary<string, ServiceLanguage>>(jsonContent.ToString()).ToList();
responseContent.ForEach(r => r.Value.Code = r.Key);
return responseContent.Select(r => r.Value).OrderBy(r => r.Name).ToList();
// If necessary, adds the Accept-Language header in order to get localized language names.
request.Headers.AcceptLanguage.Add(new StringWithQualityHeaderValue(language));
}
var response = await client.SendAsync(request).ConfigureAwait(false);
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var jsonContent = JsonDocument.Parse(content).RootElement.GetProperty("translation");
var responseContent = JsonSerializer.Deserialize<Dictionary<string, ServiceLanguage>>(jsonContent.ToString()).ToList();
responseContent.ForEach(r => r.Value.Code = r.Key);
return responseContent.Select(r => r.Value).OrderBy(r => r.Name).ToList();
}
/// <inheritdoc/>
@ -216,14 +213,13 @@ namespace Microsoft.Toolkit.Services.MicrosoftTranslator
var toQueryString = string.Join("&", to.Select(t => $"to={t}"));
var uriString = (string.IsNullOrWhiteSpace(from) ? $"{BaseUrl}translate?{toQueryString}" : $"{BaseUrl}translate?from={from}&{toQueryString}") + $"&{ApiVersion}";
using (var request = CreateHttpRequest(uriString, HttpMethod.Post, input.Select(t => new { Text = t })))
{
var response = await client.SendAsync(request).ConfigureAwait(false);
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
using var request = CreateHttpRequest(uriString, HttpMethod.Post, input.Select(t => new { Text = t }));
var responseContent = JsonConvert.DeserializeObject<IEnumerable<TranslationResponse>>(content);
return responseContent;
}
var response = await client.SendAsync(request).ConfigureAwait(false);
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var responseContent = JsonSerializer.Deserialize<IEnumerable<TranslationResponse>>(content);
return responseContent;
}
/// <inheritdoc/>
@ -254,7 +250,7 @@ namespace Microsoft.Toolkit.Services.MicrosoftTranslator
if (content != null)
{
var jsonRequest = JsonConvert.SerializeObject(content);
var jsonRequest = JsonSerializer.Serialize(content);
var requestContent = new StringContent(jsonRequest, System.Text.Encoding.UTF8, JsonMediaType);
request.Content = requestContent;
}

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

@ -4,7 +4,7 @@
using System;
using System.Globalization;
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Toolkit.Services.Twitter
{
@ -18,19 +18,19 @@ namespace Microsoft.Toolkit.Services.Twitter
/// <summary>
/// Gets or sets time item was created.
/// </summary>
[JsonProperty("created_at")]
[JsonPropertyName("created_at")]
public string CreatedAt { get; set; }
/// <summary>
/// Gets or sets item Id.
/// </summary>
[JsonProperty("id_str")]
[JsonPropertyName("id_str")]
public string Id { get; set; }
/// <summary>
/// Gets or sets text of the tweet (handles both 140 and 280 characters)
/// </summary>
[JsonProperty("text")]
[JsonPropertyName("text")]
public string Text
{
get { return _text ?? FullText; }
@ -40,13 +40,13 @@ namespace Microsoft.Toolkit.Services.Twitter
/// <summary>
/// Gets or sets text of the tweet (280 characters).
/// </summary>
[JsonProperty("full_text")]
[JsonPropertyName("full_text")]
private string FullText { get; set; }
/// <summary>
/// Gets or sets display text range (indexes of tweet text without RT and leading user mentions)
/// </summary>
[JsonProperty("display_text_range")]
[JsonPropertyName("display_text_range")]
public int[] DisplayTextRange { get; set; }
/// <summary>
@ -54,68 +54,68 @@ namespace Microsoft.Toolkit.Services.Twitter
/// (true when tweet is longer than 140 characters)
/// This entity may be deprecated - it never seems to be set to true.
/// </summary>
[JsonProperty("truncated")]
[JsonPropertyName("truncated")]
public bool IsTruncated { get; set; }
/// <summary>
/// Gets or sets attached content of the tweet
/// </summary>
[JsonProperty("entities")]
[JsonPropertyName("entities")]
public TwitterEntities Entities { get; set; }
/// <summary>
/// Gets or sets extended attached content of the tweet
/// </summary>
[JsonProperty("extended_entities")]
[JsonPropertyName("extended_entities")]
public TwitterExtendedEntities ExtendedEntities { get; set; }
/// <summary>
/// Gets or sets tweet source (client or website used)
/// </summary>
[JsonProperty("source")]
[JsonPropertyName("source")]
public string Source { get; set; }
/// <summary>
/// Gets or sets in_reply_to_screen_name
/// </summary>
[JsonProperty("in_reply_to_screen_name")]
[JsonPropertyName("in_reply_to_screen_name")]
public string InReplyToScreenName { get; set; }
/// <summary>
/// Gets or sets in_reply_to_status_id_str
/// </summary>
[JsonProperty("in_reply_to_status_id_str")]
[JsonPropertyName("in_reply_to_status_id_str")]
public string InReplyToStatusId { get; set; }
/// <summary>
/// Gets or sets in_reply_to_user_id_str
/// </summary>
[JsonProperty("in_reply_to_user_id_str")]
[JsonPropertyName("in_reply_to_user_id_str")]
public string InReplyToUserId { get; set; }
/// <summary>
/// Gets or sets user who posted the status.
/// </summary>
[JsonProperty("user")]
[JsonPropertyName("user")]
public TwitterUser User { get; set; }
/// <summary>
/// Gets or sets geo coordinates (latitude and longitude) returned by Twitter for some locations
/// </summary>
[JsonProperty("coordinates")]
[JsonPropertyName("coordinates")]
[JsonConverter(typeof(TwitterCoordinatesConverter))]
public TwitterCoordinates Coordinates { get; set; }
/// <summary>
/// Gets or sets the Place object returned by Twitter for some locations
/// </summary>
[JsonProperty("place")]
[JsonPropertyName("place")]
public TwitterPlace Place { get; set; }
/// <summary>
/// Gets or sets the Retweeted Tweet
/// </summary>
[JsonProperty("retweeted_status")]
[JsonPropertyName("retweeted_status")]
public Tweet RetweetedStatus { get; set; }
/// <summary>
@ -138,25 +138,25 @@ namespace Microsoft.Toolkit.Services.Twitter
/// <summary>
/// Gets or sets quoted_status
/// </summary>
[JsonProperty("quoted_status")]
[JsonPropertyName("quoted_status")]
public Tweet QuotedStatus { get; set; }
/// <summary>
/// Gets or sets quoted_status_id_str
/// </summary>
[JsonProperty("quoted_status_id_str")]
[JsonPropertyName("quoted_status_id_str")]
public string QuotedStatusId { get; set; }
/// <summary>
/// Gets or sets quoted_status_permalink
/// </summary>
[JsonProperty("quoted_status_permalink")]
[JsonPropertyName("quoted_status_permalink")]
public TwitterUrl QuotedStatusPermalink { get; set; }
/// <summary>
/// Gets or sets approximate count of tweets quoting tweet
/// </summary>
[JsonProperty("quote_count")]
[JsonPropertyName("quote_count")]
public int QuoteCount { get; set; }
/// <summary>
@ -165,49 +165,49 @@ namespace Microsoft.Toolkit.Services.Twitter
/// <remarks>
/// Premium and Enterprise API access only
/// </remarks>
[JsonProperty("reply_count")]
[JsonPropertyName("reply_count")]
public int ReplyCount { get; set; }
/// <summary>
/// Gets or sets number of times tweet has been retweeted
/// </summary>
[JsonProperty("retweet_count")]
[JsonPropertyName("retweet_count")]
public int RetweetCount { get; set; }
/// <summary>
/// Gets or sets number of times tweet has been liked
/// </summary>
[JsonProperty("favorite_count")]
[JsonPropertyName("favorite_count")]
public int FavoriteCount { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not logged-in user has liked tweet
/// </summary>
[JsonProperty("favorited")]
[JsonPropertyName("favorited")]
public bool Favorited { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not logged-in user has retweeted tweet
/// </summary>
[JsonProperty("retweeted")]
[JsonPropertyName("retweeted")]
public bool Retweeted { get; set; }
/// <summary>
/// Gets or sets a value indicating whether URL in tweet has been flagged for sensitive content
/// </summary>
[JsonProperty("possibly_sensitive")]
[JsonPropertyName("possibly_sensitive")]
public bool Sensitive { get; set; }
/// <summary>
/// Gets or sets stream filter of tweet
/// </summary>
[JsonProperty("filter_level")]
[JsonPropertyName("filter_level")]
public string FilterLevel { get; set; }
/// <summary>
/// Gets or sets BCP 47 language identifier of tweet content
/// </summary>
[JsonProperty("lang")]
[JsonPropertyName("lang")]
public string Language { get; set; }
}
}

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

@ -3,7 +3,7 @@
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using Newtonsoft.Json;
using System.Text.Json;
namespace Microsoft.Toolkit.Services.Twitter
{
@ -24,7 +24,7 @@ namespace Microsoft.Toolkit.Services.Twitter
return null;
}
return JsonConvert.DeserializeObject<List<Tweet>>(data);
return JsonSerializer.Deserialize<List<Tweet>>(data);
}
}
}

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

@ -3,41 +3,97 @@
// See the LICENSE file in the project root for more information.
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Toolkit.Services.Twitter
{
internal class TwitterCoordinatesConverter : JsonConverter
internal class TwitterCoordinatesConverter : JsonConverter<TwitterCoordinates>
{
private readonly JsonEncodedText latitudeName = JsonEncodedText.Encode("Latitude");
private readonly JsonEncodedText longitudeName = JsonEncodedText.Encode("Longitude");
public override bool CanConvert(Type objectType)
{
return false;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
private readonly JsonConverter<double> doubleConverter;
public TwitterCoordinatesConverter(JsonSerializerOptions options)
{
doubleConverter = options?.GetConverter(typeof(double)) as JsonConverter<double> ?? throw new InvalidOperationException();
}
public override TwitterCoordinates Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
try
{
if (reader.TokenType != JsonToken.StartObject)
if (reader.TokenType != JsonTokenType.StartObject)
{
return null;
}
var jObject = JObject.Load(reader);
var jCoordinates = jObject["coordinates"] as JArray;
double latitude = default;
bool latitudeSet = false;
if (jCoordinates.Count != 2)
double longitude = default;
bool longitudeSet = false;
// Get the first property.
reader.Read();
if (reader.TokenType != JsonTokenType.PropertyName)
{
return null;
throw new JsonException();
}
var twitterCoordinates = new TwitterCoordinates
if (reader.ValueTextEquals(latitudeName.EncodedUtf8Bytes))
{
Latitude = (double)jCoordinates[0],
Longitude = (double)jCoordinates[1]
latitude = ReadProperty(ref reader, options);
latitudeSet = true;
}
else if (reader.ValueTextEquals(longitudeName.EncodedUtf8Bytes))
{
longitude = ReadProperty(ref reader, options);
longitudeSet = true;
}
else
{
throw new JsonException();
}
// Get the second property.
reader.Read();
if (reader.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException();
}
if (latitudeSet && reader.ValueTextEquals(longitudeName.EncodedUtf8Bytes))
{
longitude = ReadProperty(ref reader, options);
}
else if (longitudeSet && reader.ValueTextEquals(latitudeName.EncodedUtf8Bytes))
{
latitude = ReadProperty(ref reader, options);
}
else
{
throw new JsonException();
}
reader.Read();
if (reader.TokenType != JsonTokenType.EndObject)
{
throw new JsonException();
}
return new TwitterCoordinates
{
Latitude = latitude,
Longitude = longitude
};
return twitterCoordinates;
}
catch
{
@ -45,9 +101,15 @@ namespace Microsoft.Toolkit.Services.Twitter
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
private double ReadProperty(ref Utf8JsonReader reader, JsonSerializerOptions options)
{
reader.Read();
return doubleConverter.Read(ref reader, typeof(double), options);
}
public override void Write(Utf8JsonWriter writer, TwitterCoordinates value, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
}
}
}

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

@ -10,9 +10,9 @@ using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Toolkit.Services.Core;
using Newtonsoft.Json;
#if WINRT
using System.Runtime.InteropServices.WindowsRuntime;
@ -140,7 +140,7 @@ namespace Microsoft.Toolkit.Services.Twitter
TwitterOAuthRequest request = new TwitterOAuthRequest();
rawResult = await request.ExecuteGetAsync(uri, _tokens, _signatureManager);
return JsonConvert.DeserializeObject<TwitterUser>(rawResult);
return JsonSerializer.Deserialize<TwitterUser>(rawResult);
}
catch (UserNotFoundException)
{
@ -150,7 +150,7 @@ namespace Microsoft.Toolkit.Services.Twitter
{
if (!string.IsNullOrEmpty(rawResult))
{
var errors = JsonConvert.DeserializeObject<TwitterErrors>(rawResult);
var errors = JsonSerializer.Deserialize<TwitterErrors>(rawResult);
throw new TwitterException { Errors = errors };
}
@ -191,7 +191,7 @@ namespace Microsoft.Toolkit.Services.Twitter
{
if (!string.IsNullOrEmpty(rawResult))
{
var errors = JsonConvert.DeserializeObject<TwitterErrors>(rawResult);
var errors = JsonSerializer.Deserialize<TwitterErrors>(rawResult);
throw new TwitterException { Errors = errors };
}
@ -273,17 +273,6 @@ namespace Microsoft.Toolkit.Services.Twitter
return false;
}
/// <summary>
/// Log user out of Twitter.
/// </summary>
[Obsolete("Logout is deprecated, please use LogoutAsync instead.", true)]
public void Logout()
{
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
LogoutAsync();
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
}
/// <summary>
/// Log user out of Twitter.
/// </summary>
@ -592,18 +581,16 @@ namespace Microsoft.Toolkit.Services.Twitter
using (var request = new HttpRequestMessage(HttpMethod.Get, new Uri(twitterUrl)))
{
using (var response = await _client.SendAsync(request).ConfigureAwait(false))
using var response = await _client.SendAsync(request).ConfigureAwait(false);
var data = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
var data = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
getResponse = data;
}
else
{
Debug.WriteLine("HttpHelper call failed trying to retrieve Twitter Request Tokens. Message: {0}", data);
return false;
}
getResponse = data;
}
else
{
Debug.WriteLine("HttpHelper call failed trying to retrieve Twitter Request Tokens. Message: {0}", data);
return false;
}
}
@ -676,10 +663,8 @@ namespace Microsoft.Toolkit.Services.Twitter
{
request.Headers.Authorization = new AuthenticationHeaderValue("OAuth", authorizationHeaderParams);
using (var response = await _client.SendAsync(request).ConfigureAwait(false))
{
data = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
}
using var response = await _client.SendAsync(request).ConfigureAwait(false);
data = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
}
var screenName = ExtractTokenFromResponse(data, TwitterOAuthTokenType.ScreenName);

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

@ -4,7 +4,7 @@
using System;
using System.Globalization;
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Toolkit.Services.Twitter
{
@ -17,70 +17,70 @@ namespace Microsoft.Toolkit.Services.Twitter
/// Gets or sets the direct message id.
/// </summary>
/// <value>The direct message id.</value>
[JsonProperty(PropertyName = "id")]
[JsonPropertyName("id")]
public decimal Id { get; set; }
/// <summary>
/// Gets or sets the sender id.
/// </summary>
/// <value>The sender id.</value>
[JsonProperty(PropertyName = "sender_id")]
[JsonPropertyName("sender_id")]
public decimal SenderId { get; set; }
/// <summary>
/// Gets or sets the direct message text.
/// </summary>
/// <value>The direct message text.</value>
[JsonProperty(PropertyName = "text")]
[JsonPropertyName("text")]
public string Text { get; set; }
/// <summary>
/// Gets or sets the recipient id.
/// </summary>
/// <value>The recipient id.</value>
[JsonProperty(PropertyName = "recipient_id")]
[JsonPropertyName("recipient_id")]
public decimal RecipientId { get; set; }
/// <summary>
/// Gets or sets the created date.
/// </summary>
/// <value>The created date.</value>
[JsonProperty(PropertyName = "created_at")]
[JsonPropertyName("created_at")]
public string CreatedAt { get; set; }
/// <summary>
/// Gets or sets the name of the sender screen.
/// </summary>
/// <value>The name of the sender screen.</value>
[JsonProperty(PropertyName = "sender_screen_name")]
[JsonPropertyName("sender_screen_name")]
public string SenderScreenName { get; set; }
/// <summary>
/// Gets or sets the name of the recipient screen.
/// </summary>
/// <value>The name of the recipient screen.</value>
[JsonProperty(PropertyName = "recipient_screen_name")]
[JsonPropertyName("recipient_screen_name")]
public string RecipientScreenName { get; set; }
/// <summary>
/// Gets or sets the sender.
/// </summary>
/// <value>The sender.</value>
[JsonProperty(PropertyName = "sender")]
[JsonPropertyName("sender")]
public TwitterUser Sender { get; set; }
/// <summary>
/// Gets or sets the recipient.
/// </summary>
/// <value>The recipient.</value>
[JsonProperty(PropertyName = "recipient")]
[JsonPropertyName("recipient")]
public TwitterUser Recipient { get; set; }
/// <summary>
/// Gets or sets the entities.
/// </summary>
/// <value>The entities.</value>
[JsonProperty(PropertyName = "entities")]
[JsonPropertyName("entities")]
public TwitterEntities Entities { get; set; }
/// <summary>

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

@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Toolkit.Services.Twitter
{
@ -15,35 +15,35 @@ namespace Microsoft.Toolkit.Services.Twitter
/// Gets or sets Hashtags array of the tweet.
/// This array will be empty if no Hashtags are present.
/// </summary>
[JsonProperty("Hashtags")]
[JsonPropertyName("Hashtags")]
public TwitterHashtag[] Hashtags { get; set; }
/// <summary>
/// Gets or sets Symbols array of the tweet.
/// This array will be empty if no Symbols are present.
/// </summary>
[JsonProperty("Symbols")]
[JsonPropertyName("Symbols")]
public TwitterSymbol[] Symbols { get; set; }
/// <summary>
/// Gets or sets Media array of the tweet.
/// This array will not exist if no media is present.
/// </summary>
[JsonProperty("media")]
[JsonPropertyName("media")]
public TwitterMedia[] Media { get; set; }
/// <summary>
/// Gets or sets Urls array of the tweet.
/// This array will be empty if no Urls are present.
/// </summary>
[JsonProperty("urls")]
[JsonPropertyName("urls")]
public TwitterUrl[] Urls { get; set; }
/// <summary>
/// Gets or sets array of usernames mentioned in the tweet.
/// This array will be empty if no usernames are mentioned.
/// </summary>
[JsonProperty("user_mentions")]
[JsonPropertyName("user_mentions")]
public TwitterUserMention[] UserMentions { get; set; }
/// <summary>
@ -51,7 +51,7 @@ namespace Microsoft.Toolkit.Services.Twitter
/// This array will not exist if no poll is present.
/// This array will always have one poll.
/// </summary>
[JsonProperty("polls")]
[JsonPropertyName("polls")]
public TwitterPoll Poll { get; set; }
}
}

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

@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Toolkit.Services.Twitter
{
@ -14,13 +14,13 @@ namespace Microsoft.Toolkit.Services.Twitter
/// <summary>
/// Gets or sets error code
/// </summary>
[JsonProperty("code")]
[JsonPropertyName("code")]
public int Code { get; set; }
/// <summary>
/// Gets or sets error message
/// </summary>
[JsonProperty("message")]
[JsonPropertyName("message")]
public string Message { get; set; }
}
}

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

@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Toolkit.Services.Twitter
{
@ -14,7 +14,7 @@ namespace Microsoft.Toolkit.Services.Twitter
/// <summary>
/// Gets or sets the list of errors
/// </summary>
[JsonProperty("errors")]
[JsonPropertyName("errors")]
public TwitterError[] Errors { get; set; }
}
}

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

@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Toolkit.Services.Twitter
{
@ -14,7 +14,7 @@ namespace Microsoft.Toolkit.Services.Twitter
/// <summary>
/// Gets or sets the text of the tweet (280 characters).
/// </summary>
[JsonProperty("full_text")]
[JsonPropertyName("full_text")]
public string FullText { get; set; }
}
}

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

@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Toolkit.Services.Twitter
{
@ -14,7 +14,7 @@ namespace Microsoft.Toolkit.Services.Twitter
/// <summary>
/// Gets or sets Media of the tweet.
/// </summary>
[JsonProperty("media")]
[JsonPropertyName("media")]
public TwitterMedia[] Media { get; set; }
}
}

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

@ -3,7 +3,7 @@
// See the LICENSE file in the project root for more information.
using System.Globalization;
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Toolkit.Services.Twitter
{
@ -19,7 +19,7 @@ namespace Microsoft.Toolkit.Services.Twitter
/// <summary>
/// Gets or sets the type of data
/// </summary>
[JsonProperty("type")]
[JsonPropertyName("type")]
public string DataType { get; set; }
/// <summary>
@ -43,7 +43,7 @@ namespace Microsoft.Toolkit.Services.Twitter
/// <summary>
/// Gets or sets the coordinates of the geographic data
/// </summary>
[JsonProperty("coordinates")]
[JsonPropertyName("coordinates")]
public string[] Coordinates { get; set; }
/// <summary>

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

@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Toolkit.Services.Twitter
{
@ -14,13 +14,13 @@ namespace Microsoft.Toolkit.Services.Twitter
/// <summary>
/// Gets or sets indices of hashtag location in tweet string.
/// </summary>
[JsonProperty("indices")]
[JsonPropertyName("indices")]
public int[] Indices { get; set; }
/// <summary>
/// Gets or sets hashtag text, excluding #.
/// </summary>
[JsonProperty("text")]
[JsonPropertyName("text")]
public string Text { get; set; }
}
}

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

@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Toolkit.Services.Twitter
{
@ -14,73 +14,73 @@ namespace Microsoft.Toolkit.Services.Twitter
/// <summary>
/// Gets or sets ID as string.
/// </summary>
[JsonProperty("id_str")]
[JsonPropertyName("id_str")]
public string Id { get; set; }
/// <summary>
/// Gets or sets indices array.
/// </summary>
[JsonProperty("indices")]
[JsonPropertyName("indices")]
public int[] Indices { get; set; }
/// <summary>
/// Gets or sets MediaUrl (direct link to image).
/// </summary>
[JsonProperty("media_url")]
[JsonPropertyName("media_url")]
public string MediaUrl { get; set; }
/// <summary>
/// Gets or sets HTTPS MediaUrl.
/// </summary>
[JsonProperty("media_url_https")]
[JsonPropertyName("media_url_https")]
public string MediaUrlHttps { get; set; }
/// <summary>
/// Gets or sets t.co shortened tweet Url.
/// </summary>
[JsonProperty("url")]
[JsonPropertyName("url")]
public string Url { get; set; }
/// <summary>
/// Gets or sets DisplayUrl (pics.twitter.com Url).
/// </summary>
[JsonProperty("display_url")]
[JsonPropertyName("display_url")]
public string DisplayUrl { get; set; }
/// <summary>
/// Gets or sets DisplayUrl (pics.twitter.com Url).
/// </summary>
[JsonProperty("expanded_url")]
[JsonPropertyName("expanded_url")]
public string ExpandedUrl { get; set; }
/// <summary>
/// Gets or sets MediaType - photo, animated_gif, or video
/// </summary>
[JsonProperty("type")]
[JsonPropertyName("type")]
public string MediaType { get; set; }
/// <summary>
/// Gets or sets size array
/// </summary>
[JsonProperty("sizes")]
[JsonPropertyName("sizes")]
public TwitterMediaSizes Sizes { get; set; }
/// <summary>
/// Gets or sets the SourceId - tweet ID of media's original tweet
/// </summary>
[JsonProperty("source_status_id_str")]
[JsonPropertyName("source_status_id_str")]
public string SourceIdStr { get; set; }
/// <summary>
/// Gets or sets metadata for video attached to tweet
/// </summary>
[JsonProperty("video_info")]
[JsonPropertyName("video_info")]
public TwitterMediaVideoInfo VideoInfo { get; set; }
/// <summary>
/// Gets or sets extended metadata for video attached to tweet.
/// </summary>
[JsonProperty("additional_media_info")]
[JsonPropertyName("additional_media_info")]
public TwitterMediaAdditionalInfo AdditionalMediaInfo { get; set; }
}
}

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

@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Toolkit.Services.Twitter
{
@ -14,25 +14,25 @@ namespace Microsoft.Toolkit.Services.Twitter
/// <summary>
/// Gets or sets title of video
/// </summary>
[JsonProperty("title")]
[JsonPropertyName("title")]
public string Title { get; set; }
/// <summary>
/// Gets or sets description of video
/// </summary>
[JsonProperty("description")]
[JsonPropertyName("description")]
public string Description { get; set; }
/// <summary>
/// Gets or sets a value indicating whether video is embeddable
/// </summary>
[JsonProperty("embeddable")]
[JsonPropertyName("embeddable")]
public bool Embeddable { get; set; }
/// <summary>
/// Gets or sets a value indicating whether "monetizable"
/// </summary>
[JsonProperty("monetizable")]
[JsonPropertyName("monetizable")]
public bool Monetizable { get; set; }
}
}

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

@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Toolkit.Services.Twitter
{
@ -14,19 +14,19 @@ namespace Microsoft.Toolkit.Services.Twitter
/// <summary>
/// Gets or sets width integer.
/// </summary>
[JsonProperty("w")]
[JsonPropertyName("w")]
public int Width { get; set; }
/// <summary>
/// Gets or sets height integer.
/// </summary>
[JsonProperty("h")]
[JsonPropertyName("h")]
public int Height { get; set; }
/// <summary>
/// Gets or sets resize string.
/// </summary>
[JsonProperty("resize")]
[JsonPropertyName("resize")]
public string Resize { get; set; }
}
}

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

@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Toolkit.Services.Twitter
{
@ -14,25 +14,25 @@ namespace Microsoft.Toolkit.Services.Twitter
/// <summary>
/// Gets or sets small metadata.
/// </summary>
[JsonProperty("small")]
[JsonPropertyName("small")]
public TwitterMediaSizeData Small { get; set; }
/// <summary>
/// Gets or sets thumbnail metadata.
/// </summary>
[JsonProperty("thumb")]
[JsonPropertyName("thumb")]
public TwitterMediaSizeData Thumb { get; set; }
/// <summary>
/// Gets or sets large metadata.
/// </summary>
[JsonProperty("large")]
[JsonPropertyName("large")]
public TwitterMediaSizeData Large { get; set; }
/// <summary>
/// Gets or sets medium metadata.
/// </summary>
[JsonProperty("medium")]
[JsonPropertyName("medium")]
public TwitterMediaSizeData Medium { get; set; }
}
}

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

@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Toolkit.Services.Twitter
{
@ -14,19 +14,19 @@ namespace Microsoft.Toolkit.Services.Twitter
/// <summary>
/// Gets or sets video aspect ratio (width, height)
/// </summary>
[JsonProperty("aspect_ratio")]
[JsonPropertyName("aspect_ratio")]
public int[] AspectRatio { get; set; }
/// <summary>
/// Gets or sets duration of video in milliseconds
/// </summary>
[JsonProperty("duration_millis")]
[JsonPropertyName("duration_millis")]
public int Duration { get; set; }
/// <summary>
/// Gets or sets video variants for different codecs, bitrates, etc.
/// </summary>
[JsonProperty("variants")]
[JsonPropertyName("variants")]
public TwitterMediaVideoVariants[] Variants { get; set; }
}
}

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

@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Toolkit.Services.Twitter
{
@ -14,19 +14,19 @@ namespace Microsoft.Toolkit.Services.Twitter
/// <summary>
/// Gets or sets video bitrate in bits-per-second
/// </summary>
[JsonProperty("bitrate")]
[JsonPropertyName("bitrate")]
public int Bitrate { get; set; }
/// <summary>
/// Gets or sets the MIME type of the video
/// </summary>
[JsonProperty("content_type")]
[JsonPropertyName("content_type")]
public string ContentType { get; set; }
/// <summary>
/// Gets or sets the direct URL for the video variant
/// </summary>
[JsonProperty("url")]
[JsonPropertyName("url")]
public string Url { get; set; }
}
}

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

@ -8,10 +8,9 @@ using System.IO.Compression;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Toolkit.Services.Core;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Microsoft.Toolkit.Services.Twitter
{
@ -46,18 +45,14 @@ namespace Microsoft.Toolkit.Services.Twitter
/// <returns>String result.</returns>
public async Task<string> ExecuteGetAsync(Uri requestUri, TwitterOAuthTokens tokens, ISignatureManager signatureManager)
{
using (var request = new HttpRequestMessage(HttpMethod.Get, requestUri))
{
var requestBuilder = new TwitterOAuthRequestBuilder(requestUri, tokens, signatureManager, "GET");
using var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
var requestBuilder = new TwitterOAuthRequestBuilder(requestUri, tokens, signatureManager, "GET");
request.Headers.Authorization = AuthenticationHeaderValue.Parse(requestBuilder.AuthorizationHeader);
request.Headers.Authorization = AuthenticationHeaderValue.Parse(requestBuilder.AuthorizationHeader);
using (var response = await client.SendAsync(request).ConfigureAwait(false))
{
response.ThrowIfNotValid();
return ProcessErrors(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
}
}
using var response = await client.SendAsync(request).ConfigureAwait(false);
response.ThrowIfNotValid();
return ProcessErrors(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
}
/// <summary>
@ -70,29 +65,22 @@ namespace Microsoft.Toolkit.Services.Twitter
/// <returns>awaitable task</returns>
public async Task ExecuteGetStreamAsync(Uri requestUri, TwitterOAuthTokens tokens, TwitterStreamCallbacks.RawJsonCallback callback, ISignatureManager signatureManager)
{
using (var request = new HttpRequestMessage(HttpMethod.Get, requestUri))
using var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
var requestBuilder = new TwitterOAuthRequestBuilder(requestUri, tokens, signatureManager);
request.Headers.Authorization = AuthenticationHeaderValue.Parse(requestBuilder.AuthorizationHeader);
using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
response.ThrowIfNotValid();
using var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
using var reader = new StreamReader(responseStream);
while (!_abort && !reader.EndOfStream)
{
var requestBuilder = new TwitterOAuthRequestBuilder(requestUri, tokens, signatureManager);
var result = reader.ReadLine();
request.Headers.Authorization = AuthenticationHeaderValue.Parse(requestBuilder.AuthorizationHeader);
using (var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false))
if (!string.IsNullOrEmpty(result))
{
response.ThrowIfNotValid();
var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
using (var reader = new StreamReader(responseStream))
{
while (!_abort && !reader.EndOfStream)
{
var result = reader.ReadLine();
if (!string.IsNullOrEmpty(result))
{
callback?.Invoke(result);
}
}
}
callback?.Invoke(result);
}
}
}
@ -114,18 +102,14 @@ namespace Microsoft.Toolkit.Services.Twitter
/// <returns>String result.</returns>
public async Task<string> ExecutePostAsync(Uri requestUri, TwitterOAuthTokens tokens, ISignatureManager signatureManager)
{
using (var request = new HttpRequestMessage(HttpMethod.Post, requestUri))
{
var requestBuilder = new TwitterOAuthRequestBuilder(requestUri, tokens, signatureManager, "POST");
using var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
var requestBuilder = new TwitterOAuthRequestBuilder(requestUri, tokens, signatureManager, "POST");
request.Headers.Authorization = AuthenticationHeaderValue.Parse(requestBuilder.AuthorizationHeader);
request.Headers.Authorization = AuthenticationHeaderValue.Parse(requestBuilder.AuthorizationHeader);
using (var response = await client.SendAsync(request).ConfigureAwait(false))
{
response.ThrowIfNotValid();
return ProcessErrors(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
}
}
using var response = await client.SendAsync(request).ConfigureAwait(false);
response.ThrowIfNotValid();
return ProcessErrors(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
}
/// <summary>
@ -139,35 +123,26 @@ namespace Microsoft.Toolkit.Services.Twitter
/// <returns>String result.</returns>
public async Task<string> ExecutePostMultipartAsync(Uri requestUri, TwitterOAuthTokens tokens, string boundary, byte[] content, ISignatureManager signatureManager)
{
JToken mediaId = null;
JsonElement mediaId = default;
try
{
using (var multipartFormDataContent = new MultipartFormDataContent(boundary))
{
using (var byteContent = new ByteArrayContent(content))
{
multipartFormDataContent.Add(byteContent, "media");
using var multipartFormDataContent = new MultipartFormDataContent(boundary);
using var byteContent = new ByteArrayContent(content);
multipartFormDataContent.Add(byteContent, "media");
using (var request = new HttpRequestMessage(HttpMethod.Post, requestUri))
{
var requestBuilder = new TwitterOAuthRequestBuilder(requestUri, tokens, signatureManager, "POST");
using var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
var requestBuilder = new TwitterOAuthRequestBuilder(requestUri, tokens, signatureManager, "POST");
request.Headers.Authorization = AuthenticationHeaderValue.Parse(requestBuilder.AuthorizationHeader);
request.Headers.Authorization = AuthenticationHeaderValue.Parse(requestBuilder.AuthorizationHeader);
request.Content = multipartFormDataContent;
request.Content = multipartFormDataContent;
using (var response = await client.SendAsync(request).ConfigureAwait(false))
{
response.ThrowIfNotValid();
string jsonResult = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
JObject jObj = JObject.Parse(jsonResult);
mediaId = jObj["media_id_string"];
}
}
}
}
using var response = await client.SendAsync(request).ConfigureAwait(false);
response.ThrowIfNotValid();
using var jsonResult = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
var jObj = await JsonDocument.ParseAsync(jsonResult).ConfigureAwait(false);
mediaId = jObj.RootElement.GetProperty("media_id_string");
}
catch (ObjectDisposedException)
{
@ -182,7 +157,7 @@ namespace Microsoft.Toolkit.Services.Twitter
{
if (content.StartsWith("{\"errors\":"))
{
var errors = JsonConvert.DeserializeObject<TwitterErrors>(content);
var errors = JsonSerializer.Deserialize<TwitterErrors>(content);
throw new TwitterException { Errors = errors };
}

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

@ -2,16 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Microsoft.Toolkit.Services.Core;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Microsoft.Toolkit.Services.Twitter
{

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

@ -3,7 +3,7 @@
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using Newtonsoft.Json;
using System.Text.Json;
namespace Microsoft.Toolkit.Services.Twitter
{
@ -28,12 +28,12 @@ namespace Microsoft.Toolkit.Services.Twitter
try
{
return JsonConvert.DeserializeObject<List<T>>(data);
return JsonSerializer.Deserialize<List<T>>(data);
}
catch (JsonSerializationException)
catch (JsonException)
{
List<T> items = new List<T>();
items.Add(JsonConvert.DeserializeObject<T>(data));
items.Add(JsonSerializer.Deserialize<T>(data));
return items;
}
}

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

@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Toolkit.Services.Twitter
{
@ -14,49 +14,49 @@ namespace Microsoft.Toolkit.Services.Twitter
/// <summary>
/// Gets or sets the ID of the place
/// </summary>
[JsonProperty("id")]
[JsonPropertyName("id")]
public string Id { get; set; }
/// <summary>
/// Gets or sets the URL of additional place metadata
/// </summary>
[JsonProperty("url")]
[JsonPropertyName("url")]
public string Url { get; set; }
/// <summary>
/// Gets or sets the place type.
/// </summary>
[JsonProperty("place_type")]
[JsonPropertyName("place_type")]
public string PlaceType { get; set; }
/// <summary>
/// Gets or sets the place name.
/// </summary>
[JsonProperty("name")]
[JsonPropertyName("name")]
public string Name { get; set; }
/// <summary>
/// Gets or sets the full, human-readable place name.
/// </summary>
[JsonProperty("full_name")]
[JsonPropertyName("full_name")]
public string FullName { get; set; }
/// <summary>
/// Gets or sets the shortened country code (e.g. US) for the place.
/// </summary>
[JsonProperty("country_code")]
[JsonPropertyName("country_code")]
public string CountryCode { get; set; }
/// <summary>
/// Gets or sets the name of the country for the place.
/// </summary>
[JsonProperty("country")]
[JsonPropertyName("country")]
public string Country { get; set; }
/// <summary>
/// Gets or sets the bounding box coordinates of a location.
/// </summary>
[JsonProperty("bounding_box")]
[JsonPropertyName("bounding_box")]
public TwitterPlaceBoundingBox BoundingBox { get; set; }
}
}

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

@ -3,8 +3,7 @@
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Globalization;
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Toolkit.Services.Twitter
{
@ -16,13 +15,13 @@ namespace Microsoft.Toolkit.Services.Twitter
/// <summary>
/// Gets or sets the bounding box coordinates of the tweet's geolocation data.
/// </summary>
[JsonProperty("coordinates")]
[JsonPropertyName("coordinates")]
public List<List<float[]>> Coordinates { get; set; }
/// <summary>
/// Gets or sets the coordinate type. Polygon for a bounding box, Point for an exact coordinate.
/// </summary>
[JsonProperty("type")]
[JsonPropertyName("type")]
public string Type { get; set; }
/// <summary>

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

@ -4,7 +4,7 @@
using System;
using System.Globalization;
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Toolkit.Services.Twitter
{
@ -16,19 +16,19 @@ namespace Microsoft.Toolkit.Services.Twitter
/// <summary>
/// Gets or sets poll questions.
/// </summary>
[JsonProperty("options")]
[JsonPropertyName("options")]
public TwitterPollOptions[] Options { get; set; }
/// <summary>
/// Gets or sets end timestamp as a string.
/// </summary>
[JsonProperty("end_datetime")]
[JsonPropertyName("end_datetime")]
public string EndDateTime { get; set; }
/// <summary>
/// Gets or sets duration of the poll in minutes.
/// </summary>
[JsonProperty("duration_minutes")]
[JsonPropertyName("duration_minutes")]
public string DurationMinutes { get; set; }
/// <summary>

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

@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Toolkit.Services.Twitter
{
@ -14,13 +14,13 @@ namespace Microsoft.Toolkit.Services.Twitter
/// <summary>
/// Gets or sets int value of the poll position.
/// </summary>
[JsonProperty("position")]
[JsonPropertyName("position")]
public int Position { get; set; }
/// <summary>
/// Gets or sets text of the poll question.
/// </summary>
[JsonProperty("text")]
[JsonPropertyName("text")]
public string Text { get; set; }
}
}

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

@ -4,7 +4,7 @@
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using System.Text.Json;
namespace Microsoft.Toolkit.Services.Twitter
{
@ -25,7 +25,7 @@ namespace Microsoft.Toolkit.Services.Twitter
return null;
}
var result = JsonConvert.DeserializeObject<TwitterSearchResult>(data);
var result = JsonSerializer.Deserialize<TwitterSearchResult>(data);
return result.Statuses.ToList();
}

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

@ -400,15 +400,6 @@ namespace Microsoft.Toolkit.Services.Twitter
return Provider.LoginAsync();
}
/// <summary>
/// Log user out of Twitter.
/// </summary>
[Obsolete("Logout is deprecated, please use LogoutAsync instead.", true)]
public void Logout()
{
Provider.Logout();
}
/// <summary>
/// Log user out of Twitter.
/// </summary>

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

@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Toolkit.Services.Twitter
{
@ -15,14 +15,14 @@ namespace Microsoft.Toolkit.Services.Twitter
/// Gets or sets the user id of the event. This is always the user who initiated the event.
/// </summary>
/// <value>The user Id.</value>
[JsonProperty(PropertyName = "user_id_str")]
[JsonPropertyName("user_id_str")]
public string UserId { get; set; }
/// <summary>
/// Gets or sets the id of the event. This is the tweet that was affected.
/// </summary>
/// <value>The tweet Id.</value>
[JsonProperty(PropertyName = "id_str")]
[JsonPropertyName("id_str")]
public string Id { get; set; }
}
}

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

@ -4,8 +4,7 @@
using System;
using System.Globalization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System.Text.Json.Serialization;
namespace Microsoft.Toolkit.Services.Twitter
{
@ -18,8 +17,8 @@ namespace Microsoft.Toolkit.Services.Twitter
/// Gets or sets the type of the event.
/// </summary>
/// <value>The type of the event.</value>
[JsonProperty(PropertyName = "event")]
[JsonConverter(typeof(StringEnumConverter))]
[JsonPropertyName("event")]
[JsonConverter(typeof(JsonStringEnumConverter))]
public TwitterStreamEventType EventType { get; set; }
/// <summary>
@ -44,7 +43,7 @@ namespace Microsoft.Toolkit.Services.Twitter
/// Gets or sets the creation date.
/// </summary>
/// <value>The creation date.</value>
[JsonProperty(PropertyName = "created_at")]
[JsonPropertyName("created_at")]
public string CreatedAt { get; set; }
/// <summary>

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

@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Toolkit.Services.Twitter
{
@ -14,13 +14,13 @@ namespace Microsoft.Toolkit.Services.Twitter
/// <summary>
/// Gets or sets indices of hashtag location in tweet string.
/// </summary>
[JsonProperty("indices")]
[JsonPropertyName("indices")]
public int[] Indices { get; set; }
/// <summary>
/// Gets or sets hashtag text, excluding #.
/// </summary>
[JsonProperty("text")]
[JsonPropertyName("text")]
public string Text { get; set; }
}
}

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

@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Toolkit.Services.Twitter
{
@ -14,31 +14,31 @@ namespace Microsoft.Toolkit.Services.Twitter
/// <summary>
/// Gets or sets DisplayUrl of the Url.
/// </summary>
[JsonProperty("display_url")]
[JsonPropertyName("display_url")]
public string DisplayUrl { get; set; }
/// <summary>
/// Gets or sets ExpandedUrl of the Url.
/// </summary>
[JsonProperty("expanded_url")]
[JsonPropertyName("expanded_url")]
public string ExpandedUrl { get; set; }
/// <summary>
/// Gets or sets indices position of the tweet.
/// </summary>
[JsonProperty("indices")]
[JsonPropertyName("indices")]
public int[] Indices { get; set; }
/// <summary>
/// Gets or sets unwound Url metadata position of the tweet.
/// </summary>
[JsonProperty("unwound")]
[JsonPropertyName("unwound")]
public TwitterUrlUnwound Unwound { get; set; }
/// <summary>
/// Gets or sets t.co Url of the tweet.
/// </summary>
[JsonProperty("url")]
[JsonPropertyName("url")]
public string Url { get; set; }
}
}

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

@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Toolkit.Services.Twitter
{
@ -14,25 +14,25 @@ namespace Microsoft.Toolkit.Services.Twitter
/// <summary>
/// Gets or sets fully unwound url.
/// </summary>
[JsonProperty("url")]
[JsonPropertyName("url")]
public string Url { get; set; }
/// <summary>
/// Gets or sets status of unwind; if anything but 200 is bad data.
/// </summary>
[JsonProperty("status")]
[JsonPropertyName("status")]
public int Status { get; set; }
/// <summary>
/// Gets or sets HTML title for url.
/// </summary>
[JsonProperty("title")]
[JsonPropertyName("title")]
public string Title { get; set; }
/// <summary>
/// Gets or sets description of link.
/// </summary>
[JsonProperty("description")]
[JsonPropertyName("description")]
public string Description { get; set; }
}
}

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

@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Toolkit.Services.Twitter
{
@ -14,194 +14,194 @@ namespace Microsoft.Toolkit.Services.Twitter
/// <summary>
/// Gets or sets user Id.
/// </summary>
[JsonProperty("id_str")]
[JsonPropertyName("id_str")]
public string Id { get; set; }
/// <summary>
/// Gets or sets user name.
/// </summary>
[JsonProperty("name")]
[JsonPropertyName("name")]
public string Name { get; set; }
/// <summary>
/// Gets or sets user screen name.
/// </summary>
[JsonProperty("screen_name")]
[JsonPropertyName("screen_name")]
public string ScreenName { get; set; }
/// <summary>
/// Gets or sets profile location.
/// </summary>
[JsonProperty("location")]
[JsonPropertyName("location")]
public string Location { get; set; }
/// <summary>
/// Gets or sets profile url.
/// </summary>
[JsonProperty("url")]
[JsonPropertyName("url")]
public string Url { get; set; }
/// <summary>
/// Gets or sets profile description.
/// </summary>
[JsonProperty("description")]
[JsonPropertyName("description")]
public string Description { get; set; }
/// <summary>
/// Gets or sets a value indicating whether protected status of user.
/// </summary>
[JsonProperty("protected")]
[JsonPropertyName("protected")]
public bool Protected { get; set; }
/// <summary>
/// Gets or sets a value indicating whether account is verified (blue check mark).
/// </summary>
[JsonProperty("verified")]
[JsonPropertyName("verified")]
public bool Verified { get; set; }
/// <summary>
/// Gets or sets followers count.
/// </summary>
[JsonProperty("followers_count")]
[JsonPropertyName("followers_count")]
public int FollowersCount { get; set; }
/// <summary>
/// Gets or sets count of accounts user is following.
/// </summary>
[JsonProperty("friends_count")]
[JsonPropertyName("friends_count")]
public int FriendsCount { get; set; }
/// <summary>
/// Gets or sets count of public lists user is a member of.
/// </summary>
[JsonProperty("listed_count")]
[JsonPropertyName("listed_count")]
public int ListedCount { get; set; }
/// <summary>
/// Gets or sets total count of tweets user has liked.
/// </summary>
[JsonProperty("favourites_count")]
[JsonPropertyName("favourites_count")]
public int FavoritesCount { get; set; }
/// <summary>
/// Gets or sets total count of tweets (including retweets) posted by user.
/// </summary>
[JsonProperty("statuses_count")]
[JsonPropertyName("statuses_count")]
public int StatusesCount { get; set; }
/// <summary>
/// Gets or sets a value indicating whether geotagging is enabled.
/// This determines whether or not to geotag the user's posts.
/// </summary>
[JsonProperty("geo_enabled")]
[JsonPropertyName("geo_enabled")]
public bool GeoEnabled { get; set; }
/// <summary>
/// Gets or sets BCP 47 language code according to user's account settings.
/// </summary>
[JsonProperty("lang")]
[JsonPropertyName("lang")]
public string Lang { get; set; }
/// <summary>
/// Gets or sets a value indicating whether contributor mode is enabled.
/// </summary>
[JsonProperty("contributors_enabled")]
[JsonPropertyName("contributors_enabled")]
public bool ContributorsEnabled { get; set; }
/// <summary>
/// Gets or sets profile background color (web hex value).
/// </summary>
[JsonProperty("profile_background_color")]
[JsonPropertyName("profile_background_color")]
public string ProfileBackgroundColor { get; set; }
/// <summary>
/// Gets or sets profile background image url.
/// </summary>
[JsonProperty("profile_background_image_url")]
[JsonPropertyName("profile_background_image_url")]
public string ProfileBackgroundImageUrl { get; set; }
/// <summary>
/// Gets or sets profile background image url using https.
/// </summary>
[JsonProperty("profile_background_image_url_https")]
[JsonPropertyName("profile_background_image_url_https")]
public string ProfileBackgroundImageUrlHttps { get; set; }
/// <summary>
/// Gets or sets a value indicating whether profile background image is tiled.
/// </summary>
[JsonProperty("profile_background_tile")]
[JsonPropertyName("profile_background_tile")]
public bool ProfileBackgroundTile { get; set; }
/// <summary>
/// Gets or sets profile banner url.
/// </summary>
[JsonProperty("profile_banner_url")]
[JsonPropertyName("profile_banner_url")]
public string ProfileBannerUrl { get; set; }
/// <summary>
/// Gets or sets profile image url.
/// </summary>
[JsonProperty("profile_image_url")]
[JsonPropertyName("profile_image_url")]
public string ProfileImageUrl { get; set; }
/// <summary>
/// Gets or sets profile image url using https.
/// </summary>
[JsonProperty("profile_image_url_https")]
[JsonPropertyName("profile_image_url_https")]
public string ProfileImageUrlHttps { get; set; }
/// <summary>
/// Gets or sets profile link color (web hex value).
/// </summary>
[JsonProperty("profile_link_color")]
[JsonPropertyName("profile_link_color")]
public string ProfileLinkColor { get; set; }
/// <summary>
/// Gets or sets profile sidebar border color (web hex value).
/// </summary>
[JsonProperty("profile_sidebar_border_color")]
[JsonPropertyName("profile_sidebar_border_color")]
public string ProfileSidebarBorderColor { get; set; }
/// <summary>
/// Gets or sets profile sidebar fill color (web hex value).
/// </summary>
[JsonProperty("profile_sidebar_fill_color")]
[JsonPropertyName("profile_sidebar_fill_color")]
public string ProfileSidebarFillColor { get; set; }
/// <summary>
/// Gets or sets profile text color (web hex value).
/// </summary>
[JsonProperty("profile_text_color")]
[JsonPropertyName("profile_text_color")]
public string ProfileTextColor { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the user has selected to use their uploaded background image in their profile.
/// </summary>
[JsonProperty("profile_use_background_image")]
[JsonPropertyName("profile_use_background_image")]
public bool ProfileUseBackgroundImage { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not user is using the default profile theme and background.
/// </summary>
[JsonProperty("default_profile")]
[JsonPropertyName("default_profile")]
public bool DefaultProfile { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not the user is using the default profile image.
/// </summary>
[JsonProperty("default_profile_image")]
[JsonPropertyName("default_profile_image")]
public bool DefaultProfileImage { get; set; }
/// <summary>
/// Gets or sets "withheld in" countries.
/// </summary>
[JsonProperty("withheld_in_countries")]
[JsonPropertyName("withheld_in_countries")]
public string[] WithheldInCountries { get; set; }
/// <summary>
/// Gets or sets withheld scope (status or profile).
/// </summary>
[JsonProperty("withheld_scope")]
[JsonPropertyName("withheld_scope")]
public string WithheldScope { get; set; }
}
}

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

@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Toolkit.Services.Twitter
{
@ -14,7 +14,7 @@ namespace Microsoft.Toolkit.Services.Twitter
/// <summary>
/// Gets or sets the start and end position of the user mention
/// </summary>
[JsonProperty("indices")]
[JsonPropertyName("indices")]
public int[] Indices { get; set; }
}
}

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

@ -2,8 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Text.Json;
namespace Microsoft.Toolkit.Services.Twitter
{
@ -24,55 +23,47 @@ namespace Microsoft.Toolkit.Services.Twitter
return null;
}
var obj = (JObject)JsonConvert.DeserializeObject(data);
var obj = JsonDocument.Parse(data);
var friends = obj.SelectToken("friends", false);
if (friends != null && friends.HasValues)
if (obj.RootElement.TryGetProperty("friends", out var friends) && friends.GetArrayLength() > 0)
{
return null;
}
var delete = obj.SelectToken("delete", false);
if (delete != null)
if (obj.RootElement.TryGetProperty("delete", out var delete))
{
var deletedStatus = delete.SelectToken("status", false);
if (deletedStatus != null && deletedStatus.HasValues)
if (delete.TryGetProperty("status", out var deletedStatus) && deletedStatus.GetArrayLength() > 0)
{
return JsonConvert.DeserializeObject<TwitterStreamDeletedEvent>(deletedStatus.ToString());
return JsonSerializer.Deserialize<TwitterStreamDeletedEvent>(deletedStatus.ToString());
}
var deletedDirectMessage = delete.SelectToken("direct_message", false);
if (deletedDirectMessage != null && deletedDirectMessage.HasValues)
if (delete.TryGetProperty("direct_message", out var deletedDirectMessage) && deletedDirectMessage.GetArrayLength() > 0)
{
return JsonConvert.DeserializeObject<TwitterStreamDeletedEvent>(deletedDirectMessage.ToString());
return JsonSerializer.Deserialize<TwitterStreamDeletedEvent>(deletedDirectMessage.ToString());
}
}
var events = obj.SelectToken("event", false);
if (events != null)
if (obj.RootElement.TryGetProperty("event", out var events))
{
var targetObject = obj.SelectToken("target_object", false);
Tweet endTargetObject = null;
if (targetObject?.SelectToken("user", false) != null)
if (obj.RootElement.TryGetProperty("target_object", out var targetObject) && targetObject.TryGetProperty("user", out _))
{
endTargetObject = JsonConvert.DeserializeObject<Tweet>(targetObject.ToString());
endTargetObject = JsonSerializer.Deserialize<Tweet>(targetObject.ToString());
}
var endEvent = JsonConvert.DeserializeObject<TwitterStreamEvent>(obj.ToString());
var endEvent = JsonSerializer.Deserialize<TwitterStreamEvent>(obj.ToString());
endEvent.TargetObject = endTargetObject;
return endEvent;
}
var user = obj.SelectToken("user", false);
if (user != null && user.HasValues)
if (obj.RootElement.TryGetProperty("user", out var user) && user.GetArrayLength() > 0)
{
return JsonConvert.DeserializeObject<Tweet>(obj.ToString());
return JsonSerializer.Deserialize<Tweet>(obj.ToString());
}
var directMessage = obj.SelectToken("direct_message", false);
if (directMessage != null && directMessage.HasValues)
if (obj.RootElement.TryGetProperty("direct_message", out var directMessage) && directMessage.GetArrayLength() > 0)
{
return JsonConvert.DeserializeObject<TwitterDirectMessage>(directMessage.ToString());
return JsonSerializer.Deserialize<TwitterDirectMessage>(directMessage.ToString());
}
return null;

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

@ -8,12 +8,11 @@ using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Toolkit.Parsers;
using Microsoft.Toolkit.Services.Core;
using Microsoft.Toolkit.Services.OAuth;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
#if WINRT
using System.Runtime.InteropServices.WindowsRuntime;
@ -163,17 +162,6 @@ namespace Microsoft.Toolkit.Services.Weibo
return false;
}
/// <summary>
/// Log user out of Weibo.
/// </summary>
[Obsolete("Logout is deprecated, please use LogoutAsync instead.", true)]
public void Logout()
{
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
LogoutAsync();
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
}
/// <summary>
/// Log user out of Weibo.
/// </summary>
@ -235,15 +223,15 @@ namespace Microsoft.Toolkit.Services.Weibo
data = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
}
JObject jObject = JObject.Parse(data);
var jObject = JsonDocument.Parse(data);
string accessToken = jObject["access_token"].ToObject<string>();
string accessToken = jObject.RootElement.GetProperty("access_token").GetString();
if (string.IsNullOrEmpty(accessToken))
{
throw new NullReferenceException("The accessToken is null.");
}
long uid = jObject["uid"].ToObject<long>();
long uid = jObject.RootElement.GetProperty("uid").GetInt64();
Uid = uid;
_tokens.AccessToken = accessToken;
@ -276,7 +264,7 @@ namespace Microsoft.Toolkit.Services.Weibo
WeiboOAuthRequest request = new WeiboOAuthRequest();
rawResult = await request.ExecuteGetAsync(uri, _tokens);
return JsonConvert.DeserializeObject<WeiboUser>(rawResult);
return JsonSerializer.Deserialize<WeiboUser>(rawResult);
}
catch (UserNotFoundException)
{
@ -286,7 +274,7 @@ namespace Microsoft.Toolkit.Services.Weibo
{
if (!string.IsNullOrEmpty(rawResult))
{
var error = JsonConvert.DeserializeObject<WeiboError>(rawResult);
var error = JsonSerializer.Deserialize<WeiboError>(rawResult);
throw new WeiboException { Error = error };
}
@ -327,7 +315,7 @@ namespace Microsoft.Toolkit.Services.Weibo
{
if (!string.IsNullOrEmpty(rawResult))
{
var errors = JsonConvert.DeserializeObject<WeiboError>(rawResult);
var errors = JsonSerializer.Deserialize<WeiboError>(rawResult);
throw new WeiboException { Error = errors };
}

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

@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Toolkit.Services.Weibo
{
@ -14,13 +14,13 @@ namespace Microsoft.Toolkit.Services.Weibo
/// <summary>
/// Gets or sets error code
/// </summary>
[JsonProperty("error_code")]
[JsonPropertyName("error_code")]
public int Code { get; set; }
/// <summary>
/// Gets or sets error message
/// </summary>
[JsonProperty("error")]
[JsonPropertyName("error")]
public string Message { get; set; }
}
}

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

@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Toolkit.Services.Services.Weibo
{
@ -14,13 +14,13 @@ namespace Microsoft.Toolkit.Services.Services.Weibo
/// <summary>
/// Gets the type of geographic information
/// </summary>
[JsonProperty("type")]
[JsonPropertyName("type")]
public string Type { get; internal set; }
/// <summary>
/// Gets the coordinates
/// </summary>
[JsonProperty("coordinates")]
[JsonPropertyName("coordinates")]
public double[] Coordinates { get; internal set; }
/// <inheritdoc/>

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

@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Toolkit.Services.Weibo
{
@ -14,19 +14,19 @@ namespace Microsoft.Toolkit.Services.Weibo
/// <summary>
/// Gets or sets the url of the attached image in thumbnail size.
/// </summary>
[JsonProperty("thumbnail_pic")]
[JsonPropertyName("thumbnail_pic")]
public string ThumbnailImageUrl { get; set; }
/// <summary>
/// Gets or sets the url of the attached image in medium size.
/// </summary>
[JsonProperty("bmiddle_pic")]
[JsonPropertyName("bmiddle_pic")]
public string MediumImageUrl { get; set; }
/// <summary>
/// Gets or sets the url of the attached image in original size.
/// </summary>
[JsonProperty("original_pic")]
[JsonPropertyName("original_pic")]
public string OriginalImageUrl { get; set; }
}
}

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

@ -7,9 +7,9 @@ using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Toolkit.Services.OAuth;
using Newtonsoft.Json;
namespace Microsoft.Toolkit.Services.Weibo
{
@ -41,26 +41,22 @@ namespace Microsoft.Toolkit.Services.Weibo
/// <returns>String result.</returns>
public async Task<string> ExecuteGetAsync(Uri requestUri, WeiboOAuthTokens tokens)
{
using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUri))
using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUri);
UriBuilder requestUriBuilder = new UriBuilder(request.RequestUri);
if (requestUriBuilder.Query.StartsWith("?"))
{
UriBuilder requestUriBuilder = new UriBuilder(request.RequestUri);
if (requestUriBuilder.Query.StartsWith("?"))
{
requestUriBuilder.Query = requestUriBuilder.Query.Substring(1) + "&access_token=" + OAuthEncoder.UrlEncode(tokens.AccessToken);
}
else
{
requestUriBuilder.Query = requestUriBuilder.Query + "?access_token=" + OAuthEncoder.UrlEncode(tokens.AccessToken);
}
request.RequestUri = requestUriBuilder.Uri;
using (HttpResponseMessage response = await _client.SendAsync(request).ConfigureAwait(false))
{
response.ThrowIfNotValid();
return ProcessError(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
}
requestUriBuilder.Query = requestUriBuilder.Query.Substring(1) + "&access_token=" + OAuthEncoder.UrlEncode(tokens.AccessToken);
}
else
{
requestUriBuilder.Query = requestUriBuilder.Query + "?access_token=" + OAuthEncoder.UrlEncode(tokens.AccessToken);
}
request.RequestUri = requestUriBuilder.Uri;
using HttpResponseMessage response = await _client.SendAsync(request).ConfigureAwait(false);
response.ThrowIfNotValid();
return ProcessError(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
}
/// <summary>
@ -75,38 +71,33 @@ namespace Microsoft.Toolkit.Services.Weibo
var contentDict = new Dictionary<string, string>();
contentDict.Add("status", status);
using (var formUrlEncodedContent = new FormUrlEncodedContent(contentDict))
using var formUrlEncodedContent = new FormUrlEncodedContent(contentDict);
using var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
UriBuilder requestUriBuilder = new UriBuilder(request.RequestUri);
if (requestUriBuilder.Query.StartsWith("?"))
{
using (var request = new HttpRequestMessage(HttpMethod.Post, requestUri))
{
UriBuilder requestUriBuilder = new UriBuilder(request.RequestUri);
if (requestUriBuilder.Query.StartsWith("?"))
{
requestUriBuilder.Query = requestUriBuilder.Query.Substring(1) + "&access_token=" + OAuthEncoder.UrlEncode(tokens.AccessToken);
}
else
{
requestUriBuilder.Query = requestUriBuilder.Query + "access_token=" + OAuthEncoder.UrlEncode(tokens.AccessToken);
}
requestUriBuilder.Query = requestUriBuilder.Query.Substring(1) + "&access_token=" + OAuthEncoder.UrlEncode(tokens.AccessToken);
}
else
{
requestUriBuilder.Query = requestUriBuilder.Query + "access_token=" + OAuthEncoder.UrlEncode(tokens.AccessToken);
}
request.RequestUri = requestUriBuilder.Uri;
request.RequestUri = requestUriBuilder.Uri;
request.Content = formUrlEncodedContent;
request.Content = formUrlEncodedContent;
using (var response = await _client.SendAsync(request).ConfigureAwait(false))
{
if (response.StatusCode == HttpStatusCode.OK)
{
return JsonConvert.DeserializeObject<WeiboStatus>(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
}
else
{
response.ThrowIfNotValid();
ProcessError(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
return null;
}
}
}
using var response = await _client.SendAsync(request).ConfigureAwait(false);
if (response.StatusCode == HttpStatusCode.OK)
{
using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
return await JsonSerializer.DeserializeAsync<WeiboStatus>(stream).ConfigureAwait(false);
}
else
{
response.ThrowIfNotValid();
ProcessError(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
return null;
}
}
@ -122,49 +113,41 @@ namespace Microsoft.Toolkit.Services.Weibo
{
try
{
using (var multipartFormDataContent = new MultipartFormDataContent())
using var multipartFormDataContent = new MultipartFormDataContent();
using var stringContent = new StringContent(status);
multipartFormDataContent.Add(stringContent, "status");
using var byteContent = new ByteArrayContent(content);
// Somehow Weibo's backend requires a Filename field to work
byteContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { FileName = "attachment", Name = "pic" };
multipartFormDataContent.Add(byteContent, "pic");
using var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
UriBuilder requestUriBuilder = new UriBuilder(request.RequestUri);
if (requestUriBuilder.Query.StartsWith("?"))
{
using (var stringContent = new StringContent(status))
{
multipartFormDataContent.Add(stringContent, "status");
using (var byteContent = new ByteArrayContent(content))
{
// Somehow Weibo's backend requires a Filename field to work
byteContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { FileName = "attachment", Name = "pic" };
multipartFormDataContent.Add(byteContent, "pic");
requestUriBuilder.Query = requestUriBuilder.Query.Substring(1) + "&access_token=" + OAuthEncoder.UrlEncode(tokens.AccessToken);
}
else
{
requestUriBuilder.Query = requestUriBuilder.Query + "access_token=" + OAuthEncoder.UrlEncode(tokens.AccessToken);
}
using (var request = new HttpRequestMessage(HttpMethod.Post, requestUri))
{
UriBuilder requestUriBuilder = new UriBuilder(request.RequestUri);
if (requestUriBuilder.Query.StartsWith("?"))
{
requestUriBuilder.Query = requestUriBuilder.Query.Substring(1) + "&access_token=" + OAuthEncoder.UrlEncode(tokens.AccessToken);
}
else
{
requestUriBuilder.Query = requestUriBuilder.Query + "access_token=" + OAuthEncoder.UrlEncode(tokens.AccessToken);
}
request.RequestUri = requestUriBuilder.Uri;
request.RequestUri = requestUriBuilder.Uri;
request.Content = multipartFormDataContent;
request.Content = multipartFormDataContent;
using (var response = await _client.SendAsync(request).ConfigureAwait(false))
{
if (response.StatusCode == HttpStatusCode.OK)
{
return JsonConvert.DeserializeObject<WeiboStatus>(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
}
else
{
response.ThrowIfNotValid();
ProcessError(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
return null;
}
}
}
}
}
using var response = await _client.SendAsync(request).ConfigureAwait(false);
if (response.StatusCode == HttpStatusCode.OK)
{
using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
return await JsonSerializer.DeserializeAsync<WeiboStatus>(stream).ConfigureAwait(false);
}
else
{
response.ThrowIfNotValid();
ProcessError(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
return null;
}
}
catch (ObjectDisposedException)
@ -180,10 +163,15 @@ namespace Microsoft.Toolkit.Services.Weibo
{
if (content.StartsWith("{\"error\":"))
{
WeiboError error = JsonConvert.DeserializeObject<WeiboError>(content, new JsonSerializerSettings()
WeiboError error;
try
{
Error = (sender, args) => throw new JsonException("Invalid Weibo error response!", args.ErrorContext.Error)
});
error = JsonSerializer.Deserialize<WeiboError>(content);
}
catch (JsonException e)
{
throw new JsonException("Invalid Weibo error response!", e);
}
throw new WeiboException { Error = error };
}

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

@ -2,16 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Microsoft.Toolkit.Services.Core;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Microsoft.Toolkit.Services.Weibo
{

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

@ -3,7 +3,7 @@
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using Newtonsoft.Json;
using System.Text.Json;
namespace Microsoft.Toolkit.Services.Weibo
{
@ -28,12 +28,12 @@ namespace Microsoft.Toolkit.Services.Weibo
try
{
return JsonConvert.DeserializeObject<List<T>>(data);
return JsonSerializer.Deserialize<List<T>>(data);
}
catch (JsonSerializationException)
catch (JsonException)
{
List<T> items = new List<T>();
items.Add(JsonConvert.DeserializeObject<T>(data));
items.Add(JsonSerializer.Deserialize<T>(data));
return items;
}
}

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

@ -183,15 +183,6 @@ namespace Microsoft.Toolkit.Services.Weibo
return Provider.LoginAsync();
}
/// <summary>
/// Log user out of Weibo.
/// </summary>
[Obsolete("Logout is deprecated, please use LogoutAsync instead.", true)]
public void Logout()
{
Provider.Logout();
}
/// <summary>
/// Log user out of Weibo.
/// </summary>

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

@ -4,8 +4,8 @@
using System;
using System.Globalization;
using System.Text.Json.Serialization;
using Microsoft.Toolkit.Services.Services.Weibo;
using Newtonsoft.Json;
namespace Microsoft.Toolkit.Services.Weibo
{
@ -17,7 +17,7 @@ namespace Microsoft.Toolkit.Services.Weibo
/// <summary>
/// Gets or sets time item was created.
/// </summary>
[JsonProperty("created_at")]
[JsonPropertyName("created_at")]
public string CreatedAt { get; set; }
/// <summary>
@ -40,13 +40,13 @@ namespace Microsoft.Toolkit.Services.Weibo
/// <summary>
/// Gets or sets item Id.
/// </summary>
[JsonProperty("id")]
[JsonPropertyName("id")]
public string Id { get; set; }
/// <summary>
/// Gets or sets text of the status (handles both 140 and 280 characters)
/// </summary>
[JsonProperty("text")]
[JsonPropertyName("text")]
public string Text { get; set; }
/// <summary>
@ -54,85 +54,85 @@ namespace Microsoft.Toolkit.Services.Weibo
/// (true when Weibo status is longer than 140 characters)
/// This entity may be deprecated - it never seems to be set to true.
/// </summary>
[JsonProperty("truncated")]
[JsonPropertyName("truncated")]
public bool IsTruncated { get; set; }
/// <summary>
/// Gets or sets status source (client or website used)
/// </summary>
[JsonProperty("source")]
[JsonPropertyName("source")]
public string Source { get; set; }
/// <summary>
/// Gets or sets in_reply_to_screen_name
/// </summary>
[JsonProperty("in_reply_to_screen_name")]
[JsonPropertyName("in_reply_to_screen_name")]
public string InReplyToScreenName { get; set; }
/// <summary>
/// Gets or sets in_reply_to_status_id
/// </summary>
[JsonProperty("in_reply_to_status_id")]
[JsonPropertyName("in_reply_to_status_id")]
public string InReplyToStatusId { get; set; }
/// <summary>
/// Gets or sets in_reply_to_user_id
/// </summary>
[JsonProperty("in_reply_to_user_id")]
[JsonPropertyName("in_reply_to_user_id")]
public string InReplyToUserId { get; set; }
/// <summary>
/// Gets or sets user who posted the status.
/// </summary>
[JsonProperty("user")]
[JsonPropertyName("user")]
public WeiboUser User { get; set; }
/// <summary>
/// Gets or sets the Reposted Weibo status
/// </summary>
[JsonProperty("retweeted_status")]
[JsonPropertyName("retweeted_status")]
public WeiboStatus RepostedStatus { get; set; }
/// <summary>
/// Gets or sets the repost count
/// </summary>
[JsonProperty("reposts_count")]
[JsonPropertyName("reposts_count")]
public int RepostCount { get; set; }
/// <summary>
/// Gets or sets the comment count
/// </summary>
[JsonProperty("comments_count")]
[JsonPropertyName("comments_count")]
public int CommentCount { get; set; }
/// <summary>
/// Gets or sets the url of the attached image in thumbnail size.
/// </summary>
[JsonProperty("thumbnail_pic")]
[JsonPropertyName("thumbnail_pic")]
public string ThumbnailImageUrl { get; set; }
/// <summary>
/// Gets or sets the url of the attached image in medium size.
/// </summary>
[JsonProperty("bmiddle_pic")]
[JsonPropertyName("bmiddle_pic")]
public string MediumImageUrl { get; set; }
/// <summary>
/// Gets or sets the url of the attached image in original size.
/// </summary>
[JsonProperty("original_pic")]
[JsonPropertyName("original_pic")]
public string OriginalImageUrl { get; set; }
/// <summary>
/// Gets or sets attached images array of the weibo.
/// </summary>
[JsonProperty("pic_urls")]
[JsonPropertyName("pic_urls")]
public WeiboImage[] AttachedImages { get; set; }
/// <summary>
/// Gets or sets the geographic information.
/// </summary>
[JsonProperty("geo")]
[JsonPropertyName("geo")]
public WeiboGeoInfo GeographicInfo { get; set; }
/// <summary>

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

@ -2,14 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Toolkit.Services.Weibo;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Text.Json;
namespace Microsoft.Toolkit.Services.Weibo
{
@ -30,14 +24,14 @@ namespace Microsoft.Toolkit.Services.Weibo
return null;
}
JObject rawObject = JObject.Parse(data);
var rawObject = JsonDocument.Parse(data);
IList<JToken> rawStatuses = rawObject["statuses"].Children().ToList();
var rawStatuses = rawObject.RootElement.GetProperty("statuses");
IList<WeiboStatus> statuses = new List<WeiboStatus>();
foreach (JToken result in rawStatuses)
foreach (var rawStatus in rawStatuses.EnumerateArray())
{
WeiboStatus searchResult = result.ToObject<WeiboStatus>();
WeiboStatus searchResult = JsonSerializer.Deserialize<WeiboStatus>(rawStatus.ToString());
statuses.Add(searchResult);
}

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

@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Toolkit.Services.Weibo
{
@ -14,79 +14,79 @@ namespace Microsoft.Toolkit.Services.Weibo
/// <summary>
/// Gets or sets user Id.
/// </summary>
[JsonProperty("id")]
[JsonPropertyName("id")]
public string Id { get; set; }
/// <summary>
/// Gets or sets user name.
/// </summary>
[JsonProperty("name")]
[JsonPropertyName("name")]
public string Name { get; set; }
/// <summary>
/// Gets or sets user screen name.
/// </summary>
[JsonProperty("screen_name")]
[JsonPropertyName("screen_name")]
public string ScreenName { get; set; }
/// <summary>
/// Gets or sets profile location.
/// </summary>
[JsonProperty("location")]
[JsonPropertyName("location")]
public string Location { get; set; }
/// <summary>
/// Gets or sets profile url.
/// </summary>
[JsonProperty("url")]
[JsonPropertyName("url")]
public string Url { get; set; }
/// <summary>
/// Gets or sets profile description.
/// </summary>
[JsonProperty("description")]
[JsonPropertyName("description")]
public string Description { get; set; }
/// <summary>
/// Gets or sets profile image url.
/// </summary>
[JsonProperty("profile_image_url")]
[JsonPropertyName("profile_image_url")]
public string ProfileImageUrl { get; set; }
/// <summary>
/// Gets or sets high resolution profile image url.
/// </summary>
[JsonProperty("avatar_large")]
[JsonPropertyName("avatar_large")]
public string HighResolutionProfileImageUrl { get; set; }
/// <summary>
/// Gets or sets followers count.
/// </summary>
[JsonProperty("followers_count")]
[JsonPropertyName("followers_count")]
public int FollowersCount { get; set; }
/// <summary>
/// Gets or sets count of accounts user is following.
/// </summary>
[JsonProperty("friends_count")]
[JsonPropertyName("friends_count")]
public int FriendsCount { get; set; }
/// <summary>
/// Gets or sets total count of statuses user has liked.
/// </summary>
[JsonProperty("favourites_count")]
[JsonPropertyName("favourites_count")]
public int FavoritesCount { get; set; }
/// <summary>
/// Gets or sets total count of Weibo statuses (including reposted statuses) posted by user.
/// </summary>
[JsonProperty("statuses_count")]
[JsonPropertyName("statuses_count")]
public int StatusesCount { get; set; }
/// <summary>
/// Gets or sets a value indicating whether account is verified (blue check mark).
/// </summary>
[JsonProperty("verified")]
[JsonPropertyName("verified")]
public bool Verified { get; set; }
}
}

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

@ -12,7 +12,6 @@ using Microsoft.Toolkit.Uwp.Helpers;
using Windows.Devices.Bluetooth;
using Windows.Devices.Bluetooth.Advertisement;
using Windows.Devices.Enumeration;
using Windows.Foundation.Metadata;
using Windows.System;
namespace Microsoft.Toolkit.Uwp.Connectivity
@ -27,11 +26,6 @@ namespace Microsoft.Toolkit.Uwp.Connectivity
/// </summary>
private const string BluetoothLeDeviceWatcherAqs = "(System.Devices.Aep.ProtocolId:=\"{bb7bb05e-5972-42b5-94fc-76eaa7084d49}\")";
/// <summary>
/// Gets a value indicating whether the Bluetooth LE Helper is supported
/// </summary>
private static bool? _isBluetoothLESupported = null;
/// <summary>
/// We need to cache all DeviceInformation objects we get as they may
/// get updated in the future. The update may make them eligible to be put on
@ -82,12 +76,6 @@ namespace Microsoft.Toolkit.Uwp.Connectivity
/// </summary>
public static BluetoothLEHelper Context { get; } = new BluetoothLEHelper();
/// <summary>
/// Gets a value indicating whether the Bluetooth LE Helper is supported.
/// </summary>
public static bool IsBluetoothLESupported => (bool)(_isBluetoothLESupported ??
(_isBluetoothLESupported = ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 4)));
/// <summary>
/// Gets the list of available bluetooth devices
/// </summary>

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

@ -1,7 +1,7 @@
<Project Sdk="MSBuild.Sdk.Extras">
<PropertyGroup>
<TargetFramework>uap10.0.16299</TargetFramework>
<TargetFramework>uap10.0.17763</TargetFramework>
<Title>Windows Community Toolkit Devices</Title>
<Description>This library enables easier consumption of connectivity Devices/Peripherals and handle its connection to Windows devices. It contains BluetoothLE and Network connectivity helpers.</Description>
<PackageTags>UWP Toolkit Windows Devices Bluetooth BluetoothLE BLE Networking</PackageTags>

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

@ -1,7 +1,7 @@
<Project Sdk="MSBuild.Sdk.Extras">
<PropertyGroup>
<TargetFramework>uap10.0.16299</TargetFramework>
<TargetFramework>uap10.0.17763</TargetFramework>
<Title>Windows Community Toolkit Developer Tools</Title>
<Description>This library provides XAML user controls and services to help developers build their app. It is part of the Windows Community Toolkit.

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

@ -42,7 +42,7 @@
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
<AppContainerApplication>true</AppContainerApplication>
<ApplicationType>Windows Store</ApplicationType>
<WindowsTargetPlatformVersion>10.0.17763.0</WindowsTargetPlatformVersion>
<WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
<WindowsTargetPlatformMinVersion>10.0.17134.0</WindowsTargetPlatformMinVersion>
<ApplicationTypeRevision>10.0</ApplicationTypeRevision>
<ProjectName>Microsoft.Toolkit.Uwp.Input.GazeInteraction</ProjectName>

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

@ -1,24 +0,0 @@
<Project Sdk="MSBuild.Sdk.Extras">
<PropertyGroup>
<TargetFramework>uap10.0</TargetFramework>
<IncludeBuildOutput>false</IncludeBuildOutput>
<Title>Windows Community Toolkit Notifications for JavaScript</Title>
<Description>
This project is used for packaging the WinMD to work for WinJS projects.
Generate tile, toast, and badge notifications for Windows 10 via code, with the help of IntelliSense, instead of directly using XML.
Supports adaptive tiles and adaptive/interactive toasts for Windows 10. It is part of the Windows Community Toolkit.
Supports C# and C++ UWP project types (see Microsoft.Toolkit.Uwp.Notifications).
Also works with C# portable class libraries and non-UWP C# projects like server projects.
</Description>
<PackageTags>notifications win10 windows-10 tile tiles toast toasts badge xml uwp javascript</PackageTags>
<ExtrasImplicitPlatformPackageIsPrivate>true</ExtrasImplicitPlatformPackageIsPrivate>
</PropertyGroup>
<ItemGroup>
<None Include="Microsoft.Toolkit.Uwp.Notifications.JavaScript.targets" PackagePath="build\Windows" Pack="true" />
<None Include="..\Microsoft.Toolkit.Uwp.Notifications\bin\$(Configuration)\native\*.*" PackagePath="lib\Windows" Pack="true" />
</ItemGroup>
</Project>

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

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Reference Include="Microsoft.Toolkit.Uwp.Notifications.Windows">
<HintPath>$(MSBuildThisFileDirectory)..\..\lib\Windows\Microsoft.Toolkit.Uwp.Notifications.winmd</HintPath>
<IsWinMDFile>true</IsWinMDFile>
</Reference>
</ItemGroup>
</Project>

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

@ -7,7 +7,7 @@
<Description>
Generate tile, toast, and badge notifications for Windows 10 via code, with the help of IntelliSense.
Adds Support for adaptive tiles and adaptive/interactive toasts for Windows 10. It is part of the Windows Community Toolkit.
Supports C# and C++ UWP project types (see Microsoft.Toolkit.Uwp.Notifications.JavaScript for the JS version).
Supports C# and C++ UWP project types.
Also works with C# portable class libraries and non-UWP C# projects like server projects.
This project contains outputs for netstandard1.4, uap10.0 and native for WinRT.
</Description>
@ -21,7 +21,7 @@
<When Condition="'$(TargetFramework)'=='net461' or '$(TargetFramework)'=='netcoreapp3.1'">
<ItemGroup>
<!--Reference Windows SDK NuGet of correct target platform version-->
<PackageReference Include="Microsoft.Windows.SDK.Contracts" Version="10.0.17763.1000" />
<PackageReference Include="Microsoft.Windows.SDK.Contracts" Version="10.0.19041.1" />
</ItemGroup>
<PropertyGroup>
<!--Define the WINDOWS_UWP conditional symbol, since the Windows.Data.Xml and the Windows.UI.Notification namespaces are available-->
@ -59,7 +59,7 @@
<NugetTargetMoniker Condition="'$(DesignTimeBuild)' == 'true'">native</NugetTargetMoniker>
<NugetTargetMoniker Condition="'$(DesignTimeBuild)' != 'true'">UAP,Version=v10.0</NugetTargetMoniker>
<PackageTargetFallback>uap10.0</PackageTargetFallback>
<TargetPlatformVersion Condition="'$(TargetPlatformVersion)' == '' ">10.0.18362.0</TargetPlatformVersion>
<TargetPlatformVersion Condition="'$(TargetPlatformVersion)' == '' ">10.0.19041.0</TargetPlatformVersion>
<TargetPlatformMinVersion Condition="'$(TargetPlatformMinVersion)' == '' ">10.0.10240.0</TargetPlatformMinVersion>
<DefineConstants Condition="'$(DisableImplicitFrameworkDefines)' != 'true'">$(DefineConstants);NETFX_CORE;WINDOWS_UWP;WINRT</DefineConstants>
<CopyLocalLockFileAssemblies Condition="'$(CopyLocalLockFileAssemblies)' == ''">false</CopyLocalLockFileAssemblies>

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

@ -6,22 +6,16 @@ Any code for generating notifications should be written in the Microsoft.Toolkit
If there's UWP-specific code, use the appropriate `#ifdef`, `WINDOWS_UWP` or `WINRT`.
## What are all the projects for?
There's two notification projects...
- Microsoft.Toolkit.Uwp.Notifications
- Microsoft.Toolkit.Uwp.Notifications.JavaScript
All the code is contained on the Microsoft.Toolkit.Uwp.Notifications project.
The first project is where all the code is contained.
The JavaScript project is just for packaging the `WinMD` to work for WinJS projects.
It outputs `netstandard1.4`, `uap10.0`, `native` for WinRT, and netcoreapp for .Net Core projects. The UWP library is only for C#, while the WinRT library is a Windows Runtime Component for C++.
The first project contains outputs for `netstandard1.4`, `uap10.0` and a `native` for WinRT. The UWP library is only for C#, while the WinRT library is a Windows Runtime Component for JavaScript and C++.
| C# | JavaScript/C++ |
| C# | C++ |
| ---------------- | ------------------- |
| NET Standard 1.4 | UWP WinRT Component |
| UWP C# DLL | |
| .Net Core DLL | |

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

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
</startup>
</configuration>

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

@ -1,65 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{292D34E8-0F01-4FA8-951D-8232F75A88D5}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>DifferencesGen</RootNamespace>
<AssemblyName>DifferencesGen</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\DifferencesGen.xml</DocumentationFile>
<NoWarn>1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Web.Extensions" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json">
<Version>10.0.3</Version>
</PackageReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="Pack">
<!-- No-op to avoid build error when packing solution from commandline -->
</Target>
</Project>

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

@ -1,256 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text;
namespace DifferencesGen
{
public class Program
{
private static HashSet<string> enumTypes = new HashSet<string>();
private static HashSet<string> typeEvents = new HashSet<string>();
public static void Main(string[] args)
{
string min = null;
string max = null;
foreach (var arg in args)
{
if (arg.StartsWith("/min:"))
{
min = arg.Replace("/min:", string.Empty);
}
else if (arg.StartsWith("/max:"))
{
max = arg.Replace("/max:", string.Empty);
}
}
Version.TryParse(min, out Version minVersion);
Version.TryParse(max, out Version maxVersion);
if (minVersion == null || maxVersion == null)
{
Console.WriteLine("The differences generator needs to be run as follows:");
Console.WriteLine("DifferencesGen /min:4.0.0.0 /max:5.0.0.0");
return;
}
string folderPath = @"C:\Program Files (x86)\Windows Kits\10\References";
string universalApiFile = "Windows.Foundation.UniversalApiContract.winmd";
string universalApiDifferencesCompressedFile = "Differences-{0}.gz";
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += (sender, eventArgs) => Assembly.ReflectionOnlyLoad(eventArgs.Name);
WindowsRuntimeMetadata.ReflectionOnlyNamespaceResolve += (sender, eventArgs) =>
{
string path =
WindowsRuntimeMetadata.ResolveNamespace(eventArgs.NamespaceName, Enumerable.Empty<string>())
.FirstOrDefault();
if (path == null)
{
return;
}
eventArgs.ResolvedAssemblies.Add(Assembly.ReflectionOnlyLoadFrom(path));
};
DirectoryInfo directoryInfo = new DirectoryInfo(folderPath);
FileInfo[] files = directoryInfo.GetFiles(universalApiFile, SearchOption.AllDirectories);
List<Tuple<Version, Assembly>> assemblyList = new List<Tuple<Version, Assembly>>();
if (files.Length > 0)
{
foreach (var file in files)
{
var assembly = Assembly.ReflectionOnlyLoadFrom(file.FullName);
var nameParts = assembly.FullName.Split(new string[] { ", " }, StringSplitOptions.RemoveEmptyEntries);
var versionParts = nameParts[1].Split(new char[] { '=' }, StringSplitOptions.RemoveEmptyEntries);
var version = Version.Parse(versionParts[1]);
if (version >= minVersion && version <= maxVersion)
{
assemblyList.Add(new Tuple<Version, Assembly>(version, assembly));
}
}
}
if (assemblyList.Count >= 2)
{
var orderedList = assemblyList.OrderBy(t => t.Item1).ToList();
for (int i = 1; i < orderedList.Count; i++)
{
var previousVersionAssembly = orderedList[i - 1].Item2;
var newerVersionAssembly = orderedList[i].Item2;
var version = orderedList[i].Item1;
var previousVersionTypes = ProcessAssembly(previousVersionAssembly);
var newerVersionTypes = ProcessAssembly(newerVersionAssembly);
var addedTypes = new Dictionary<string, List<string>>();
foreach (var type in newerVersionTypes)
{
if (!previousVersionTypes.ContainsKey(type.Key))
{
addedTypes.Add(type.Key, null);
if (enumTypes.Contains(type.Key))
{
System.Diagnostics.Debug.WriteLine($"New enum {type.Key}");
}
continue;
}
HashSet<string> previousVersionTypeMembers = new HashSet<string>(previousVersionTypes[type.Key]);
HashSet<string> newerVersionTypeMembers = new HashSet<string>(type.Value);
newerVersionTypeMembers.ExceptWith(previousVersionTypeMembers);
if (newerVersionTypeMembers.Count == 0)
{
continue;
}
if (enumTypes.Contains(type.Key))
{
System.Diagnostics.Debug.WriteLine($"Enum {type.Key} has new members: {string.Join(",", newerVersionTypeMembers)}");
}
foreach (var member in newerVersionTypeMembers)
{
if (typeEvents.Contains($"{type.Key}-{member}"))
{
System.Diagnostics.Debug.WriteLine($"Type {type.Key} has new event: {member}");
}
}
addedTypes.Add(type.Key, newerVersionTypeMembers.ToList());
}
StringBuilder stringBuilder = new StringBuilder();
using (var compressedFS = File.Create(Path.Combine(AssemblyDirectory, string.Format(universalApiDifferencesCompressedFile, version.ToString()))))
{
using (var compressionFS = new GZipStream(compressedFS, CompressionMode.Compress))
{
using (var writer = new StreamWriter(compressionFS))
{
foreach (var addedType in addedTypes)
{
stringBuilder.Clear();
stringBuilder.Append(addedType.Key);
if (addedType.Value != null && addedType.Value.Count > 0)
{
stringBuilder.Append(':');
stringBuilder.Append(string.Join(",", addedType.Value));
}
writer.WriteLine(stringBuilder.ToString());
}
}
}
}
stringBuilder.Length = 0;
}
}
}
public static string AssemblyDirectory
{
get
{
string codeBase = Assembly.GetExecutingAssembly().CodeBase;
UriBuilder uri = new UriBuilder(codeBase);
string path = Uri.UnescapeDataString(uri.Path);
return Path.GetDirectoryName(path);
}
}
private static Dictionary<string, List<string>> ProcessAssembly(Assembly assembly)
{
var types = new Dictionary<string, List<string>>();
foreach (var exportedType in assembly.ExportedTypes)
{
var members = new List<string>();
if (exportedType.IsEnum)
{
if (!enumTypes.Contains(exportedType.FullName))
{
enumTypes.Add(exportedType.FullName);
}
foreach (var member in exportedType.GetFields())
{
if (member.Name.Equals("value__"))
{
continue;
}
members.Add(member.Name);
}
}
else
{
foreach (var methodInfo in exportedType.GetMethods())
{
if (!methodInfo.IsPublic)
{
continue;
}
if (methodInfo.Name.StartsWith("get_") ||
methodInfo.Name.StartsWith("set_") ||
methodInfo.Name.StartsWith("put_") ||
methodInfo.Name.StartsWith("add_") ||
methodInfo.Name.StartsWith("remove_"))
{
continue;
}
members.Add($"{methodInfo.Name}#{methodInfo.GetParameters().Length}");
}
foreach (var propertyInfo in exportedType.GetProperties())
{
members.Add(propertyInfo.Name);
}
foreach (var eventInfo in exportedType.GetEvents())
{
typeEvents.Add($"{exportedType.FullName}-{eventInfo.Name}");
members.Add(eventInfo.Name);
}
}
types.Add(exportedType.FullName, members);
}
return types;
}
}
}

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

@ -1,357 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using Microsoft.CodeAnalysis;
namespace Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer
{
/// <summary>
/// This class offers loads platform differences for use by Code Analyzer and Code Fixer.
/// </summary>
public static class Analyzer
{
internal enum TypePresenceIndicator
{
New,
Changes,
NotFound,
}
private static Dictionary<string, Dictionary<string, List<NewMember>>> _differencesDictionary = null;
/// <summary>
/// Embedded differences between API contract version 4 and 5.
/// </summary>
public const string N1DifferencesRes = "Differences-5.0.0.0.gz";
/// <summary>
/// Embedded differences between API contract version 5 and 6.
/// </summary>
public const string N0DifferencesRes = "Differences-6.0.0.0.gz";
/// <summary>
/// Earliest supported SDK version.
/// </summary>
public const string N2SDKVersion = "15063";
/// <summary>
/// Intermediate SDK version.
/// </summary>
public const string N1SDKVersion = "16299";
/// <summary>
/// Latest SDK version.
/// </summary>
public const string N0SDKVersion = "17134";
/// <summary>
/// Platform related diagnostic descriptor
/// </summary>
public static readonly DiagnosticDescriptor PlatformRule = new DiagnosticDescriptor("UWP001", "Platform-specific", "Platform-specific code detected. Consider using ApiInformation.IsTypePresent to guard against failure", "Safety", DiagnosticSeverity.Warning, true);
/// <summary>
/// Version related diagnostic descriptor
/// </summary>
public static readonly DiagnosticDescriptor VersionRule = new DiagnosticDescriptor("UWP002", "Version-specific", "Version-specific code detected. Consider using ApiInformation.IsTypePresent / ApiInformation.IsMethodPresent / ApiInformation.IsPropertyPresent to guard against failure", "Safety", DiagnosticSeverity.Warning, true);
private static char[] typeMemberSeparator = { ':' };
private static char[] memberSeparator = { ',' };
static Analyzer()
{
_differencesDictionary = new Dictionary<string, Dictionary<string, List<NewMember>>>();
_differencesDictionary.Add(N0DifferencesRes, GetApiAdditions(N0DifferencesRes));
_differencesDictionary.Add(N1DifferencesRes, GetApiAdditions(N1DifferencesRes));
}
/// <summary>
/// Gets the API differences from specified resource.
/// </summary>
/// <param name="resourceName">name of embedded resource</param>
/// <returns>Dictionary with Fully qualified name of type as key and list of new members as value</returns>
public static Dictionary<string, List<NewMember>> GetUniversalApiAdditions(string resourceName)
{
return _differencesDictionary[resourceName];
}
private static Dictionary<string, List<NewMember>> GetApiAdditions(string resourceName)
{
Dictionary<string, List<NewMember>> apiAdditionsDictionary = new Dictionary<string, List<NewMember>>();
Assembly assembly = typeof(Analyzer).GetTypeInfo().Assembly;
var resource = assembly.GetManifestResourceStream("Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer." + resourceName);
if (resource == null)
{
System.Diagnostics.Debug.WriteLine($"Resource {resourceName} not found.");
return new Dictionary<string, List<NewMember>>();
}
System.Diagnostics.Debug.WriteLine($"Resource {resourceName} found.");
Dictionary<string, List<string>> differencesDictionary = new Dictionary<string, List<string>>();
using (GZipStream decompressionStream = new GZipStream(resource, CompressionMode.Decompress))
{
using (StreamReader reader = new StreamReader(decompressionStream))
{
while (!reader.EndOfStream)
{
var typeDetails = reader.ReadLine();
var typeMemberParts = typeDetails.Split(typeMemberSeparator, StringSplitOptions.RemoveEmptyEntries);
if (typeMemberParts.Length == 1)
{
differencesDictionary.Add(typeMemberParts[0], null);
continue;
}
var membersAddedToType = typeMemberParts[1].Split(memberSeparator, StringSplitOptions.RemoveEmptyEntries);
differencesDictionary.Add(typeMemberParts[0], new List<string>(membersAddedToType));
}
}
}
if (differencesDictionary == null)
{
return apiAdditionsDictionary;
}
foreach (var kvp in differencesDictionary)
{
var list = new List<NewMember>();
if (kvp.Value != null)
{
list.AddRange(kvp.Value.Select(v => new NewMember(v)));
}
apiAdditionsDictionary.Add(kvp.Key, list);
}
return apiAdditionsDictionary;
}
/// <summary>
/// This function tells which version/platform the symbol is from.
/// </summary>
/// <param name="symbol">represents a compiler <see cref="ISymbol"/></param>
/// <returns>instance of <see cref="Platform"/></returns>
public static Platform GetPlatformForSymbol(ISymbol symbol)
{
if (symbol == null)
{
return new Platform(PlatformKind.Unchecked);
}
if (symbol.ContainingNamespace != null && symbol.ContainingNamespace.ToDisplayString().StartsWith("Windows."))
{
var assembly = symbol.ContainingAssembly.Name;
var version = symbol.ContainingAssembly.Identity.Version.Major;
// Any call to ApiInformation.* is allowed without warning
if (symbol.ContainingType?.Name == "ApiInformation")
{
return new Platform(PlatformKind.Uwp, Analyzer.N2SDKVersion);
}
// Don't want to give warning when analyzing code in an PCL project.
// In those two targets, every Windows type is found in Windows.winmd, so that's how we'll suppress it:
if (assembly == "Windows")
{
return new Platform(PlatformKind.Unchecked);
}
// Some WinRT types like Windows.UI.Color get projected to come from .NET assemblies, always present:
if (assembly.StartsWith("System.Runtime."))
{
return new Platform(PlatformKind.Uwp, Analyzer.N2SDKVersion);
}
// Some things are emphatically part of UWP.10240
if (assembly == "Windows.Foundation.FoundationContract" || (assembly == "Windows.Foundation.UniversalApiContract" && version == 1))
{
return new Platform(PlatformKind.Uwp, Analyzer.N2SDKVersion);
}
if (assembly == "Windows.Foundation.UniversalApiContract")
{
var isType = symbol.Kind == SymbolKind.NamedType;
var typeName = isType ? symbol.ToDisplayString() : symbol.ContainingType.ToDisplayString();
TypePresenceIndicator presentInN0ApiDiff = CheckCollectionForType(Analyzer.GetUniversalApiAdditions(Analyzer.N0DifferencesRes), typeName, symbol);
if (presentInN0ApiDiff == TypePresenceIndicator.New)
{
// the entire type was found in Target Version
return new Platform(PlatformKind.Uwp, Analyzer.N0SDKVersion);
}
else if (presentInN0ApiDiff == TypePresenceIndicator.Changes)
{
// the entire type was found in Target Version with matching parameter lengths
return new Platform(PlatformKind.Uwp, Analyzer.N0SDKVersion, true);
}
else
{
TypePresenceIndicator presentInN1ApiDiff = CheckCollectionForType(Analyzer.GetUniversalApiAdditions(Analyzer.N1DifferencesRes), typeName, symbol);
if (presentInN1ApiDiff == TypePresenceIndicator.New)
{
// the entire type was found in Target Version
return new Platform(PlatformKind.Uwp, Analyzer.N1SDKVersion);
}
else if (presentInN1ApiDiff == TypePresenceIndicator.Changes)
{
// the entire type was found in Target Version with matching parameter lengths
return new Platform(PlatformKind.Uwp, Analyzer.N1SDKVersion, true);
}
else
{
// the type was in Min version
return new Platform(PlatformKind.Uwp, Analyzer.N2SDKVersion);
}
}
}
// All other Windows.* types come from platform-specific extensions
return new Platform(PlatformKind.ExtensionSDK);
}
else
{
return new Platform(PlatformKind.Unchecked);
}
}
/// <summary>
/// returns instance of <see cref="HowToGuard"/> for <see cref="ISymbol"/>
/// </summary>
/// <param name="target">instance of <see cref="ISymbol"/></param>
/// <returns>instance of <see cref="HowToGuard"/></returns>
public static HowToGuard GetGuardForSymbol(ISymbol target)
{
var plat = Analyzer.GetPlatformForSymbol(target);
switch (plat.Kind)
{
case PlatformKind.ExtensionSDK:
return new HowToGuard()
{
TypeToCheck = target.Kind == SymbolKind.NamedType ? target.ToDisplayString() : target.ContainingType.ToDisplayString(),
KindOfCheck = "IsTypePresent"
};
case PlatformKind.Uwp:
if (target.Kind == SymbolKind.NamedType)
{
return new HowToGuard()
{
TypeToCheck = target.ToDisplayString(),
KindOfCheck = "IsTypePresent"
};
}
else
{
var g = new HowToGuard
{
TypeToCheck = target.ContainingType.ToDisplayString()
};
var d0 = Analyzer.GetUniversalApiAdditions(Analyzer.N0DifferencesRes);
var d1 = Analyzer.GetUniversalApiAdditions(Analyzer.N1DifferencesRes);
if (!d0.TryGetValue(g.TypeToCheck, out List<NewMember> newMembers))
{
d1.TryGetValue(g.TypeToCheck, out newMembers);
}
if (newMembers == null)
{
throw new InvalidOperationException("oops! expected this UWP version API to be in the dictionary of new things");
}
g.MemberToCheck = target.Name;
if (target.Kind == SymbolKind.Field)
{
// the only fields in WinRT are enum fields
g.KindOfCheck = "IsEnumNamedValuePresent";
}
else if (target.Kind == SymbolKind.Event)
{
g.KindOfCheck = "IsEventPresent";
}
else if (target.Kind == SymbolKind.Property)
{
// TODO: if SDK starts introducing additional accessors on properties, we'll have to change this
g.KindOfCheck = "IsPropertyPresent";
}
else if (target.Kind == SymbolKind.Method)
{
g.KindOfCheck = "IsMethodPresent";
if (target.Kind == SymbolKind.Method && plat.ByParameterCount)
{
g.ParameterCountToCheck = (target as IMethodSymbol).Parameters.Length;
}
}
return g;
}
default:
throw new InvalidOperationException("oops! don't know why I was asked to check something that's fine");
}
}
private static TypePresenceIndicator CheckCollectionForType(Dictionary<string, List<NewMember>> collection, string typeName, ISymbol symbol)
{
if (!collection.TryGetValue(typeName, out var newMembers))
{
return TypePresenceIndicator.NotFound;
}
if (newMembers == null || newMembers.Count == 0)
{
return TypePresenceIndicator.New;
}
if (symbol.Kind == SymbolKind.NamedType)
{
return TypePresenceIndicator.NotFound;
}
var memberName = symbol.Name;
foreach (var newMember in newMembers)
{
if (memberName == newMember.Name && !newMember.ParameterCount.HasValue)
{
return TypePresenceIndicator.New;
}
// this member was new in collection
if (symbol.Kind != SymbolKind.Method)
{
// TODO: Continue For... Warning!!! not translated
}
if (memberName == newMember.Name && ((IMethodSymbol)symbol).Parameters.Length == newMember.ParameterCount)
{
return TypePresenceIndicator.Changes;
}
}
// this member existed in a different collection
return TypePresenceIndicator.NotFound;
}
}
}

Двоичный файл не отображается.

Двоичный файл не отображается.

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

@ -1,32 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer
{
/// <summary>
/// The struct provides guard related info.
/// </summary>
public struct HowToGuard
{
/// <summary>
/// Type being checked
/// </summary>
public string TypeToCheck;
/// <summary>
/// Member being checked
/// </summary>
public string MemberToCheck;
/// <summary>
/// Whether parameter count will be used for the check
/// </summary>
public int? ParameterCountToCheck;
/// <summary>
/// Type of check
/// </summary>
public string KindOfCheck;
}
}

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

@ -1,36 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Title>Windows Community Toolkit UWP Platform Specific Analyzer</Title>
<Description>This standard library provides code analysis and code fixers (on CS and VB) to ensure that version / platform specific code is well guarded.</Description>
<PackageTags>UWP Toolkit Windows Platform Specific Analyzer</PackageTags>
<IncludeBuildOutput>false</IncludeBuildOutput>
</PropertyGroup>
<ItemGroup>
<None Remove="Differences-6.0.0.0.gz" />
<None Remove="Differences-7.0.0.0.gz" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Differences-6.0.0.0.gz" />
<EmbeddedResource Include="Differences-7.0.0.0.gz" />
</ItemGroup>
<ItemGroup>
<PackageReference Update="NETStandard.Library" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<None Update="tools\*.ps1" CopyToOutputDirectory="Always" Pack="true" PackagePath="" />
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/vb" Visible="false" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis" Version="2.8.2" PrivateAssets="All" />
</ItemGroup>
</Project>

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

@ -1,46 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer
{
/// <summary>
/// This class wraps a new data members
/// </summary>
public struct NewMember
{
private static char[] methodCountSeparator = { '#' };
/// <summary>
/// Member name
/// </summary>
public string Name;
/// <summary>
/// Parameter count (if its a method)
/// </summary>
public int? ParameterCount;
/// <summary>
/// Initializes a new instance of the <see cref="NewMember"/> struct.
/// </summary>
/// <param name="s">data containing name and optionally parameter count</param>
public NewMember(string s)
{
string[] parts = s.Split(methodCountSeparator, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 2)
{
Name = parts[0];
ParameterCount = int.Parse(parts[1]);
}
else
{
Name = s;
ParameterCount = null;
}
}
}
}

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

@ -1,69 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer
{
/// <summary>
/// Simple struct to hold platform / version / param count info for given symbol
/// </summary>
public struct Platform
{
/// <summary>
/// Platform Kind
/// </summary>
public PlatformKind Kind;
/// <summary>
/// For UWP, this is version 15063 or 16299etc. For User, the fully qualified name of the attribute in use
/// </summary>
public string Version;
/// <summary>
/// For UWP only
/// </summary>
public bool ByParameterCount;
/// <summary>
/// Initializes a new instance of the <see cref="Platform"/> struct.
/// </summary>
/// <param name="kind"><see cref="PlatformKind"/></param>
/// <param name="version">version</param>
/// <param name="byParameterCount">boolean</param>
public Platform(PlatformKind kind, string version = null, bool byParameterCount = false)
{
Kind = kind;
Version = version;
ByParameterCount = byParameterCount;
switch (kind)
{
case PlatformKind.Unchecked:
if (version != null)
{
throw new ArgumentException("No version expected");
}
break;
case PlatformKind.Uwp:
break;
case PlatformKind.ExtensionSDK:
if (version != null)
{
throw new ArgumentException("Don't specify versions for extension SDKs");
}
break;
}
if (byParameterCount && kind != PlatformKind.Uwp)
{
throw new ArgumentException("Only UWP can be distinguished by parameter count");
}
}
}
}

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

@ -1,27 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer
{
/// <summary>
/// Platform kind enum
/// </summary>
public enum PlatformKind
{
/// <summary>
/// .NET and Pre-UWP WinRT
/// </summary>
Unchecked,
/// <summary>
/// Core UWP platform
/// </summary>
Uwp,
/// <summary>
/// Desktop, Mobile, IOT, Xbox extension SDK
/// </summary>
ExtensionSDK
}
}

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

@ -1,230 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer
{
/// <summary>
/// This class is a Roslyn code analyzer that checks for types / members that should be guarded against.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class PlatformSpecificAnalyzerCS : DiagnosticAnalyzer
{
/// <summary>
/// Gets supported diagnostics
/// </summary>
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
{
get { return ImmutableArray.Create(Analyzer.PlatformRule, Analyzer.VersionRule); }
}
/// <summary>
/// Gets instance of symbol from syntax node
/// </summary>
/// <param name="node">instance of <see cref="SyntaxNode"/></param>
/// <param name="semanticModel"><see cref="SemanticModel"/></param>
/// <returns><see cref="ISymbol"/></returns>
public static ISymbol GetTargetOfNode(SyntaxNode node, SemanticModel semanticModel)
{
var parentKind = node.Parent.Kind();
if (parentKind == SyntaxKind.InvocationExpression && node == ((InvocationExpressionSyntax)node.Parent).Expression)
{
// <target>(...)
// points to the method after overload resolution
return semanticModel.GetSymbolInfo((InvocationExpressionSyntax)node.Parent).Symbol;
}
else if (parentKind == SyntaxKind.ObjectCreationExpression && node == ((ObjectCreationExpressionSyntax)node.Parent).Type)
{
// New <target>
var objectCreationExpression = (ObjectCreationExpressionSyntax)node.Parent;
var target = semanticModel.GetSymbolInfo(objectCreationExpression).Symbol;
// points to the constructor after overload resolution
return target;
}
else
{
// f<target>(...)
// <target> x = ...
// Action x = <target> -- note that following code does pick the right overload
// <target> += delegate -- the following code does recognize events
// nameof(<target>) -- I think it's nicer to report on this, even if not technically needed
// Field access? I'll disallow it for enum values, and allow it for everything else
var target = semanticModel.GetSymbolInfo(node).Symbol;
if (target == null)
{
return null;
}
var targetKind = target.Kind;
if (targetKind == SymbolKind.Method || targetKind == SymbolKind.Event || targetKind == SymbolKind.Property || targetKind == SymbolKind.NamedType)
{
return target;
}
if (targetKind == SymbolKind.Field && target.ContainingType.TypeKind == TypeKind.Enum)
{
return target;
}
return null;
}
}
/// <summary>
/// Initializes the analyzer, registering for code analysis.
/// </summary>
/// <param name="context"><see cref="AnalysisContext"/></param>
public override void Initialize(AnalysisContext context)
{
ConcurrentDictionary<int, Diagnostic> reportsDictionary = new ConcurrentDictionary<int, Diagnostic>();
context.RegisterSyntaxNodeAction((c) => AnalyzeExpression(c, reportsDictionary), SyntaxKind.VariableDeclaration, SyntaxKind.FieldDeclaration, SyntaxKind.IdentifierName, SyntaxKind.SimpleMemberAccessExpression, SyntaxKind.QualifiedName);
}
private static IEnumerable<ISymbol> GetGuards(SyntaxNode node, SemanticModel semanticModel)
{
foreach (var condition in GetConditions(node))
{
// First check for invocations of ApiInformation.IsTypePresent
foreach (var invocation in condition.DescendantNodesAndSelf(i => i is InvocationExpressionSyntax))
{
var targetMethod = semanticModel.GetSymbolInfo(invocation).Symbol;
if (targetMethod?.ContainingType?.Name == "ApiInformation")
{
yield return targetMethod;
}
}
// Next check for any property/field access
var accesses1 = condition.DescendantNodesAndSelf(d => d is MemberAccessExpressionSyntax).Select(n => semanticModel.GetSymbolInfo(n).Symbol);
var accesses2 = condition.DescendantNodesAndSelf(d => d is IdentifierNameSyntax).Select(n => semanticModel.GetSymbolInfo(n).Symbol);
foreach (var symbol in accesses1.Concat(accesses2))
{
var symbolKind = symbol.Kind;
if (symbolKind == SymbolKind.Field || symbolKind == SymbolKind.Property)
{
yield return symbol;
}
}
}
}
private static IEnumerable<ExpressionSyntax> GetConditions(SyntaxNode node)
{
var check = node.FirstAncestorOrSelf<IfStatementSyntax>();
while (check != null)
{
yield return check.Condition;
check = check.Parent.FirstAncestorOrSelf<IfStatementSyntax>();
}
}
private void AnalyzeExpression(SyntaxNodeAnalysisContext context, ConcurrentDictionary<int, Diagnostic> reports)
{
var parentKind = context.Node.Parent.Kind();
// will be handled at higher level
if (parentKind == SyntaxKind.SimpleMemberAccessExpression || parentKind == SyntaxKind.QualifiedName)
{
return;
}
var target = GetTargetOfNode(context.Node, context.SemanticModel);
if (target == null)
{
return;
}
var platform = Analyzer.GetPlatformForSymbol(target);
// Some quick escapes
if (platform.Kind == PlatformKind.Unchecked)
{
return;
}
if (platform.Kind == PlatformKind.Uwp && platform.Version == Analyzer.N2SDKVersion)
{
return;
}
// Is this expression inside a method/constructor/property that claims to be specific?
var containingBlock = context.Node.FirstAncestorOrSelf<BlockSyntax>();
// for constructors and methods
MemberDeclarationSyntax containingMember = containingBlock?.FirstAncestorOrSelf<BaseMethodDeclarationSyntax>();
if (containingBlock == null || containingBlock?.Parent is AccessorDeclarationSyntax)
{
containingMember = context.Node.FirstAncestorOrSelf<PropertyDeclarationSyntax>();
}
// Is this invocation properly guarded? See readme.md for explanations.
if (IsProperlyGuarded(context.Node, context.SemanticModel))
{
return;
}
if (containingBlock != null)
{
foreach (var ret in containingBlock.DescendantNodes().OfType<ReturnStatementSyntax>())
{
if (IsProperlyGuarded(ret, context.SemanticModel))
{
return;
}
}
}
// We'll report only a single diagnostic per line, the first.
var loc = context.Node.GetLocation();
if (!loc.IsInSource)
{
return;
}
var line = loc.GetLineSpan().StartLinePosition.Line;
if (reports.TryGetValue(line, out var diagnostic) && diagnostic.Location.SourceSpan.Start <= loc.SourceSpan.Start)
{
return;
}
diagnostic = Diagnostic.Create(platform.Kind == PlatformKind.Uwp ? Analyzer.VersionRule : Analyzer.PlatformRule, loc);
reports[line] = diagnostic;
context.ReportDiagnostic(diagnostic);
}
private bool IsProperlyGuarded(SyntaxNode node, SemanticModel semanticModel)
{
foreach (var symbol in GetGuards(node, semanticModel))
{
if (symbol.ContainingType?.Name == "ApiInformation")
{
return true;
}
}
return false;
}
}
}

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

@ -1,254 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.VisualBasic;
using Microsoft.CodeAnalysis.VisualBasic.Syntax;
namespace Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer
{
/// <summary>
/// This class is a Roslyn code analyzer that checks for types / members that should be guarded against.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.VisualBasic)]
public class PlatformSpecificAnalyzerVB : DiagnosticAnalyzer
{
/// <summary>
/// Gets supported diagnostics
/// </summary>
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
{
get { return ImmutableArray.Create(Analyzer.PlatformRule, Analyzer.VersionRule); }
}
/// <summary>
/// Gets instance of symbol from syntax node
/// </summary>
/// <param name="node">instance of <see cref="SyntaxNode"/></param>
/// <param name="semanticModel"><see cref="SemanticModel"/></param>
/// <returns><see cref="ISymbol"/></returns>
public static ISymbol GetTargetOfNode(SyntaxNode node, SemanticModel semanticModel)
{
var parentKind = node.Parent.Kind();
if (parentKind == SyntaxKind.InvocationExpression && node == ((InvocationExpressionSyntax)node.Parent).Expression)
{
// <target>(...)
// points to the method after overload resolution
return semanticModel.GetSymbolInfo((InvocationExpressionSyntax)node.Parent).Symbol;
}
else if (parentKind == SyntaxKind.AddressOfExpression)
{
// AddressOf <target>
return semanticModel.GetSymbolInfo(node).Symbol; // points to the method after overload resolution
}
else if (parentKind == SyntaxKind.ObjectCreationExpression && node == ((ObjectCreationExpressionSyntax)node.Parent).Type)
{
// New <target>
var objectCreationExpression = (ObjectCreationExpressionSyntax)node.Parent;
var target = semanticModel.GetSymbolInfo(objectCreationExpression).Symbol;
// points to the constructor after overload resolution
return target;
}
else if (parentKind == SyntaxKind.AddHandlerStatement && node == ((AddRemoveHandlerStatementSyntax)node.Parent).EventExpression)
{
// AddHandler <target>, delegate
return semanticModel.GetSymbolInfo(node).Symbol; // points to the event
}
else if (parentKind == SyntaxKind.NameOfExpression)
{
// NameOf(<target>)
return null;
}
else
{
// f(Of <target>)(...) -- no warning
// Dim x As <target> = ... -- no warning
// property access -- warning
// field access -- only warning on enum fields
// method access without arguments -- warning
var target = semanticModel.GetSymbolInfo(node).Symbol;
if (target == null)
{
return null;
}
var targetKind = target.Kind;
if (targetKind == SymbolKind.Method || targetKind == SymbolKind.Property || targetKind == SymbolKind.NamedType)
{
return target;
}
if (targetKind == SymbolKind.Field && target.ContainingType.TypeKind == TypeKind.Enum)
{
return target;
}
return null;
}
}
/// <summary>
/// Initializes the analyzer, registering for code analysis.
/// </summary>
/// <param name="context"><see cref="AnalysisContext"/></param>
public override void Initialize(AnalysisContext context)
{
ConcurrentDictionary<int, Diagnostic> reportsDictionary = new ConcurrentDictionary<int, Diagnostic>();
context.RegisterSyntaxNodeAction((c) => AnalyzeExpression(c, reportsDictionary), SyntaxKind.LocalDeclarationStatement, SyntaxKind.IdentifierName, SyntaxKind.SimpleMemberAccessExpression, SyntaxKind.QualifiedName);
}
private static IEnumerable<ISymbol> GetGuards(SyntaxNode node, SemanticModel semanticModel)
{
foreach (var condition in GetConditions(node))
{
// First check for invocations of ApiInformation.IsTypePresent
foreach (var invocation in condition.DescendantNodesAndSelf(i => i is InvocationExpressionSyntax))
{
var targetMethod = semanticModel.GetSymbolInfo(invocation).Symbol;
if (targetMethod?.ContainingType?.Name == "ApiInformation")
{
yield return targetMethod;
}
}
// Next check for any property/field access
var accesses1 = condition.DescendantNodesAndSelf(d => d is MemberAccessExpressionSyntax).Select(n => semanticModel.GetSymbolInfo(n).Symbol);
var accesses2 = condition.DescendantNodesAndSelf(d => d is IdentifierNameSyntax).Select(n => semanticModel.GetSymbolInfo(n).Symbol);
foreach (var symbol in accesses1.Concat(accesses2))
{
if (symbol == null)
{
continue;
}
var symbolKind = symbol.Kind;
if (symbolKind == SymbolKind.Field || symbolKind == SymbolKind.Property)
{
yield return symbol;
}
}
}
}
private static IEnumerable<ExpressionSyntax> GetConditions(SyntaxNode node)
{
var check1 = node.FirstAncestorOrSelf<MultiLineIfBlockSyntax>();
while (check1 != null)
{
yield return check1.IfStatement.Condition;
check1 = check1.Parent.FirstAncestorOrSelf<MultiLineIfBlockSyntax>();
}
var check2 = node.FirstAncestorOrSelf<SingleLineIfStatementSyntax>();
while (check2 != null)
{
yield return check2.Condition;
check2 = check2.Parent.FirstAncestorOrSelf<SingleLineIfStatementSyntax>();
}
}
private void AnalyzeExpression(SyntaxNodeAnalysisContext context, ConcurrentDictionary<int, Diagnostic> reports)
{
var parentKind = context.Node.Parent.Kind();
// will be handled at higher level
if (parentKind == SyntaxKind.SimpleMemberAccessExpression || parentKind == SyntaxKind.QualifiedName)
{
return;
}
var target = GetTargetOfNode(context.Node, context.SemanticModel);
if (target == null)
{
return;
}
var platform = Analyzer.GetPlatformForSymbol(target);
// Some quick escapes
if (platform.Kind == PlatformKind.Unchecked)
{
return;
}
if (platform.Kind == PlatformKind.Uwp && platform.Version == Analyzer.N2SDKVersion)
{
return;
}
// Is this expression inside a method/constructor/property that claims to be specific?
DeclarationStatementSyntax containingMember = context.Node.FirstAncestorOrSelf<MethodBlockBaseSyntax>();
if (containingMember is AccessorBlockSyntax)
{
containingMember = containingMember.FirstAncestorOrSelf<PropertyBlockSyntax>();
}
// Is this invocation properly guarded? See readme.md for explanations.
if (IsProperlyGuarded(context.Node, context.SemanticModel))
{
return;
}
if (containingMember != null)
{
foreach (var ret in containingMember.DescendantNodes().OfType<ReturnStatementSyntax>())
{
if (IsProperlyGuarded(ret, context.SemanticModel))
{
return;
}
}
}
// We'll report only a single diagnostic per line, the first.
var loc = context.Node.GetLocation();
if (!loc.IsInSource)
{
return;
}
var line = loc.GetLineSpan().StartLinePosition.Line;
if (reports.TryGetValue(line, out var diagnostic) && diagnostic.Location.SourceSpan.Start <= loc.SourceSpan.Start)
{
return;
}
diagnostic = Diagnostic.Create(platform.Kind == PlatformKind.Uwp ? Analyzer.VersionRule : Analyzer.PlatformRule, loc);
reports[line] = diagnostic;
context.ReportDiagnostic(diagnostic);
}
private bool IsProperlyGuarded(SyntaxNode node, SemanticModel semanticModel)
{
foreach (var symbol in GetGuards(node, semanticModel))
{
if (symbol.ContainingType?.Name == "ApiInformation")
{
return true;
}
}
return false;
}
}
}

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

@ -1,168 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Simplification;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer
{
/// <summary>
/// This class provides guard suggestion and can make the suggested changes.
/// </summary>
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(PlatformSpecificFixerCS))]
[Shared]
public class PlatformSpecificFixerCS : CodeFixProvider
{
/// <summary>
/// Gets the list of Diagnostics that can be fixed.
/// </summary>
public sealed override ImmutableArray<string> FixableDiagnosticIds
{
get { return ImmutableArray.Create(Analyzer.PlatformRule.Id, Analyzer.VersionRule.Id); }
}
/// <summary>
/// Gets the Fix All provider
/// </summary>
/// <returns><see cref="WellKnownFixAllProviders"/></returns>
public sealed override FixAllProvider GetFixAllProvider()
{
return WellKnownFixAllProviders.BatchFixer;
}
/// <summary>
/// Registers for code fix.
/// </summary>
/// <param name="context"><see cref="CodeFixContext"/></param>
/// <returns>awaitable <see cref="Task"/></returns>
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
try
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);
// Which node are we interested in? -- if the squiggle is over A.B().C,
// then we need the largest IdentifierName/SimpleMemberAccess/QualifiedName
// that encompasses "C" itself
var diagnostic = context.Diagnostics.First();
var span = new TextSpan(diagnostic.Location.SourceSpan.End - 1, 1);
var node = root.FindToken(span.Start).Parent;
SyntaxKind nodeKind = node.Kind();
while (nodeKind != SyntaxKind.IdentifierName && nodeKind != SyntaxKind.SimpleMemberAccessExpression && nodeKind != SyntaxKind.QualifiedName)
{
node = node.Parent;
if (node == null)
{
return;
}
nodeKind = node.Kind();
}
while (true)
{
if (node.Parent?.Kind() == SyntaxKind.SimpleMemberAccessExpression)
{
node = node.Parent;
continue;
}
if (node.Parent?.Kind() == SyntaxKind.QualifiedName)
{
node = node.Parent;
continue;
}
break;
}
var target = PlatformSpecificAnalyzerCS.GetTargetOfNode(node, semanticModel);
var g = Analyzer.GetGuardForSymbol(target);
// Introduce a guard? (only if it is a method/accessor/constructor, i.e. somewhere that allows code)
var containingBlock = node.FirstAncestorOrSelf<BlockSyntax>();
if (containingBlock != null)
{
var act1 = CodeAction.Create($"Add 'If ApiInformation.{g.KindOfCheck}'", (c) => IntroduceGuardAsync(context.Document, node, g, c), "PlatformSpecificGuard");
context.RegisterCodeFix(act1, diagnostic);
}
}
catch
{
}
}
private async Task<Document> IntroduceGuardAsync(Document document, SyntaxNode node, HowToGuard g, CancellationToken cancellationToken)
{
// + if (Windows.Foundation.Metadata.ApiInformation.IsTypePresent(targetContainingType))
// {
// old-statement
// + }
try
{
var oldStatement = node.FirstAncestorOrSelf<StatementSyntax>();
var oldLeadingTrivia = oldStatement.GetLeadingTrivia();
var conditionReceiver = SyntaxFactory.ParseName($"Windows.Foundation.Metadata.ApiInformation.{g.KindOfCheck}").WithAdditionalAnnotations(Simplifier.Annotation);
ArgumentListSyntax conditionArgument = null;
if (g.MemberToCheck == null)
{
var conditionString1 = SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(g.TypeToCheck));
conditionArgument = SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument(conditionString1)));
}
else
{
var conditionString1 = SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(g.TypeToCheck));
var conditionString2 = SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(g.MemberToCheck));
var conditionInt3 = SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(g.ParameterCountToCheck ?? 0));
IEnumerable<ArgumentSyntax> conditions = null;
if (g.ParameterCountToCheck.HasValue)
{
conditions = new ArgumentSyntax[] { SyntaxFactory.Argument(conditionString1), SyntaxFactory.Argument(conditionString2), SyntaxFactory.Argument(conditionInt3) };
}
else
{
conditions = new ArgumentSyntax[] { SyntaxFactory.Argument(conditionString1), SyntaxFactory.Argument(conditionString2) };
}
conditionArgument = SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(conditions));
}
var condition = SyntaxFactory.InvocationExpression(conditionReceiver, conditionArgument);
var thenStatements = SyntaxFactory.Block(oldStatement.WithoutLeadingTrivia());
var ifStatement = SyntaxFactory.IfStatement(condition, thenStatements).WithLeadingTrivia(oldLeadingTrivia).WithAdditionalAnnotations(Formatter.Annotation);
var oldRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var newRoot = oldRoot.ReplaceNode(oldStatement, ifStatement);
return document.WithSyntaxRoot(newRoot);
}
catch
{
}
return document;
}
}
}

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

@ -1,168 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Simplification;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.VisualBasic;
using Microsoft.CodeAnalysis.VisualBasic.Syntax;
namespace Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer
{
/// <summary>
/// This class provides guard suggestion and can make the suggested changes.
/// </summary>
[ExportCodeFixProvider(LanguageNames.VisualBasic, Name = nameof(PlatformSpecificFixerCS))]
[Shared]
public class PlatformSpecificFixerVB : CodeFixProvider
{
/// <summary>
/// Gets the list of Diagnostics that can be fixed.
/// </summary>
public sealed override ImmutableArray<string> FixableDiagnosticIds
{
get { return ImmutableArray.Create(Analyzer.PlatformRule.Id, Analyzer.VersionRule.Id); }
}
/// <summary>
/// Gets the Fix All provider
/// </summary>
/// <returns><see cref="WellKnownFixAllProviders"/></returns>
public sealed override FixAllProvider GetFixAllProvider()
{
return WellKnownFixAllProviders.BatchFixer;
}
/// <summary>
/// Registers for code fix.
/// </summary>
/// <param name="context"><see cref="CodeFixContext"/></param>
/// <returns>awaitable <see cref="Task"/></returns>
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
try
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);
// Which node are we interested in? -- if the squiggle is over A.B().C,
// then we need the largest IdentifierName/SimpleMemberAccess/QualifiedName
// that encompasses "C" itself
var diagnostic = context.Diagnostics.First();
var span = new TextSpan(diagnostic.Location.SourceSpan.End - 1, 1);
var node = root.FindToken(span.Start).Parent;
SyntaxKind nodeKind = node.Kind();
while (nodeKind != SyntaxKind.IdentifierName && nodeKind != SyntaxKind.SimpleMemberAccessExpression && nodeKind != SyntaxKind.QualifiedName)
{
node = node.Parent;
if (node == null)
{
return;
}
nodeKind = node.Kind();
}
while (true)
{
if (node.Parent?.Kind() == SyntaxKind.SimpleMemberAccessExpression)
{
node = node.Parent;
continue;
}
if (node.Parent?.Kind() == SyntaxKind.QualifiedName)
{
node = node.Parent;
continue;
}
break;
}
var target = PlatformSpecificAnalyzerVB.GetTargetOfNode(node, semanticModel);
var g = Analyzer.GetGuardForSymbol(target);
// Introduce a guard? (only if it is a method/accessor/constructor, i.e. somewhere that allows code)
var containingBlock = node.FirstAncestorOrSelf<MethodBlockBaseSyntax>();
if (containingBlock != null)
{
var act1 = CodeAction.Create($"Add 'If ApiInformation.{g.KindOfCheck}'", (c) => IntroduceGuardAsync(context.Document, node, g, c), "PlatformSpecificGuard");
context.RegisterCodeFix(act1, diagnostic);
}
}
catch
{
}
}
private async Task<Document> IntroduceGuardAsync(Document document, SyntaxNode node, HowToGuard g, CancellationToken cancellationToken)
{
// + If Windows.Foundation.Metadata.ApiInformation.IsTypePresent(targetContainingType) Then
// old-statement
// + End If
try
{
var oldStatement = node.FirstAncestorOrSelf<StatementSyntax>();
var oldLeadingTrivia = oldStatement.GetLeadingTrivia();
var conditionReceiver = SyntaxFactory.ParseName($"Windows.Foundation.Metadata.ApiInformation.{g.KindOfCheck}").WithAdditionalAnnotations(Simplifier.Annotation);
ArgumentListSyntax conditionArgument = null;
if (g.MemberToCheck == null)
{
var conditionString1 = SyntaxFactory.StringLiteralExpression(SyntaxFactory.StringLiteralToken($"\"{g.TypeToCheck}\"", g.TypeToCheck));
conditionArgument = SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList<ArgumentSyntax>(SyntaxFactory.SimpleArgument(conditionString1)));
}
else
{
var conditionString1 = SyntaxFactory.StringLiteralExpression(SyntaxFactory.StringLiteralToken($"\"{g.TypeToCheck}\"", g.TypeToCheck));
var conditionString2 = SyntaxFactory.StringLiteralExpression(SyntaxFactory.StringLiteralToken($"\"{g.MemberToCheck}\"", g.MemberToCheck));
var conditionInt3 = SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(g.ParameterCountToCheck ?? 0));
IEnumerable<ArgumentSyntax> conditions = null;
if (g.ParameterCountToCheck.HasValue)
{
conditions = new ArgumentSyntax[] { SyntaxFactory.SimpleArgument(conditionString1), SyntaxFactory.SimpleArgument(conditionString2), SyntaxFactory.SimpleArgument(conditionInt3) };
}
else
{
conditions = new ArgumentSyntax[] { SyntaxFactory.SimpleArgument(conditionString1), SyntaxFactory.SimpleArgument(conditionString2) };
}
conditionArgument = SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(conditions));
}
var condition = SyntaxFactory.InvocationExpression(conditionReceiver, conditionArgument);
var ifStatement = SyntaxFactory.IfStatement(condition);
var thenStatements = SyntaxFactory.SingletonList(oldStatement.WithoutLeadingTrivia());
var ifBlock = SyntaxFactory.MultiLineIfBlock(ifStatement).WithStatements(thenStatements).WithLeadingTrivia(oldLeadingTrivia).WithAdditionalAnnotations(Formatter.Annotation);
var oldRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var newRoot = oldRoot.ReplaceNode(oldStatement, ifBlock);
return document.WithSyntaxRoot(newRoot);
}
catch
{
}
return document;
}
}
}

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

@ -1,58 +0,0 @@
param($installPath, $toolsPath, $package, $project)
if($project.Object.SupportsPackageDependencyResolution)
{
if($project.Object.SupportsPackageDependencyResolution())
{
# Do not install analyzers via install.ps1, instead let the project system handle it.
return
}
}
$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve
foreach($analyzersPath in $analyzersPaths)
{
if (Test-Path $analyzersPath)
{
# Install the language agnostic analyzers.
foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll)
{
if($project.Object.AnalyzerReferences)
{
$project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName)
}
}
}
}
# $project.Type gives the language name like (C# or VB.NET)
$languageFolder = ""
if($project.Type -eq "C#")
{
$languageFolder = "cs"
}
if($project.Type -eq "VB.NET")
{
$languageFolder = "vb"
}
if($languageFolder -eq "")
{
return
}
foreach($analyzersPath in $analyzersPaths)
{
# Install language specific analyzers.
$languageAnalyzersPath = join-path $analyzersPath $languageFolder
if (Test-Path $languageAnalyzersPath)
{
foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll)
{
if($project.Object.AnalyzerReferences)
{
$project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName)
}
}
}
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше