Skip to content

Instantly share code, notes, and snippets.

@mikebeaton
Last active January 30, 2019 14:53
Show Gist options
  • Select an option

  • Save mikebeaton/ea9e47109f55fbaec0113756904b900e to your computer and use it in GitHub Desktop.

Select an option

Save mikebeaton/ea9e47109f55fbaec0113756904b900e to your computer and use it in GitHub Desktop.
Stand-alone implementation of Swashbuckle.AspNetCore pull #960 which fixes the formatting of non-string JSON examples. Can be used with existing distributions until the pull makes it into a public release. See https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/1021 for instructions.
using System;
using System.ComponentModel;
using System.Xml.XPath;
using System.Reflection;
using System.Linq;
using Microsoft.OpenApi.Any;
using Newtonsoft.Json.Serialization;
using Microsoft.OpenApi.Models;
using Microsoft.AspNetCore.Mvc;
namespace Swashbuckle.AspNetCore.SwaggerGen
{
internal static class JsonPropertyExtensions
{
internal static bool TryGetMemberInfo(this JsonProperty jsonProperty, out MemberInfo memberInfo)
{
if (jsonProperty.UnderlyingName == null)
{
memberInfo = null;
return false;
}
var metadataAttribute = jsonProperty.DeclaringType.GetTypeInfo()
.GetCustomAttributes(typeof(ModelMetadataTypeAttribute), true)
.FirstOrDefault();
var typeToReflect = (metadataAttribute != null)
? ((ModelMetadataTypeAttribute)metadataAttribute).MetadataType
: jsonProperty.DeclaringType;
memberInfo = typeToReflect.GetMember(jsonProperty.UnderlyingName).FirstOrDefault();
return (memberInfo != null);
}
}
/// <summary>
/// Implementation of <see cref="XmlCommentsSchemaFilter"/> with https://github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/960 applied,
/// for use until the fix is put in a release of Swashbuckle.AspNetCore
/// </summary>
public class XmlCommentsSchemaFilter_Fix960 : ISchemaFilter
{
private const string MemberXPath = "/doc/members/member[@name='{0}']";
private const string SummaryTag = "summary";
private const string ExampleXPath = "example";
private readonly XPathNavigator _xmlNavigator;
/// <summary>
/// Constructor
/// </summary>
/// <param name="xmlDoc"></param>
public XmlCommentsSchemaFilter_Fix960(XPathDocument xmlDoc)
{
_xmlNavigator = xmlDoc.CreateNavigator();
}
/// <summary>
/// Apply filter
/// </summary>
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
var jsonObjectContract = context.JsonContract as JsonObjectContract;
if (jsonObjectContract == null) return;
var memberName = XmlCommentsMemberNameHelper.GetMemberNameForType(context.SystemType);
var typeNode = _xmlNavigator.SelectSingleNode(string.Format(MemberXPath, memberName));
if (typeNode != null)
{
var summaryNode = typeNode.SelectSingleNode(SummaryTag);
if (summaryNode != null)
schema.Description = XmlCommentsTextHelper.Humanize(summaryNode.InnerXml);
}
if (schema.Properties == null) return;
foreach (var entry in schema.Properties)
{
var jsonProperty = jsonObjectContract.Properties[entry.Key];
if (jsonProperty == null) continue;
if (jsonProperty.TryGetMemberInfo(out MemberInfo memberInfo))
{
ApplyPropertyComments(entry.Value, memberInfo);
}
}
}
private void ApplyPropertyComments(OpenApiSchema propertySchema, MemberInfo memberInfo)
{
var memberName = XmlCommentsMemberNameHelper.GetMemberNameForMember(memberInfo);
var memberNode = _xmlNavigator.SelectSingleNode(string.Format(MemberXPath, memberName));
if (memberNode == null) return;
var summaryNode = memberNode.SelectSingleNode(SummaryTag);
if (summaryNode != null)
{
propertySchema.Description = XmlCommentsTextHelper.Humanize(summaryNode.InnerXml);
}
var exampleNode = memberNode.SelectSingleNode(ExampleXPath);
if (exampleNode != null)
{
var exampleString = XmlCommentsTextHelper.Humanize(exampleNode.InnerXml);
var memberType = (memberInfo.MemberType & MemberTypes.Field) != 0 ? ((FieldInfo)memberInfo).FieldType : ((PropertyInfo)memberInfo).PropertyType;
propertySchema.Example = ConvertToOpenApiType(exampleString, memberType);
}
}
private static IOpenApiPrimitive ConvertToOpenApiType(string value, Type type)
{
object typedExample;
try
{
typedExample = TypeDescriptor.GetConverter(type).ConvertFrom(value);
}
catch (Exception)
{
return new OpenApiString(value);
}
return OpenApiPrimitiveFactory.CreateFrom(typedExample) ?? new OpenApiString(value);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment