Merge branch 'master' into aleader/notifications-registry
This commit is contained in:
Коммит
0b65a73b30
|
@ -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<int> myTask;
|
||||
///
|
||||
/// public Task<int> 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<ILogger, Logger>();
|
||||
/// });
|
||||
/// </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<ILogger>().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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче