Class XmlDocumentationService
Namespace: Momentum.Extensions.XmlDocs
Assembly: Momentum.Extensions.XmlDocs.dll
Provides services for loading and retrieving XML documentation comments from compiled assemblies.
public class XmlDocumentationService : IXmlDocumentationService
Inheritance
object ← XmlDocumentationService
Implements
Inherited Members
object.GetType(), object.MemberwiseClone(), object.ToString(), object.Equals(object?), object.Equals(object?, object?), object.ReferenceEquals(object?, object?), object.GetHashCode()
Remarks
XML Documentation Service
Service Overview
This service parses XML documentation files generated during compilation and provides methods to retrieve documentation for types, methods, properties, and other members. Documentation is cached for performance after initial loading.
Core Functionality
Documentation Loading
public async Task<bool> LoadDocumentationAsync(string xmlFilePath)
{
if (!File.Exists(xmlFilePath))
{
logger.LogWarning("XML documentation file not found: {FilePath}", xmlFilePath);
return false;
}
try
{
await using var fileStream = new FileStream(xmlFilePath,
FileMode.Open,
FileAccess.Read,
FileShare.Read,
bufferSize: 8192,
useAsync: true);
using var xmlReader = XmlReader.Create(fileStream, new XmlReaderSettings
{
Async = true,
IgnoreComments = true,
IgnoreWhitespace = false,
ConformanceLevel = ConformanceLevel.Document
});
await ParseXmlDocumentationAsync(xmlReader);
logger.LogInformation("Loaded XML documentation from {FilePath} with {Count} entries",
xmlFilePath, _documentationCache.Count);
return true;
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to load XML documentation from {FilePath}", xmlFilePath);
return false;
}
}
Performance Optimizations
Asynchronous File I/O
await using var fileStream = new FileStream(xmlFilePath,
FileMode.Open,
FileAccess.Read,
FileShare.Read,
bufferSize: 8192,
useAsync: true);
Efficient XML Reading
using var xmlReader = XmlReader.Create(fileStream, new XmlReaderSettings
{
Async = true,
IgnoreComments = true,
IgnoreWhitespace = false,
ConformanceLevel = ConformanceLevel.Document
});
Documentation Parsing
Member Discovery
private async Task ParseXmlDocumentationAsync(XmlReader reader)
{
while (await reader.ReadAsync())
{
if (reader is not { NodeType: XmlNodeType.Element, Name: "member" })
continue;
var nameAttribute = reader.GetAttribute("name");
if (!string.IsNullOrEmpty(nameAttribute))
{
var docInfo = await ParseMemberDocumentationAsync(reader);
if (docInfo is not null)
{
_documentationCache.TryAdd(nameAttribute, docInfo);
}
}
}
}
Element Processing
private static async Task<XmlDocumentationInfo?> ParseMemberDocumentationAsync(XmlReader reader)
{
var docInfo = new XmlDocumentationInfo();
var hasContent = false;
if (reader.IsEmptyElement)
return null;
while (await reader.ReadAsync())
{
if (reader is { NodeType: XmlNodeType.EndElement, Name: "member" })
break;
if (reader.NodeType == XmlNodeType.Element)
{
switch (reader.Name.ToLowerInvariant())
{
case "summary":
docInfo.Summary = await ReadElementContentAsync(reader);
hasContent = true;
break;
case "remarks":
docInfo.Remarks = await ReadElementContentAsync(reader);
hasContent = true;
break;
// ... other elements
}
}
}
return hasContent ? docInfo : null;
}
Caching Strategy
Thread-Safe Caching
private readonly ConcurrentDictionary<string, XmlDocumentationInfo> _documentationCache = new();
Cache Operations
public XmlDocumentationInfo? GetDocumentation(string memberName) =>
_documentationCache.GetValueOrDefault(memberName);
public void ClearCache() => _documentationCache.Clear();
Member Name Resolution
Type Documentation
public XmlDocumentationInfo? GetTypeDocumentation(Type type)
{
return GetDocumentation($"T:{type.FullName}");
}
Method Documentation
public XmlDocumentationInfo? GetMethodDocumentation(MethodInfo methodInfo)
{
var memberName = GetMethodDocumentationName(methodInfo);
return GetDocumentation(memberName);
}
Property Documentation
public XmlDocumentationInfo? GetPropertyDocumentation(PropertyInfo propertyInfo)
{
return GetDocumentation($"P:{propertyInfo.DeclaringType?.FullName}.{propertyInfo.Name}");
}
Method Name Generation
Complex Method Signatures
private static string GetMethodDocumentationName(MethodInfo methodInfo)
{
var sb = new StringBuilder();
sb.Append("M:");
sb.Append(methodInfo.DeclaringType?.FullName);
sb.Append('.');
sb.Append(methodInfo.Name);
if (methodInfo.GetParameters().Length > 0)
{
sb.Append('(');
var parameters = methodInfo.GetParameters();
for (var i = 0; i < parameters.Length; i++)
{
if (i > 0) sb.Append(',');
sb.Append(GetTypeName(parameters[i].ParameterType));
}
sb.Append(')');
}
return sb.ToString();
}
Generic Type Support
private static string GetTypeName(Type type)
{
if (!type.IsGenericType)
return type.FullName ?? type.Name;
var genericTypeName = type.GetGenericTypeDefinition().FullName;
var genericArgs = type.GetGenericArguments();
var sb = new StringBuilder();
sb.Append(genericTypeName?[..genericTypeName.IndexOf('`')]);
sb.Append('{');
for (var i = 0; i < genericArgs.Length; i++)
{
if (i > 0) sb.Append(',');
sb.Append(GetTypeName(genericArgs[i]));
}
sb.Append('}');
return sb.ToString();
}
Content Extraction
Element Content Reading
private static async Task<string?> ReadElementContentAsync(XmlReader reader)
{
if (reader.IsEmptyElement)
return null;
var content = new StringBuilder();
while (await reader.ReadAsync())
{
if (reader.NodeType == XmlNodeType.EndElement)
break;
if (reader.NodeType is XmlNodeType.Text or XmlNodeType.CDATA)
{
content.Append(reader.Value);
}
}
var result = content.ToString();
// Preserve internal newlines but trim leading/trailing whitespace
return result.Trim();
}
Supported XML Elements
Core Documentation Elements
- summary: Main description of the member
- remarks: Additional detailed information
- returns: Description of return value
- example: Usage examples
Parameter Documentation
case "param":
var paramName = reader.GetAttribute("name");
if (!string.IsNullOrEmpty(paramName))
{
var paramDoc = await ReadElementContentAsync(reader);
var paramExample = reader.GetAttribute("example");
docInfo.Parameters[paramName] = new XmlDocumentationInfo.ParameterInfo(paramDoc, paramExample);
hasContent = true;
}
break;
Response Documentation
case "response":
var responseCode = reader.GetAttribute("code");
var responseDoc = await ReadElementContentAsync(reader);
if (!string.IsNullOrEmpty(responseCode))
{
docInfo.Responses[responseCode] = responseDoc;
hasContent = true;
}
break;
Error Handling
File Access Errors
if (!File.Exists(xmlFilePath))
{
logger.LogWarning("XML documentation file not found: {FilePath}", xmlFilePath);
return false;
}
XML Parsing Errors
catch (Exception ex)
{
logger.LogError(ex, "Failed to load XML documentation from {FilePath}", xmlFilePath);
return false;
}
Graceful Degradation
The service continues operation even when:
- XML Files Missing: Returns null for documentation queries
- Malformed XML: Skips problematic elements
- Empty Elements: Ignores elements without content
Integration with OpenAPI
Service Registration
services.AddSingleton<IXmlDocumentationService, XmlDocumentationService>();
Usage in Transformers
var methodDoc = xmlDocumentationService.GetMethodDocumentation(methodInfo);
if (methodDoc?.Summary is not null)
{
operation.Summary = methodDoc.Summary;
}
Performance Characteristics
Memory Usage
- Efficient Caching: Documentation cached after first access
- Memory Bounded: Cache size limited by assembly size
- String Interning: Repeated strings optimized
I/O Performance
- Async Operations: Non-blocking file access
- Buffered Reading: 8KB buffer for optimal throughput
- Single Load: Documentation loaded once per application lifecycle
Thread Safety
- Concurrent Access: Thread-safe cache operations
- Read-Heavy Optimization: Optimized for frequent reads
- Lazy Loading: Documentation loaded on demand
Constructors
XmlDocumentationService(ILogger<XmlDocumentationService>)
Provides services for loading and retrieving XML documentation comments from compiled assemblies.
public XmlDocumentationService(ILogger<XmlDocumentationService> logger)
Parameters
logger
ILogger<XmlDocumentationService>
Remarks
XML Documentation Service
Service Overview
This service parses XML documentation files generated during compilation and provides methods to retrieve documentation for types, methods, properties, and other members. Documentation is cached for performance after initial loading.
Core Functionality
Documentation Loading
public async Task<bool> LoadDocumentationAsync(string xmlFilePath)
{
if (!File.Exists(xmlFilePath))
{
logger.LogWarning("XML documentation file not found: {FilePath}", xmlFilePath);
return false;
}
try
{
await using var fileStream = new FileStream(xmlFilePath,
FileMode.Open,
FileAccess.Read,
FileShare.Read,
bufferSize: 8192,
useAsync: true);
using var xmlReader = XmlReader.Create(fileStream, new XmlReaderSettings
{
Async = true,
IgnoreComments = true,
IgnoreWhitespace = false,
ConformanceLevel = ConformanceLevel.Document
});
await ParseXmlDocumentationAsync(xmlReader);
logger.LogInformation("Loaded XML documentation from {FilePath} with {Count} entries",
xmlFilePath, _documentationCache.Count);
return true;
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to load XML documentation from {FilePath}", xmlFilePath);
return false;
}
}
Performance Optimizations
Asynchronous File I/O
await using var fileStream = new FileStream(xmlFilePath,
FileMode.Open,
FileAccess.Read,
FileShare.Read,
bufferSize: 8192,
useAsync: true);
Efficient XML Reading
using var xmlReader = XmlReader.Create(fileStream, new XmlReaderSettings
{
Async = true,
IgnoreComments = true,
IgnoreWhitespace = false,
ConformanceLevel = ConformanceLevel.Document
});
Documentation Parsing
Member Discovery
private async Task ParseXmlDocumentationAsync(XmlReader reader)
{
while (await reader.ReadAsync())
{
if (reader is not { NodeType: XmlNodeType.Element, Name: "member" })
continue;
var nameAttribute = reader.GetAttribute("name");
if (!string.IsNullOrEmpty(nameAttribute))
{
var docInfo = await ParseMemberDocumentationAsync(reader);
if (docInfo is not null)
{
_documentationCache.TryAdd(nameAttribute, docInfo);
}
}
}
}
Element Processing
private static async Task<XmlDocumentationInfo?> ParseMemberDocumentationAsync(XmlReader reader)
{
var docInfo = new XmlDocumentationInfo();
var hasContent = false;
if (reader.IsEmptyElement)
return null;
while (await reader.ReadAsync())
{
if (reader is { NodeType: XmlNodeType.EndElement, Name: "member" })
break;
if (reader.NodeType == XmlNodeType.Element)
{
switch (reader.Name.ToLowerInvariant())
{
case "summary":
docInfo.Summary = await ReadElementContentAsync(reader);
hasContent = true;
break;
case "remarks":
docInfo.Remarks = await ReadElementContentAsync(reader);
hasContent = true;
break;
// ... other elements
}
}
}
return hasContent ? docInfo : null;
}
Caching Strategy
Thread-Safe Caching
private readonly ConcurrentDictionary<string, XmlDocumentationInfo> _documentationCache = new();
Cache Operations
public XmlDocumentationInfo? GetDocumentation(string memberName) =>
_documentationCache.GetValueOrDefault(memberName);
public void ClearCache() => _documentationCache.Clear();
Member Name Resolution
Type Documentation
public XmlDocumentationInfo? GetTypeDocumentation(Type type)
{
return GetDocumentation($"T:{type.FullName}");
}
Method Documentation
public XmlDocumentationInfo? GetMethodDocumentation(MethodInfo methodInfo)
{
var memberName = GetMethodDocumentationName(methodInfo);
return GetDocumentation(memberName);
}
Property Documentation
public XmlDocumentationInfo? GetPropertyDocumentation(PropertyInfo propertyInfo)
{
return GetDocumentation($"P:{propertyInfo.DeclaringType?.FullName}.{propertyInfo.Name}");
}
Method Name Generation
Complex Method Signatures
private static string GetMethodDocumentationName(MethodInfo methodInfo)
{
var sb = new StringBuilder();
sb.Append("M:");
sb.Append(methodInfo.DeclaringType?.FullName);
sb.Append('.');
sb.Append(methodInfo.Name);
if (methodInfo.GetParameters().Length > 0)
{
sb.Append('(');
var parameters = methodInfo.GetParameters();
for (var i = 0; i < parameters.Length; i++)
{
if (i > 0) sb.Append(',');
sb.Append(GetTypeName(parameters[i].ParameterType));
}
sb.Append(')');
}
return sb.ToString();
}
Generic Type Support
private static string GetTypeName(Type type)
{
if (!type.IsGenericType)
return type.FullName ?? type.Name;
var genericTypeName = type.GetGenericTypeDefinition().FullName;
var genericArgs = type.GetGenericArguments();
var sb = new StringBuilder();
sb.Append(genericTypeName?[..genericTypeName.IndexOf('`')]);
sb.Append('{');
for (var i = 0; i < genericArgs.Length; i++)
{
if (i > 0) sb.Append(',');
sb.Append(GetTypeName(genericArgs[i]));
}
sb.Append('}');
return sb.ToString();
}
Content Extraction
Element Content Reading
private static async Task<string?> ReadElementContentAsync(XmlReader reader)
{
if (reader.IsEmptyElement)
return null;
var content = new StringBuilder();
while (await reader.ReadAsync())
{
if (reader.NodeType == XmlNodeType.EndElement)
break;
if (reader.NodeType is XmlNodeType.Text or XmlNodeType.CDATA)
{
content.Append(reader.Value);
}
}
var result = content.ToString();
// Preserve internal newlines but trim leading/trailing whitespace
return result.Trim();
}
Supported XML Elements
Core Documentation Elements
- summary: Main description of the member
- remarks: Additional detailed information
- returns: Description of return value
- example: Usage examples
Parameter Documentation
case "param":
var paramName = reader.GetAttribute("name");
if (!string.IsNullOrEmpty(paramName))
{
var paramDoc = await ReadElementContentAsync(reader);
var paramExample = reader.GetAttribute("example");
docInfo.Parameters[paramName] = new XmlDocumentationInfo.ParameterInfo(paramDoc, paramExample);
hasContent = true;
}
break;
Response Documentation
case "response":
var responseCode = reader.GetAttribute("code");
var responseDoc = await ReadElementContentAsync(reader);
if (!string.IsNullOrEmpty(responseCode))
{
docInfo.Responses[responseCode] = responseDoc;
hasContent = true;
}
break;
Error Handling
File Access Errors
if (!File.Exists(xmlFilePath))
{
logger.LogWarning("XML documentation file not found: {FilePath}", xmlFilePath);
return false;
}
XML Parsing Errors
catch (Exception ex)
{
logger.LogError(ex, "Failed to load XML documentation from {FilePath}", xmlFilePath);
return false;
}
Graceful Degradation
The service continues operation even when:
- XML Files Missing: Returns null for documentation queries
- Malformed XML: Skips problematic elements
- Empty Elements: Ignores elements without content
Integration with OpenAPI
Service Registration
services.AddSingleton<IXmlDocumentationService, XmlDocumentationService>();
Usage in Transformers
var methodDoc = xmlDocumentationService.GetMethodDocumentation(methodInfo);
if (methodDoc?.Summary is not null)
{
operation.Summary = methodDoc.Summary;
}
Performance Characteristics
Memory Usage
- Efficient Caching: Documentation cached after first access
- Memory Bounded: Cache size limited by assembly size
- String Interning: Repeated strings optimized
I/O Performance
- Async Operations: Non-blocking file access
- Buffered Reading: 8KB buffer for optimal throughput
- Single Load: Documentation loaded once per application lifecycle
Thread Safety
- Concurrent Access: Thread-safe cache operations
- Read-Heavy Optimization: Optimized for frequent reads
- Lazy Loading: Documentation loaded on demand
Methods
ClearCache()
Clears all cached documentation.
public void ClearCache()
GetDocumentation(string)
Gets the documentation for a specific member by its XML documentation name.
public XmlDocumentationInfo? GetDocumentation(string memberName)
Parameters
memberName
string
The XML documentation member name (e.g., "T:Namespace.Type").
Returns
The documentation information if found; otherwise, null
.
GetMethodDocumentation(MethodInfo)
Gets the documentation for a method.
public XmlDocumentationInfo? GetMethodDocumentation(MethodInfo methodInfo)
Parameters
methodInfo
MethodInfo
The method to get documentation for.
Returns
The documentation information if found; otherwise, null
.
GetPropertyDocumentation(PropertyInfo)
Gets the documentation for a property.
public XmlDocumentationInfo? GetPropertyDocumentation(PropertyInfo propertyInfo)
Parameters
propertyInfo
PropertyInfo
The property to get documentation for.
Returns
The documentation information if found; otherwise, null
.
GetTypeDocumentation(Type)
Gets the documentation for a type.
public XmlDocumentationInfo? GetTypeDocumentation(Type type)
Parameters
type
Type
The type to get documentation for.
Returns
The documentation information if found; otherwise, null
.
LoadDocumentationAsync(string)
Asynchronously loads XML documentation from the specified file.
public Task<bool> LoadDocumentationAsync(string xmlFilePath)
Parameters
xmlFilePath
string
The path to the XML documentation file.
Returns
A task that represents the asynchronous operation. The task result contains true
if the documentation was loaded successfully; otherwise, false
.