对比新文件 |
| | |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.Collections.ObjectModel; |
| | | using System.ComponentModel; |
| | | using System.Diagnostics; |
| | | using System.Diagnostics.CodeAnalysis; |
| | | using System.Globalization; |
| | | using System.Linq; |
| | | using System.Net.Http; |
| | | using System.Net.Http.Headers; |
| | | using System.Web.Http; |
| | | using System.Web.Http.Controllers; |
| | | using System.Web.Http.Description; |
| | | using IStation.WebApi.Areas.HelpPage.ModelDescriptions; |
| | | using IStation.WebApi.Areas.HelpPage.Models; |
| | | |
| | | namespace IStation.WebApi.Areas.HelpPage |
| | | { |
| | | public static class HelpPageConfigurationExtensions |
| | | { |
| | | private const string ApiModelPrefix = "MS_HelpPageApiModel_"; |
| | | |
| | | /// <summary> |
| | | /// Sets the documentation provider for help page. |
| | | /// </summary> |
| | | /// <param name="config">The <see cref="HttpConfiguration"/>.</param> |
| | | /// <param name="documentationProvider">The documentation provider.</param> |
| | | public static void SetDocumentationProvider(this HttpConfiguration config, IDocumentationProvider documentationProvider) |
| | | { |
| | | config.Services.Replace(typeof(IDocumentationProvider), documentationProvider); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Sets the objects that will be used by the formatters to produce sample requests/responses. |
| | | /// </summary> |
| | | /// <param name="config">The <see cref="HttpConfiguration"/>.</param> |
| | | /// <param name="sampleObjects">The sample objects.</param> |
| | | public static void SetSampleObjects(this HttpConfiguration config, IDictionary<Type, object> sampleObjects) |
| | | { |
| | | config.GetHelpPageSampleGenerator().SampleObjects = sampleObjects; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Sets the sample request directly for the specified media type and action. |
| | | /// </summary> |
| | | /// <param name="config">The <see cref="HttpConfiguration"/>.</param> |
| | | /// <param name="sample">The sample request.</param> |
| | | /// <param name="mediaType">The media type.</param> |
| | | /// <param name="controllerName">Name of the controller.</param> |
| | | /// <param name="actionName">Name of the action.</param> |
| | | public static void SetSampleRequest(this HttpConfiguration config, object sample, MediaTypeHeaderValue mediaType, string controllerName, string actionName) |
| | | { |
| | | config.GetHelpPageSampleGenerator().ActionSamples.Add(new HelpPageSampleKey(mediaType, SampleDirection.Request, controllerName, actionName, new[] { "*" }), sample); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Sets the sample request directly for the specified media type and action with parameters. |
| | | /// </summary> |
| | | /// <param name="config">The <see cref="HttpConfiguration"/>.</param> |
| | | /// <param name="sample">The sample request.</param> |
| | | /// <param name="mediaType">The media type.</param> |
| | | /// <param name="controllerName">Name of the controller.</param> |
| | | /// <param name="actionName">Name of the action.</param> |
| | | /// <param name="parameterNames">The parameter names.</param> |
| | | public static void SetSampleRequest(this HttpConfiguration config, object sample, MediaTypeHeaderValue mediaType, string controllerName, string actionName, params string[] parameterNames) |
| | | { |
| | | config.GetHelpPageSampleGenerator().ActionSamples.Add(new HelpPageSampleKey(mediaType, SampleDirection.Request, controllerName, actionName, parameterNames), sample); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Sets the sample request directly for the specified media type of the action. |
| | | /// </summary> |
| | | /// <param name="config">The <see cref="HttpConfiguration"/>.</param> |
| | | /// <param name="sample">The sample response.</param> |
| | | /// <param name="mediaType">The media type.</param> |
| | | /// <param name="controllerName">Name of the controller.</param> |
| | | /// <param name="actionName">Name of the action.</param> |
| | | public static void SetSampleResponse(this HttpConfiguration config, object sample, MediaTypeHeaderValue mediaType, string controllerName, string actionName) |
| | | { |
| | | config.GetHelpPageSampleGenerator().ActionSamples.Add(new HelpPageSampleKey(mediaType, SampleDirection.Response, controllerName, actionName, new[] { "*" }), sample); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Sets the sample response directly for the specified media type of the action with specific parameters. |
| | | /// </summary> |
| | | /// <param name="config">The <see cref="HttpConfiguration"/>.</param> |
| | | /// <param name="sample">The sample response.</param> |
| | | /// <param name="mediaType">The media type.</param> |
| | | /// <param name="controllerName">Name of the controller.</param> |
| | | /// <param name="actionName">Name of the action.</param> |
| | | /// <param name="parameterNames">The parameter names.</param> |
| | | public static void SetSampleResponse(this HttpConfiguration config, object sample, MediaTypeHeaderValue mediaType, string controllerName, string actionName, params string[] parameterNames) |
| | | { |
| | | config.GetHelpPageSampleGenerator().ActionSamples.Add(new HelpPageSampleKey(mediaType, SampleDirection.Response, controllerName, actionName, parameterNames), sample); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Sets the sample directly for all actions with the specified media type. |
| | | /// </summary> |
| | | /// <param name="config">The <see cref="HttpConfiguration"/>.</param> |
| | | /// <param name="sample">The sample.</param> |
| | | /// <param name="mediaType">The media type.</param> |
| | | public static void SetSampleForMediaType(this HttpConfiguration config, object sample, MediaTypeHeaderValue mediaType) |
| | | { |
| | | config.GetHelpPageSampleGenerator().ActionSamples.Add(new HelpPageSampleKey(mediaType), sample); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Sets the sample directly for all actions with the specified type and media type. |
| | | /// </summary> |
| | | /// <param name="config">The <see cref="HttpConfiguration"/>.</param> |
| | | /// <param name="sample">The sample.</param> |
| | | /// <param name="mediaType">The media type.</param> |
| | | /// <param name="type">The parameter type or return type of an action.</param> |
| | | public static void SetSampleForType(this HttpConfiguration config, object sample, MediaTypeHeaderValue mediaType, Type type) |
| | | { |
| | | config.GetHelpPageSampleGenerator().ActionSamples.Add(new HelpPageSampleKey(mediaType, type), sample); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Specifies the actual type of <see cref="System.Net.Http.ObjectContent{T}"/> passed to the <see cref="System.Net.Http.HttpRequestMessage"/> in an action. |
| | | /// The help page will use this information to produce more accurate request samples. |
| | | /// </summary> |
| | | /// <param name="config">The <see cref="HttpConfiguration"/>.</param> |
| | | /// <param name="type">The type.</param> |
| | | /// <param name="controllerName">Name of the controller.</param> |
| | | /// <param name="actionName">Name of the action.</param> |
| | | public static void SetActualRequestType(this HttpConfiguration config, Type type, string controllerName, string actionName) |
| | | { |
| | | config.GetHelpPageSampleGenerator().ActualHttpMessageTypes.Add(new HelpPageSampleKey(SampleDirection.Request, controllerName, actionName, new[] { "*" }), type); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Specifies the actual type of <see cref="System.Net.Http.ObjectContent{T}"/> passed to the <see cref="System.Net.Http.HttpRequestMessage"/> in an action. |
| | | /// The help page will use this information to produce more accurate request samples. |
| | | /// </summary> |
| | | /// <param name="config">The <see cref="HttpConfiguration"/>.</param> |
| | | /// <param name="type">The type.</param> |
| | | /// <param name="controllerName">Name of the controller.</param> |
| | | /// <param name="actionName">Name of the action.</param> |
| | | /// <param name="parameterNames">The parameter names.</param> |
| | | public static void SetActualRequestType(this HttpConfiguration config, Type type, string controllerName, string actionName, params string[] parameterNames) |
| | | { |
| | | config.GetHelpPageSampleGenerator().ActualHttpMessageTypes.Add(new HelpPageSampleKey(SampleDirection.Request, controllerName, actionName, parameterNames), type); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Specifies the actual type of <see cref="System.Net.Http.ObjectContent{T}"/> returned as part of the <see cref="System.Net.Http.HttpRequestMessage"/> in an action. |
| | | /// The help page will use this information to produce more accurate response samples. |
| | | /// </summary> |
| | | /// <param name="config">The <see cref="HttpConfiguration"/>.</param> |
| | | /// <param name="type">The type.</param> |
| | | /// <param name="controllerName">Name of the controller.</param> |
| | | /// <param name="actionName">Name of the action.</param> |
| | | public static void SetActualResponseType(this HttpConfiguration config, Type type, string controllerName, string actionName) |
| | | { |
| | | config.GetHelpPageSampleGenerator().ActualHttpMessageTypes.Add(new HelpPageSampleKey(SampleDirection.Response, controllerName, actionName, new[] { "*" }), type); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Specifies the actual type of <see cref="System.Net.Http.ObjectContent{T}"/> returned as part of the <see cref="System.Net.Http.HttpRequestMessage"/> in an action. |
| | | /// The help page will use this information to produce more accurate response samples. |
| | | /// </summary> |
| | | /// <param name="config">The <see cref="HttpConfiguration"/>.</param> |
| | | /// <param name="type">The type.</param> |
| | | /// <param name="controllerName">Name of the controller.</param> |
| | | /// <param name="actionName">Name of the action.</param> |
| | | /// <param name="parameterNames">The parameter names.</param> |
| | | public static void SetActualResponseType(this HttpConfiguration config, Type type, string controllerName, string actionName, params string[] parameterNames) |
| | | { |
| | | config.GetHelpPageSampleGenerator().ActualHttpMessageTypes.Add(new HelpPageSampleKey(SampleDirection.Response, controllerName, actionName, parameterNames), type); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Gets the help page sample generator. |
| | | /// </summary> |
| | | /// <param name="config">The <see cref="HttpConfiguration"/>.</param> |
| | | /// <returns>The help page sample generator.</returns> |
| | | public static HelpPageSampleGenerator GetHelpPageSampleGenerator(this HttpConfiguration config) |
| | | { |
| | | return (HelpPageSampleGenerator)config.Properties.GetOrAdd( |
| | | typeof(HelpPageSampleGenerator), |
| | | k => new HelpPageSampleGenerator()); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Sets the help page sample generator. |
| | | /// </summary> |
| | | /// <param name="config">The <see cref="HttpConfiguration"/>.</param> |
| | | /// <param name="sampleGenerator">The help page sample generator.</param> |
| | | public static void SetHelpPageSampleGenerator(this HttpConfiguration config, HelpPageSampleGenerator sampleGenerator) |
| | | { |
| | | config.Properties.AddOrUpdate( |
| | | typeof(HelpPageSampleGenerator), |
| | | k => sampleGenerator, |
| | | (k, o) => sampleGenerator); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Gets the model description generator. |
| | | /// </summary> |
| | | /// <param name="config">The configuration.</param> |
| | | /// <returns>The <see cref="ModelDescriptionGenerator"/></returns> |
| | | public static ModelDescriptionGenerator GetModelDescriptionGenerator(this HttpConfiguration config) |
| | | { |
| | | return (ModelDescriptionGenerator)config.Properties.GetOrAdd( |
| | | typeof(ModelDescriptionGenerator), |
| | | k => InitializeModelDescriptionGenerator(config)); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Gets the model that represents an API displayed on the help page. The model is initialized on the first call and cached for subsequent calls. |
| | | /// </summary> |
| | | /// <param name="config">The <see cref="HttpConfiguration"/>.</param> |
| | | /// <param name="apiDescriptionId">The <see cref="ApiDescription"/> ID.</param> |
| | | /// <returns> |
| | | /// An <see cref="HelpPageApiModel"/> |
| | | /// </returns> |
| | | public static HelpPageApiModel GetHelpPageApiModel(this HttpConfiguration config, string apiDescriptionId) |
| | | { |
| | | object model; |
| | | string modelId = ApiModelPrefix + apiDescriptionId; |
| | | if (!config.Properties.TryGetValue(modelId, out model)) |
| | | { |
| | | Collection<ApiDescription> apiDescriptions = config.Services.GetApiExplorer().ApiDescriptions; |
| | | ApiDescription apiDescription = apiDescriptions.FirstOrDefault(api => String.Equals(api.GetFriendlyId(), apiDescriptionId, StringComparison.OrdinalIgnoreCase)); |
| | | if (apiDescription != null) |
| | | { |
| | | model = GenerateApiModel(apiDescription, config); |
| | | config.Properties.TryAdd(modelId, model); |
| | | } |
| | | } |
| | | |
| | | return (HelpPageApiModel)model; |
| | | } |
| | | |
| | | private static HelpPageApiModel GenerateApiModel(ApiDescription apiDescription, HttpConfiguration config) |
| | | { |
| | | HelpPageApiModel apiModel = new HelpPageApiModel() |
| | | { |
| | | ApiDescription = apiDescription, |
| | | }; |
| | | |
| | | ModelDescriptionGenerator modelGenerator = config.GetModelDescriptionGenerator(); |
| | | HelpPageSampleGenerator sampleGenerator = config.GetHelpPageSampleGenerator(); |
| | | GenerateUriParameters(apiModel, modelGenerator); |
| | | GenerateRequestModelDescription(apiModel, modelGenerator, sampleGenerator); |
| | | GenerateResourceDescription(apiModel, modelGenerator); |
| | | GenerateSamples(apiModel, sampleGenerator); |
| | | |
| | | return apiModel; |
| | | } |
| | | |
| | | private static void GenerateUriParameters(HelpPageApiModel apiModel, ModelDescriptionGenerator modelGenerator) |
| | | { |
| | | ApiDescription apiDescription = apiModel.ApiDescription; |
| | | foreach (ApiParameterDescription apiParameter in apiDescription.ParameterDescriptions) |
| | | { |
| | | if (apiParameter.Source == ApiParameterSource.FromUri) |
| | | { |
| | | HttpParameterDescriptor parameterDescriptor = apiParameter.ParameterDescriptor; |
| | | Type parameterType = null; |
| | | ModelDescription typeDescription = null; |
| | | ComplexTypeModelDescription complexTypeDescription = null; |
| | | if (parameterDescriptor != null) |
| | | { |
| | | parameterType = parameterDescriptor.ParameterType; |
| | | typeDescription = modelGenerator.GetOrCreateModelDescription(parameterType); |
| | | complexTypeDescription = typeDescription as ComplexTypeModelDescription; |
| | | } |
| | | |
| | | // Example: |
| | | // [TypeConverter(typeof(PointConverter))] |
| | | // public class Point |
| | | // { |
| | | // public Point(int x, int y) |
| | | // { |
| | | // X = x; |
| | | // Y = y; |
| | | // } |
| | | // public int X { get; set; } |
| | | // public int Y { get; set; } |
| | | // } |
| | | // Class Point is bindable with a TypeConverter, so Point will be added to UriParameters collection. |
| | | // |
| | | // public class Point |
| | | // { |
| | | // public int X { get; set; } |
| | | // public int Y { get; set; } |
| | | // } |
| | | // Regular complex class Point will have properties X and Y added to UriParameters collection. |
| | | if (complexTypeDescription != null |
| | | && !IsBindableWithTypeConverter(parameterType)) |
| | | { |
| | | foreach (ParameterDescription uriParameter in complexTypeDescription.Properties) |
| | | { |
| | | apiModel.UriParameters.Add(uriParameter); |
| | | } |
| | | } |
| | | else if (parameterDescriptor != null) |
| | | { |
| | | ParameterDescription uriParameter = |
| | | AddParameterDescription(apiModel, apiParameter, typeDescription); |
| | | |
| | | if (!parameterDescriptor.IsOptional) |
| | | { |
| | | uriParameter.Annotations.Add(new ParameterAnnotation() { Documentation = "Required" }); |
| | | } |
| | | |
| | | object defaultValue = parameterDescriptor.DefaultValue; |
| | | if (defaultValue != null) |
| | | { |
| | | uriParameter.Annotations.Add(new ParameterAnnotation() { Documentation = "Default value is " + Convert.ToString(defaultValue, CultureInfo.InvariantCulture) }); |
| | | } |
| | | } |
| | | else |
| | | { |
| | | Debug.Assert(parameterDescriptor == null); |
| | | |
| | | // If parameterDescriptor is null, this is an undeclared route parameter which only occurs |
| | | // when source is FromUri. Ignored in request model and among resource parameters but listed |
| | | // as a simple string here. |
| | | ModelDescription modelDescription = modelGenerator.GetOrCreateModelDescription(typeof(string)); |
| | | AddParameterDescription(apiModel, apiParameter, modelDescription); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | private static bool IsBindableWithTypeConverter(Type parameterType) |
| | | { |
| | | if (parameterType == null) |
| | | { |
| | | return false; |
| | | } |
| | | |
| | | return TypeDescriptor.GetConverter(parameterType).CanConvertFrom(typeof(string)); |
| | | } |
| | | |
| | | private static ParameterDescription AddParameterDescription(HelpPageApiModel apiModel, |
| | | ApiParameterDescription apiParameter, ModelDescription typeDescription) |
| | | { |
| | | ParameterDescription parameterDescription = new ParameterDescription |
| | | { |
| | | Name = apiParameter.Name, |
| | | Documentation = apiParameter.Documentation, |
| | | TypeDescription = typeDescription, |
| | | }; |
| | | |
| | | apiModel.UriParameters.Add(parameterDescription); |
| | | return parameterDescription; |
| | | } |
| | | |
| | | private static void GenerateRequestModelDescription(HelpPageApiModel apiModel, ModelDescriptionGenerator modelGenerator, HelpPageSampleGenerator sampleGenerator) |
| | | { |
| | | ApiDescription apiDescription = apiModel.ApiDescription; |
| | | foreach (ApiParameterDescription apiParameter in apiDescription.ParameterDescriptions) |
| | | { |
| | | if (apiParameter.Source == ApiParameterSource.FromBody) |
| | | { |
| | | Type parameterType = apiParameter.ParameterDescriptor.ParameterType; |
| | | apiModel.RequestModelDescription = modelGenerator.GetOrCreateModelDescription(parameterType); |
| | | apiModel.RequestDocumentation = apiParameter.Documentation; |
| | | } |
| | | else if (apiParameter.ParameterDescriptor != null && |
| | | apiParameter.ParameterDescriptor.ParameterType == typeof(HttpRequestMessage)) |
| | | { |
| | | Type parameterType = sampleGenerator.ResolveHttpRequestMessageType(apiDescription); |
| | | |
| | | if (parameterType != null) |
| | | { |
| | | apiModel.RequestModelDescription = modelGenerator.GetOrCreateModelDescription(parameterType); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | private static void GenerateResourceDescription(HelpPageApiModel apiModel, ModelDescriptionGenerator modelGenerator) |
| | | { |
| | | ResponseDescription response = apiModel.ApiDescription.ResponseDescription; |
| | | Type responseType = response.ResponseType ?? response.DeclaredType; |
| | | if (responseType != null && responseType != typeof(void)) |
| | | { |
| | | apiModel.ResourceDescription = modelGenerator.GetOrCreateModelDescription(responseType); |
| | | } |
| | | } |
| | | |
| | | [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The exception is recorded as ErrorMessages.")] |
| | | private static void GenerateSamples(HelpPageApiModel apiModel, HelpPageSampleGenerator sampleGenerator) |
| | | { |
| | | try |
| | | { |
| | | foreach (var item in sampleGenerator.GetSampleRequests(apiModel.ApiDescription)) |
| | | { |
| | | apiModel.SampleRequests.Add(item.Key, item.Value); |
| | | LogInvalidSampleAsError(apiModel, item.Value); |
| | | } |
| | | |
| | | foreach (var item in sampleGenerator.GetSampleResponses(apiModel.ApiDescription)) |
| | | { |
| | | apiModel.SampleResponses.Add(item.Key, item.Value); |
| | | LogInvalidSampleAsError(apiModel, item.Value); |
| | | } |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | apiModel.ErrorMessages.Add(String.Format(CultureInfo.CurrentCulture, |
| | | "An exception has occurred while generating the sample. Exception message: {0}", |
| | | HelpPageSampleGenerator.UnwrapException(e).Message)); |
| | | } |
| | | } |
| | | |
| | | private static bool TryGetResourceParameter(ApiDescription apiDescription, HttpConfiguration config, out ApiParameterDescription parameterDescription, out Type resourceType) |
| | | { |
| | | parameterDescription = apiDescription.ParameterDescriptions.FirstOrDefault( |
| | | p => p.Source == ApiParameterSource.FromBody || |
| | | (p.ParameterDescriptor != null && p.ParameterDescriptor.ParameterType == typeof(HttpRequestMessage))); |
| | | |
| | | if (parameterDescription == null) |
| | | { |
| | | resourceType = null; |
| | | return false; |
| | | } |
| | | |
| | | resourceType = parameterDescription.ParameterDescriptor.ParameterType; |
| | | |
| | | if (resourceType == typeof(HttpRequestMessage)) |
| | | { |
| | | HelpPageSampleGenerator sampleGenerator = config.GetHelpPageSampleGenerator(); |
| | | resourceType = sampleGenerator.ResolveHttpRequestMessageType(apiDescription); |
| | | } |
| | | |
| | | if (resourceType == null) |
| | | { |
| | | parameterDescription = null; |
| | | return false; |
| | | } |
| | | |
| | | return true; |
| | | } |
| | | |
| | | private static ModelDescriptionGenerator InitializeModelDescriptionGenerator(HttpConfiguration config) |
| | | { |
| | | ModelDescriptionGenerator modelGenerator = new ModelDescriptionGenerator(config); |
| | | Collection<ApiDescription> apis = config.Services.GetApiExplorer().ApiDescriptions; |
| | | foreach (ApiDescription api in apis) |
| | | { |
| | | ApiParameterDescription parameterDescription; |
| | | Type parameterType; |
| | | if (TryGetResourceParameter(api, config, out parameterDescription, out parameterType)) |
| | | { |
| | | modelGenerator.GetOrCreateModelDescription(parameterType); |
| | | } |
| | | } |
| | | return modelGenerator; |
| | | } |
| | | |
| | | private static void LogInvalidSampleAsError(HelpPageApiModel apiModel, object sample) |
| | | { |
| | | InvalidSample invalidSample = sample as InvalidSample; |
| | | if (invalidSample != null) |
| | | { |
| | | apiModel.ErrorMessages.Add(invalidSample.ErrorMessage); |
| | | } |
| | | } |
| | | } |
| | | } |