diff --git a/src/NJsonSchema.Tests/Generation/XmlDocTests.cs b/src/NJsonSchema.Tests/Generation/XmlDocTests.cs index ec10d8767..d6de7103d 100644 --- a/src/NJsonSchema.Tests/Generation/XmlDocTests.cs +++ b/src/NJsonSchema.Tests/Generation/XmlDocTests.cs @@ -118,7 +118,7 @@ public async Task When_xml_doc_is_missing_then_summary_is_missing() //// Assert Assert.Empty(summary); } - + public abstract class BaseBaseClass { /// Foo. diff --git a/src/NJsonSchema/Generation/JsonSchemaGenerator.cs b/src/NJsonSchema/Generation/JsonSchemaGenerator.cs index ff733f2fb..1947bc34e 100644 --- a/src/NJsonSchema/Generation/JsonSchemaGenerator.cs +++ b/src/NJsonSchema/Generation/JsonSchemaGenerator.cs @@ -325,7 +325,7 @@ public virtual string GetPropertyName(Newtonsoft.Json.Serialization.JsonProperty schema.IsAbstract = type.GetTypeInfo().IsAbstract; await GeneratePropertiesAndInheritanceAsync(type, schema, schemaResolver).ConfigureAwait(false); - await ApplyAdditionalPropertiesAsync(type, schema, schemaResolver); + await ApplyAdditionalPropertiesAsync(type, schema, schemaResolver).ConfigureAwait(false); if (Settings.GenerateKnownTypes) await GenerateKnownTypesAsync(type, schemaResolver).ConfigureAwait(false); @@ -345,7 +345,7 @@ public virtual string GetPropertyName(Newtonsoft.Json.Serialization.JsonProperty var genericTypeArguments = extensionDataProperty.PropertyType.GetGenericTypeArguments(); var extensionDataPropertyType = genericTypeArguments.Length == 2 ? genericTypeArguments[1] : typeof(object); - schema.AdditionalPropertiesSchema = await GenerateWithReferenceAndNullabilityAsync(extensionDataPropertyType, null, schemaResolver); + schema.AdditionalPropertiesSchema = await GenerateWithReferenceAndNullabilityAsync(extensionDataPropertyType, null, schemaResolver).ConfigureAwait(false); } else schema.AllowAdditionalProperties = false; diff --git a/src/NJsonSchema/Infrastructure/DynamicApis.cs b/src/NJsonSchema/Infrastructure/DynamicApis.cs index 68eb7608b..8743f4a2d 100644 --- a/src/NJsonSchema/Infrastructure/DynamicApis.cs +++ b/src/NJsonSchema/Infrastructure/DynamicApis.cs @@ -121,7 +121,7 @@ public static async Task DirectoryExistsAsync(string filePath) return false; return await FromResult((bool)DirectoryType.GetRuntimeMethod("Exists", - new[] { typeof(string) }).Invoke(null, new object[] { filePath })); + new[] { typeof(string) }).Invoke(null, new object[] { filePath })).ConfigureAwait(false); } /// Checks whether a file exists. @@ -137,7 +137,7 @@ public static async Task FileExistsAsync(string filePath) return false; return await FromResult((bool)FileType.GetRuntimeMethod("Exists", - new[] { typeof(string) }).Invoke(null, new object[] { filePath })); + new[] { typeof(string) }).Invoke(null, new object[] { filePath })).ConfigureAwait(false); } /// Reads all content of a file (UTF8). @@ -206,13 +206,13 @@ public static object XPathEvaluate(XDocument document, string path) } #if LEGACY - private static async Task FromResult(T result) + internal static async Task FromResult(T result) { return result; } #else [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Task FromResult(T result) + internal static Task FromResult(T result) { return Task.FromResult(result); } diff --git a/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs b/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs index d05b6f47c..4100d4aaa 100644 --- a/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs +++ b/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs @@ -23,10 +23,8 @@ namespace NJsonSchema.Infrastructure /// This class currently works only on the desktop .NET framework. public static class XmlDocumentationExtensions { - private static readonly SemaphoreSlim _lock = new SemaphoreSlim(1, 1); - - private static readonly Dictionary Cache = - new Dictionary(StringComparer.OrdinalIgnoreCase); + private static readonly AsyncLock Lock = new AsyncLock(); + private static readonly Dictionary Cache = new Dictionary(StringComparer.OrdinalIgnoreCase); #if !LEGACY @@ -82,91 +80,6 @@ public static async Task GetXmlRemarksAsync(this MemberInfo member) return await GetXmlDocumentationTagAsync(member, "remarks").ConfigureAwait(false); } - /// Returns the contents of an XML documentation tag for the specified member. - /// The reflected member. - /// The contents of the "summary" tag for the member. - public static Task GetXmlDocumentationAsync(this MemberInfo member) - { - return GetXmlDocumentationAsync(member, true); - } - - /// Returns the contents of an XML documentation tag for the specified member. - /// The reflected member. - /// Name of the tag. - /// The contents of the "summary" tag for the member. - public static async Task GetXmlDocumentationTagAsync(this MemberInfo member, string tagName) - { - if (DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) - return string.Empty; - - var assemblyName = member.Module.Assembly.GetName(); - if (await IgnoreAssemblyAsync(assemblyName, true)) - return string.Empty; - - var documentationPath = await GetXmlDocumentationPathAsync(member.Module.Assembly).ConfigureAwait(false); - var element = await GetXmlDocumentationAsync(member, documentationPath).ConfigureAwait(false); - return RemoveLineBreakWhiteSpaces(GetXmlDocumentationText(element?.Element(tagName))); - } - - /// Returns the contents of the "returns" or "param" XML documentation tag for the specified parameter. - /// The reflected parameter or return info. - /// The contents of the "returns" or "param" tag. - public static async Task GetXmlDocumentationAsync(this ParameterInfo parameter) - { - if (DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) - return string.Empty; - - var assemblyName = parameter.Member.Module.Assembly.GetName(); - if (await IgnoreAssemblyAsync(assemblyName, true)) - return string.Empty; - - var documentationPath = await GetXmlDocumentationPathAsync(parameter.Member.Module.Assembly).ConfigureAwait(false); - var element = await GetXmlDocumentationAsync(parameter, documentationPath).ConfigureAwait(false); - return RemoveLineBreakWhiteSpaces(GetXmlDocumentationText(element)); - } - - /// Returns the contents of the "summary" XML documentation tag for the specified member. - /// The type. - /// The path to the XML documentation file. - /// The contents of the "summary" tag for the member. - public static Task GetXmlDocumentationAsync(this Type type, string pathToXmlFile) - { - return ((MemberInfo)type.GetTypeInfo()).GetXmlDocumentationAsync(pathToXmlFile); - } - - /// Returns the contents of the "summary" XML documentation tag for the specified member. - /// The reflected member. - /// The path to the XML documentation file. - /// The contents of the "summary" tag for the member. - public static Task GetXmlDocumentationAsync(this MemberInfo member, string pathToXmlFile) - { - return GetXmlDocumentationAsync(member, pathToXmlFile, true); - } - - /// Returns the contents of the "returns" or "param" XML documentation tag for the specified parameter. - /// The reflected parameter or return info. - /// The path to the XML documentation file. - /// The contents of the "returns" or "param" tag. - public static async Task GetXmlDocumentationAsync(this ParameterInfo parameter, string pathToXmlFile) - { - try - { - if (pathToXmlFile == null || DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) - return null; - - var assemblyName = parameter.Member.Module.Assembly.GetName(); - var document = await TryGetXmlDocumentAsync(assemblyName, pathToXmlFile, true); - if (document == null) - return null; - - return await GetXmlDocumentationAsync(parameter, document); - } - catch - { - return null; - } - } - /// Gets the description of the given member (based on the DescriptionAttribute, DisplayAttribute or XML Documentation). /// The member info /// The attributes. @@ -269,52 +182,84 @@ public static string GetXmlDocumentationText(this XElement element) /// Clears the cache. /// The task. - public static async Task ClearCacheAsync() + public static Task ClearCacheAsync() { -#if !LEGACY - await _lock.WaitAsync(); -#else - _lock.Wait(); -#endif - - try + using (Lock.Lock()) { Cache.Clear(); + return DynamicApis.FromResult(null); } - finally + } + + /// Returns the contents of an XML documentation tag for the specified member. + /// The reflected member. + /// Name of the tag. + /// The contents of the "summary" tag for the member. + public static async Task GetXmlDocumentationTagAsync(this MemberInfo member, string tagName) + { + if (DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) + return string.Empty; + + var assemblyName = member.Module.Assembly.GetName(); + using (Lock.Lock()) { - _lock.Release(); + if (IgnoreAssembly(assemblyName)) + return string.Empty; + + var documentationPath = await GetXmlDocumentationPathAsync(member.Module.Assembly).ConfigureAwait(false); + var element = await GetXmlDocumentationWithoutLockAsync(member, documentationPath).ConfigureAwait(false); + return RemoveLineBreakWhiteSpaces(GetXmlDocumentationText(element?.Element(tagName))); } } - private static async Task GetXmlDocumentationAsync(this MemberInfo member, bool useLock) + /// Returns the contents of the "returns" or "param" XML documentation tag for the specified parameter. + /// The reflected parameter or return info. + /// The contents of the "returns" or "param" tag. + public static async Task GetXmlDocumentationAsync(this ParameterInfo parameter) { if (DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) - return null; + return string.Empty; - var assemblyName = member.Module.Assembly.GetName(); - if (await IgnoreAssemblyAsync(assemblyName, useLock)) - return null; + var assemblyName = parameter.Member.Module.Assembly.GetName(); + using (Lock.Lock()) + { + if (IgnoreAssembly(assemblyName)) + return string.Empty; - var documentationPath = await GetXmlDocumentationPathAsync(member.Module.Assembly).ConfigureAwait(false); - return await GetXmlDocumentationAsync(member, documentationPath, useLock).ConfigureAwait(false); + var documentationPath = await GetXmlDocumentationPathAsync(parameter.Member.Module.Assembly).ConfigureAwait(false); + var element = await GetXmlDocumentationWithoutLockAsync(parameter, documentationPath).ConfigureAwait(false); + return RemoveLineBreakWhiteSpaces(GetXmlDocumentationText(element)); + } } - private static async Task GetXmlDocumentationAsync(this MemberInfo member, string pathToXmlFile, bool useLock) + /// Returns the contents of the "summary" XML documentation tag for the specified member. + /// The type. + /// The path to the XML documentation file. + /// The contents of the "summary" tag for the member. + public static async Task GetXmlDocumentationAsync(this Type type, string pathToXmlFile) + { + using (Lock.Lock()) + { + return await ((MemberInfo)type.GetTypeInfo()).GetXmlDocumentationWithoutLockAsync(pathToXmlFile).ConfigureAwait(false); + } + } + + /// Returns the contents of the "returns" or "param" XML documentation tag for the specified parameter. + /// The reflected parameter or return info. + /// The path to the XML documentation file. + /// The contents of the "returns" or "param" tag. + public static async Task GetXmlDocumentationAsync(this ParameterInfo parameter, string pathToXmlFile) { try { if (pathToXmlFile == null || DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) return null; - var assemblyName = member.Module.Assembly.GetName(); - var document = await TryGetXmlDocumentAsync(assemblyName, pathToXmlFile, useLock); - if (document == null) - return null; - - var element = GetXmlDocumentation(member, document); - await ReplaceInheritdocElementsAsync(member, element, useLock); - return element; + var assemblyName = parameter.Member.Module.Assembly.GetName(); + using (Lock.Lock()) + { + return await GetXmlDocumentationWithoutLockAsync(parameter, pathToXmlFile).ConfigureAwait(false); + } } catch { @@ -322,61 +267,105 @@ private static async Task GetXmlDocumentationAsync(this MemberInfo mem } } - private static async Task TryGetXmlDocumentAsync(AssemblyName assemblyName, string pathToXmlFile, bool useLock) + /// Returns the contents of an XML documentation tag for the specified member. + /// The reflected member. + /// The contents of the "summary" tag for the member. + public static async Task GetXmlDocumentationAsync(this MemberInfo member) { - if (useLock) + using (Lock.Lock()) { -#if !LEGACY - await _lock.WaitAsync(); -#else - _lock.Wait(); -#endif + return await GetXmlDocumentationWithoutLockAsync(member).ConfigureAwait(false); + } + } + + /// Returns the contents of the "summary" XML documentation tag for the specified member. + /// The reflected member. + /// The path to the XML documentation file. + /// The contents of the "summary" tag for the member. + public static async Task GetXmlDocumentationAsync(this MemberInfo member, string pathToXmlFile) + { + using (Lock.Lock()) + { + return await GetXmlDocumentationWithoutLockAsync(member, pathToXmlFile).ConfigureAwait(false); } + } + private static async Task GetXmlDocumentationWithoutLockAsync(this ParameterInfo parameter, string pathToXmlFile) + { try { - if (!Cache.ContainsKey(assemblyName.FullName)) - { - if (await DynamicApis.FileExistsAsync(pathToXmlFile).ConfigureAwait(false) == false) - { - Cache[assemblyName.FullName] = null; - return null; - } + if (DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) + return null; - Cache[assemblyName.FullName] = await Task.Factory.StartNew(() => XDocument.Load(pathToXmlFile, LoadOptions.PreserveWhitespace)).ConfigureAwait(false); - } + var assemblyName = parameter.Member.Module.Assembly.GetName(); + var document = await TryGetXmlDocumentAsync(assemblyName, pathToXmlFile).ConfigureAwait(false); + if (document == null) + return null; - return Cache[assemblyName.FullName]; + return await GetXmlDocumentationAsync(parameter, document).ConfigureAwait(false); } - finally + catch { - if (useLock) - _lock.Release(); + return null; } } - private static async Task IgnoreAssemblyAsync(AssemblyName assemblyName, bool useLock) + private static async Task GetXmlDocumentationWithoutLockAsync(this MemberInfo member) { - if (useLock) - { -#if !LEGACY - await _lock.WaitAsync(); -#else - _lock.Wait(); -#endif - } + if (DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) + return null; + var assemblyName = member.Module.Assembly.GetName(); + if (IgnoreAssembly(assemblyName)) + return null; + + var documentationPath = await GetXmlDocumentationPathAsync(member.Module.Assembly).ConfigureAwait(false); + return await GetXmlDocumentationWithoutLockAsync(member, documentationPath).ConfigureAwait(false); + } + + private static async Task GetXmlDocumentationWithoutLockAsync(this MemberInfo member, string pathToXmlFile) + { try { - if (Cache.ContainsKey(assemblyName.FullName) && Cache[assemblyName.FullName] == null) - return true; + if (DynamicApis.SupportsXPathApis == false || DynamicApis.SupportsFileApis == false || DynamicApis.SupportsPathApis == false) + return null; + + var assemblyName = member.Module.Assembly.GetName(); + var document = await TryGetXmlDocumentAsync(assemblyName, pathToXmlFile).ConfigureAwait(false); + if (document == null) + return null; + + var element = GetXmlDocumentation(member, document); + await ReplaceInheritdocElementsAsync(member, element).ConfigureAwait(false); + return element; + } + catch + { + return null; } - finally + } + + private static async Task TryGetXmlDocumentAsync(AssemblyName assemblyName, string pathToXmlFile) + { + if (!Cache.ContainsKey(assemblyName.FullName)) { - if (useLock) - _lock.Release(); + if (await DynamicApis.FileExistsAsync(pathToXmlFile).ConfigureAwait(false) == false) + { + Cache[assemblyName.FullName] = null; + return null; + } + + Cache[assemblyName.FullName] = await Task.Factory.StartNew(() => XDocument.Load(pathToXmlFile, LoadOptions.PreserveWhitespace)).ConfigureAwait(false); } + return Cache[assemblyName.FullName]; + } + + private static bool IgnoreAssembly(AssemblyName assemblyName) + { + if (Cache.ContainsKey(assemblyName.FullName) && Cache[assemblyName.FullName] == null) + return true; + return false; } @@ -393,7 +382,7 @@ private static async Task GetXmlDocumentationAsync(this ParameterInfo var result = (IEnumerable)DynamicApis.XPathEvaluate(xml, $"/doc/members/member[@name='{name}']"); var element = result.OfType().First(); - await ReplaceInheritdocElementsAsync(parameter.Member, element, true); + await ReplaceInheritdocElementsAsync(parameter.Member, element).ConfigureAwait(false); if (parameter.IsRetval || string.IsNullOrEmpty(parameter.Name)) result = (IEnumerable)DynamicApis.XPathEvaluate(xml, $"/doc/members/member[@name='{name}']/returns"); @@ -403,49 +392,38 @@ private static async Task GetXmlDocumentationAsync(this ParameterInfo return result.OfType().FirstOrDefault(); } - private static async Task ReplaceInheritdocElementsAsync(this MemberInfo member, XElement element, bool useLock) + private static async Task ReplaceInheritdocElementsAsync(this MemberInfo member, XElement element) { #if !LEGACY if (element == null) return; - if (useLock) - await _lock.WaitAsync(); - - try + var children = element.Nodes().ToList(); + foreach (var child in children.OfType()) { - var children = element.Nodes().ToList(); - foreach (var child in children.OfType()) + if (child.Name.LocalName.ToLowerInvariant() == "inheritdoc") { - if (child.Name.LocalName.ToLowerInvariant() == "inheritdoc") + var baseType = member.DeclaringType.GetTypeInfo().BaseType; + var baseMember = baseType?.GetTypeInfo().DeclaredMembers.SingleOrDefault(m => m.Name == member.Name); + if (baseMember != null) { - var baseType = member.DeclaringType.GetTypeInfo().BaseType; - var baseMember = baseType?.GetTypeInfo().DeclaredMembers.SingleOrDefault(m => m.Name == member.Name); - if (baseMember != null) + var baseDoc = await baseMember.GetXmlDocumentationWithoutLockAsync().ConfigureAwait(false); + if (baseDoc != null) { - var baseDoc = await baseMember.GetXmlDocumentationAsync(false); - if (baseDoc != null) - { - var nodes = baseDoc.Nodes().OfType().ToArray(); - child.ReplaceWith(nodes); - } - else - { - await ProcessInheritdocInterfaceElementsAsync(member, child); - } + var nodes = baseDoc.Nodes().OfType().ToArray(); + child.ReplaceWith(nodes); } else { - await ProcessInheritdocInterfaceElementsAsync(member, child); + await ProcessInheritdocInterfaceElementsAsync(member, child).ConfigureAwait(false); } } + else + { + await ProcessInheritdocInterfaceElementsAsync(member, child).ConfigureAwait(false); + } } } - finally - { - if (useLock) - _lock.Release(); - } #endif } @@ -457,7 +435,7 @@ private static async Task ProcessInheritdocInterfaceElementsAsync(this MemberInf var baseMember = baseInterface?.GetTypeInfo().DeclaredMembers.SingleOrDefault(m => m.Name == member.Name); if (baseMember != null) { - var baseDoc = await baseMember.GetXmlDocumentationAsync(false); + var baseDoc = await baseMember.GetXmlDocumentationWithoutLockAsync().ConfigureAwait(false); if (baseDoc != null) { var nodes = baseDoc.Nodes().OfType().ToArray(); @@ -546,6 +524,9 @@ private static async Task GetXmlDocumentationPathAsync(dynamic assembly) if (string.IsNullOrEmpty(assemblyName.Name)) return null; + if (Cache.ContainsKey(assemblyName.FullName)) + return null; + var assemblyDirectory = DynamicApis.PathGetDirectoryName((string)assembly.Location); var path = DynamicApis.PathCombine(assemblyDirectory, (string)assemblyName.Name + ".xml"); if (await DynamicApis.FileExistsAsync(path).ConfigureAwait(false)) @@ -579,5 +560,21 @@ private static async Task GetXmlDocumentationPathAsync(dynamic assembly) return null; } } + + private class AsyncLock : IDisposable + { + private SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1); + + public AsyncLock Lock() + { + _semaphoreSlim.Wait(); + return this; + } + + public void Dispose() + { + _semaphoreSlim.Release(); + } + } } -} +} \ No newline at end of file diff --git a/src/NJsonSchema/JsonReferenceResolver.cs b/src/NJsonSchema/JsonReferenceResolver.cs index dc22ad9ae..d6e26c068 100644 --- a/src/NJsonSchema/JsonReferenceResolver.cs +++ b/src/NJsonSchema/JsonReferenceResolver.cs @@ -58,7 +58,7 @@ public void AddDocumentReference(string documentPath, IJsonReference schema) /// Could not resolve the JSON path. public async Task ResolveReferenceAsync(object rootObject, string jsonPath) { - return await ResolveReferenceAsync(rootObject, jsonPath, true); + return await ResolveReferenceAsync(rootObject, jsonPath, true).ConfigureAwait(false); } /// Gets the object from the given JSON path. @@ -69,7 +69,7 @@ public async Task ResolveReferenceAsync(object rootObject, strin /// Could not resolve the JSON path. public async Task ResolveReferenceWithoutAppendAsync(object rootObject, string jsonPath) { - return await ResolveReferenceAsync(rootObject, jsonPath, false); + return await ResolveReferenceAsync(rootObject, jsonPath, false).ConfigureAwait(false); } /// Resolves a document reference. diff --git a/src/NJsonSchema/JsonSchema4.cs b/src/NJsonSchema/JsonSchema4.cs index e9a230b91..87f408e28 100644 --- a/src/NJsonSchema/JsonSchema4.cs +++ b/src/NJsonSchema/JsonSchema4.cs @@ -158,7 +158,7 @@ public static async Task FromUrlAsync(string url) /// The HttpClient.GetAsync API is not available on this platform. public static async Task FromUrlAsync(string url, Func referenceResolverFactory) { - var data = await DynamicApis.HttpGetAsync(url); + var data = await DynamicApis.HttpGetAsync(url).ConfigureAwait(false); return await FromJsonAsync(data, url, referenceResolverFactory).ConfigureAwait(false); } diff --git a/src/NJsonSchema/JsonSchemaReferenceUtilities.cs b/src/NJsonSchema/JsonSchemaReferenceUtilities.cs index 13e0f9c1c..018fcb35a 100644 --- a/src/NJsonSchema/JsonSchemaReferenceUtilities.cs +++ b/src/NJsonSchema/JsonSchemaReferenceUtilities.cs @@ -96,9 +96,9 @@ public JsonReferenceUpdater(object rootObject, JsonReferenceResolver referenceRe public override async Task VisitAsync(object obj) { _replaceRefsRound = true; - await base.VisitAsync(obj); + await base.VisitAsync(obj).ConfigureAwait(false); _replaceRefsRound = false; - await base.VisitAsync(obj); + await base.VisitAsync(obj).ConfigureAwait(false); } protected override async Task VisitJsonReferenceAsync(IJsonReference reference, string path, string typeNameHint)