Imports System
Imports System.Collections.Generic
Imports System.Collections.ObjectModel
Imports System.ComponentModel
Imports System.Diagnostics.CodeAnalysis
Imports System.Globalization
Imports System.IO
Imports System.Linq
Imports System.Net.Http
Imports System.Net.Http.Formatting
Imports System.Net.Http.Headers
Imports System.Runtime.InteropServices
Imports System.Web.Http.Description
Imports System.Xml.Linq
Imports Newtonsoft.Json
Namespace Areas.HelpPage
'''
''' This class will generate the samples for the help page.
'''
Public Class HelpPageSampleGenerator
Private _actualHttpMessageTypes As IDictionary(Of HelpPageSampleKey, Type)
Private _actionSamples As IDictionary(Of HelpPageSampleKey, Object)
Private _sampleObjects As IDictionary(Of Type, Object)
Private _sampleObjectFactories As IList(Of Func(Of HelpPageSampleGenerator, Type, Object))
'''
''' Initializes a new instance of the class.
'''
Public Sub New()
ActualHttpMessageTypes = New Dictionary(Of HelpPageSampleKey, Type)
ActionSamples = New Dictionary(Of HelpPageSampleKey, Object)
SampleObjects = New Dictionary(Of Type, Object)
SampleObjectFactories = New List(Of Func(Of HelpPageSampleGenerator, Type, Object))
SampleObjectFactories.Add(AddressOf DefaultSampleObjectFactory)
End Sub
'''
''' Gets CLR types that are used as the content of or .
'''
Public Property ActualHttpMessageTypes As IDictionary(Of HelpPageSampleKey, Type)
Get
Return _actualHttpMessageTypes
End Get
Friend Set(value As IDictionary(Of HelpPageSampleKey, Type))
_actualHttpMessageTypes = value
End Set
End Property
'''
''' Gets the objects that are used directly as samples for certain actions.
'''
Public Property ActionSamples As IDictionary(Of HelpPageSampleKey, Object)
Get
Return _actionSamples
End Get
Friend Set(value As IDictionary(Of HelpPageSampleKey, Object))
_actionSamples = value
End Set
End Property
'''
''' Gets the objects that are serialized as samples by the supported formatters.
'''
Public Property SampleObjects As IDictionary(Of Type, Object)
Get
Return _sampleObjects
End Get
Friend Set(value As IDictionary(Of Type, Object))
_sampleObjects = value
End Set
End Property
'''
''' Gets factories for the objects that the supported formatters will serialize as samples. Processed in order,
''' stopping when the factory successfully returns a non- object.
'''
'''
''' Collection includes just initially. Use
''' SampleObjectFactories.Insert(0, func)
to provide an override and
''' SampleObjectFactories.Add(func)
to provide a fallback.
Public Property SampleObjectFactories As IList(Of Func(Of HelpPageSampleGenerator, Type, Object))
Get
Return _sampleObjectFactories
End Get
Private Set(value As IList(Of Func(Of HelpPageSampleGenerator, Type, Object)))
_sampleObjectFactories = value
End Set
End Property
'''
''' Gets the request body samples for a given .
'''
''' The .
''' The samples keyed by media type.
Public Function GetSampleRequests(api As ApiDescription) As IDictionary(Of MediaTypeHeaderValue, Object)
Return GetSample(api, SampleDirection.Request)
End Function
'''
''' Gets the response body samples for a given .
'''
''' The .
''' The samples keyed by media type.
Public Function GetSampleResponses(api As ApiDescription) As IDictionary(Of MediaTypeHeaderValue, Object)
Return GetSample(api, SampleDirection.Response)
End Function
'''
''' Gets the request or response body samples.
'''
''' The .
''' The value indicating whether the sample is for a request or for a response.
''' The samples keyed by media type.
Public Overridable Function GetSample(api As ApiDescription, sampleDirection As SampleDirection) As IDictionary(Of MediaTypeHeaderValue, Object)
If (api Is Nothing) Then
Throw New ArgumentNullException("api")
End If
Dim controllerName As String = api.ActionDescriptor.ControllerDescriptor.ControllerName
Dim actionName As String = api.ActionDescriptor.ActionName
Dim parameterNames As IEnumerable(Of String) = api.ParameterDescriptions.Select(Function(p) p.Name)
Dim formatters As New Collection(Of MediaTypeFormatter)
Dim type As Type = ResolveType(api, controllerName, actionName, parameterNames, sampleDirection, formatters)
Dim samples As New Dictionary(Of MediaTypeHeaderValue, Object)
' Use the samples provided directly for actions
Dim actionSamples = GetAllActionSamples(controllerName, actionName, parameterNames, sampleDirection)
For Each actionSample In actionSamples
samples.Add(actionSample.Key.MediaType, WrapSampleIfString(actionSample.Value))
Next
' Do the sample generation based on formatters only if an action doesn't return an HttpResponseMessage.
' Here we cannot rely on formatters because we don't know what's in the HttpResponseMessage, it might not even use formatters.
If (Not type Is Nothing AndAlso Not GetType(HttpResponseMessage).IsAssignableFrom(type)) Then
Dim sampleObject As Object = GetSampleObject(type)
For Each formatter In formatters
For Each mediaType As MediaTypeHeaderValue In formatter.SupportedMediaTypes
If (Not samples.ContainsKey(mediaType)) Then
Dim sample As Object = GetActionSample(controllerName, actionName, parameterNames, type, formatter, mediaType, sampleDirection)
' If no sample found, try generate sample using formatter and sample object
If (sample Is Nothing And Not sampleObject Is Nothing) Then
sample = WriteSampleObjectUsingFormatter(formatter, sampleObject, type, mediaType)
End If
samples.Add(mediaType, WrapSampleIfString(sample))
End If
Next
Next
End If
Return samples
End Function
'''
''' Search for samples that are provided directly through .
'''
''' Name of the controller.
''' Name of the action.
''' The parameter names.
''' The CLR type.
''' The formatter.
''' The media type.
''' The value indicating whether the sample is for a request or for a response.
''' The sample that matches the parameters.
Public Overridable Function GetActionSample(controllerName As String, actionName As String, parameterNames As IEnumerable(Of String), type As Type, formatter As MediaTypeFormatter, mediaType As MediaTypeHeaderValue, sampleDirection As SampleDirection) As Object
Dim sample As New Object
' First, try to get the sample provided for the specified mediaType, sampleDirection, controllerName, actionName and parameterNames.
' If not found, try to get the sample provided for the specified mediaType, sampleDirection, controllerName and actionName regardless of the parameterNames.
' If still not found, try to get the sample provided for the specified mediaType and type.
' Finally, try to get the sample provided for the specified mediaType.
If (ActionSamples.TryGetValue(New HelpPageSampleKey(mediaType, sampleDirection, controllerName, actionName, parameterNames), sample) OrElse
ActionSamples.TryGetValue(New HelpPageSampleKey(mediaType, sampleDirection, controllerName, actionName, New String() {"*"}), sample) OrElse
ActionSamples.TryGetValue(New HelpPageSampleKey(mediaType, type), sample) OrElse
ActionSamples.TryGetValue(New HelpPageSampleKey(mediaType), sample)) Then
Return sample
End If
Return Nothing
End Function
'''
''' Gets the sample object that will be serialized by the formatters.
''' First, it will look at the . If no sample object is found, it will try to create
''' one using (which wraps an ) and other
''' factories in .
'''
''' The type.
''' The sample object.
Public Overridable Function GetSampleObject(type As Type) As Object
Dim sampleObject As New Object
If (Not SampleObjects.TryGetValue(type, sampleObject)) Then
' No specific object available, try our factories.
For Each factory As Func(Of HelpPageSampleGenerator, Type, Object) In SampleObjectFactories
If factory Is Nothing Then
Continue For
End If
Try
sampleObject = factory(Me, type)
If sampleObject IsNot Nothing Then
Exit For
End If
Catch
' Ignore any problems encountered in the factory; go on to the next one (if any).
End Try
Next
End If
Return sampleObject
End Function
'''
''' Resolves the actual type of passed to the in an action.
'''
''' The .
''' The type.
Public Overridable Function ResolveHttpRequestMessageType(api As ApiDescription) As Type
Dim controllerName As String = api.ActionDescriptor.ControllerDescriptor.ControllerName
Dim actionName As String = api.ActionDescriptor.ActionName
Dim parameterNames As IEnumerable(Of String) = api.ParameterDescriptions.[Select](Function(p) p.Name)
Dim formatters As Collection(Of MediaTypeFormatter) = Nothing
Return ResolveType(api, controllerName, actionName, parameterNames, SampleDirection.Request, formatters)
End Function
'''
''' Resolves the type of the action parameter or return value when or is used.
'''
''' The .
''' Name of the controller.
''' Name of the action.
''' The parameter names.
''' The value indicating whether the sample is for a request or a response.
''' The formatters.
Public Overridable Function ResolveType(api As ApiDescription, controllerName As String, actionName As String, parameterNames As IEnumerable(Of String), sampleDirection As SampleDirection, ByRef formatters As Collection(Of MediaTypeFormatter)) As Type
If (Not [Enum].IsDefined(GetType(SampleDirection), sampleDirection)) Then
Throw New InvalidEnumArgumentException("sampleDirection", CInt(sampleDirection), GetType(SampleDirection))
End If
If (api Is Nothing) Then
Throw New ArgumentNullException("api")
End If
Dim type As Type = GetType(Object)
If (ActualHttpMessageTypes.TryGetValue(New HelpPageSampleKey(sampleDirection, controllerName, actionName, parameterNames), type) OrElse
ActualHttpMessageTypes.TryGetValue(New HelpPageSampleKey(sampleDirection, controllerName, actionName, New String() {"*"}), type)) Then
' Re-compute the supported formatters based on type
Dim newFormatters As New Collection(Of MediaTypeFormatter)
For Each formatter In api.ActionDescriptor.Configuration.Formatters
If (IsFormatSupported(sampleDirection, formatter, type)) Then
newFormatters.Add(formatter)
End If
Next
formatters = newFormatters
Else
Select Case sampleDirection
Case sampleDirection.Request
Dim requestBodyParameter As ApiParameterDescription = api.ParameterDescriptions.FirstOrDefault(Function(p) p.Source = ApiParameterSource.FromBody)
type = If(requestBodyParameter Is Nothing, Nothing, requestBodyParameter.ParameterDescriptor.ParameterType)
formatters = api.SupportedRequestBodyFormatters
Case Else
'Case sampleDirection.Response
type = If(api.ResponseDescription.ResponseType, api.ResponseDescription.DeclaredType)
formatters = api.SupportedResponseFormatters
End Select
End If
Return type
End Function
'''
''' Writes the sample object using formatter.
'''
''' The formatter.
''' The value.
''' The type.
''' Type of the media.
'''
Public Overridable Function WriteSampleObjectUsingFormatter(formatter As MediaTypeFormatter, value As Object, type As Type, mediaType As MediaTypeHeaderValue) As Object
If (formatter Is Nothing) Then
Throw New ArgumentNullException("formatter")
End If
If (mediaType Is Nothing) Then
Throw New ArgumentNullException("mediaType")
End If
Dim sample As Object = String.Empty
Dim MS As MemoryStream = Nothing
Dim content As HttpContent = Nothing
Try
If (formatter.CanWriteType(type)) Then
MS = New MemoryStream()
content = New ObjectContent(type, value, formatter, mediaType)
formatter.WriteToStreamAsync(type, value, MS, content, Nothing).Wait()
MS.Position = 0
Dim reader As New StreamReader(MS)
Dim serializedSampleString As String = reader.ReadToEnd()
If (mediaType.MediaType.ToUpperInvariant().Contains("XML")) Then
serializedSampleString = TryFormatXml(serializedSampleString)
ElseIf (mediaType.MediaType.ToUpperInvariant().Contains("JSON")) Then
serializedSampleString = TryFormatJson(serializedSampleString)
End If
sample = New TextSample(serializedSampleString)
Else
sample = New InvalidSample(String.Format(
CultureInfo.CurrentCulture,
"Failed to generate the sample for media type '{0}'. Cannot use formatter '{1}' to write type '{2}'.",
mediaType,
formatter.GetType().Name,
type.Name))
End If
Catch e As Exception
sample = New InvalidSample(String.Format(
CultureInfo.CurrentCulture,
"An exception has occurred while using the formatter '{0}' to generate sample for media type '{1}'. Exception message: {2}",
formatter.GetType().Name,
mediaType.MediaType,
UnwrapException(e).Message))
Finally
If (Not MS Is Nothing) Then
MS.Dispose()
End If
If (Not content Is Nothing) Then
content.Dispose()
End If
End Try
Return sample
End Function
Friend Shared Function UnwrapException(exception As Exception) As Exception
Dim aggregateException As AggregateException = TryCast(exception, AggregateException)
If aggregateException IsNot Nothing Then
Return aggregateException.Flatten().InnerException
End If
Return exception
End Function
Private Shared Function DefaultSampleObjectFactory(sampleGenerator As HelpPageSampleGenerator, type As Type) As Object
' Try create a default sample object
Dim objectGenerator As New ObjectGenerator()
Return objectGenerator.GenerateObject(type)
End Function
Private Shared Function TryFormatJson(str As String) As String
Try
Dim parsedJson As Object = JsonConvert.DeserializeObject(str)
Return JsonConvert.SerializeObject(parsedJson, Formatting.Indented)
Catch
' can't parse JSON, return the original string
Return str
End Try
End Function
Private Shared Function TryFormatXml(str As String) As String
Try
Dim Xml As XDocument = XDocument.Parse(str)
Return Xml.ToString()
Catch
' can't parse XML, return the original string
Return str
End Try
End Function
Private Shared Function IsFormatSupported(sampleDirection As SampleDirection, formatter As MediaTypeFormatter, type As Type) As Boolean
Select Case sampleDirection
Case sampleDirection.Request
Return formatter.CanReadType(type)
Case sampleDirection.Response
Return formatter.CanWriteType(type)
End Select
Return False
End Function
Private Iterator Function GetAllActionSamples(controllerName As String, actionName As String, parameterNames As IEnumerable(Of String), sampleDirection As SampleDirection) As IEnumerable(Of KeyValuePair(Of HelpPageSampleKey, Object))
Dim parameterNamesSet As New HashSet(Of String)(parameterNames, StringComparer.OrdinalIgnoreCase)
For Each sample In ActionSamples
Dim sampleKey As HelpPageSampleKey = sample.Key
If (String.Equals(controllerName, sampleKey.ControllerName, StringComparison.OrdinalIgnoreCase) And
String.Equals(actionName, sampleKey.ActionName, StringComparison.OrdinalIgnoreCase) And
(sampleKey.ParameterNames.SetEquals(New String() {"*"}) Or parameterNamesSet.SetEquals(sampleKey.ParameterNames)) And
sampleDirection = sampleKey.SampleDirection) Then
Yield sample
End If
Next
End Function
Private Shared Function WrapSampleIfString(sample As Object) As Object
Dim stringSample As String = TryCast(sample, String)
If (Not stringSample Is Nothing) Then
Return New TextSample(stringSample)
End If
Return sample
End Function
End Class
End Namespace