From 03a0d99de9c5fed6bea8bc83b49ce27786bda38c Mon Sep 17 00:00:00 2001 From: tangxu <tangxu76880903> Date: 星期五, 07 二月 2025 09:29:59 +0800 Subject: [PATCH] 添加OPENAPI接口 --- WebApi/Areas/HelpPage/ModelDescriptions/ModelDescriptionGenerator.cs | 451 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 451 insertions(+), 0 deletions(-) diff --git a/WebApi/Areas/HelpPage/ModelDescriptions/ModelDescriptionGenerator.cs b/WebApi/Areas/HelpPage/ModelDescriptions/ModelDescriptionGenerator.cs new file mode 100644 index 0000000..3ac5ba2 --- /dev/null +++ b/WebApi/Areas/HelpPage/ModelDescriptions/ModelDescriptionGenerator.cs @@ -0,0 +1,451 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel.DataAnnotations; +using System.Globalization; +using System.Reflection; +using System.Runtime.Serialization; +using System.Web.Http; +using System.Web.Http.Description; +using System.Xml.Serialization; +using Newtonsoft.Json; + +namespace IStation.WebApi.Areas.HelpPage.ModelDescriptions +{ + /// <summary> + /// Generates model descriptions for given types. + /// </summary> + public class ModelDescriptionGenerator + { + // Modify this to support more data annotation attributes. + private readonly IDictionary<Type, Func<object, string>> AnnotationTextGenerator = new Dictionary<Type, Func<object, string>> + { + { typeof(RequiredAttribute), a => "Required" }, + { typeof(RangeAttribute), a => + { + RangeAttribute range = (RangeAttribute)a; + return String.Format(CultureInfo.CurrentCulture, "Range: inclusive between {0} and {1}", range.Minimum, range.Maximum); + } + }, + { typeof(MaxLengthAttribute), a => + { + MaxLengthAttribute maxLength = (MaxLengthAttribute)a; + return String.Format(CultureInfo.CurrentCulture, "Max length: {0}", maxLength.Length); + } + }, + { typeof(MinLengthAttribute), a => + { + MinLengthAttribute minLength = (MinLengthAttribute)a; + return String.Format(CultureInfo.CurrentCulture, "Min length: {0}", minLength.Length); + } + }, + { typeof(StringLengthAttribute), a => + { + StringLengthAttribute strLength = (StringLengthAttribute)a; + return String.Format(CultureInfo.CurrentCulture, "String length: inclusive between {0} and {1}", strLength.MinimumLength, strLength.MaximumLength); + } + }, + { typeof(DataTypeAttribute), a => + { + DataTypeAttribute dataType = (DataTypeAttribute)a; + return String.Format(CultureInfo.CurrentCulture, "Data type: {0}", dataType.CustomDataType ?? dataType.DataType.ToString()); + } + }, + { typeof(RegularExpressionAttribute), a => + { + RegularExpressionAttribute regularExpression = (RegularExpressionAttribute)a; + return String.Format(CultureInfo.CurrentCulture, "Matching regular expression pattern: {0}", regularExpression.Pattern); + } + }, + }; + + // Modify this to add more default documentations. + private readonly IDictionary<Type, string> DefaultTypeDocumentation = new Dictionary<Type, string> + { + { typeof(Int16), "integer" }, + { typeof(Int32), "integer" }, + { typeof(Int64), "integer" }, + { typeof(UInt16), "unsigned integer" }, + { typeof(UInt32), "unsigned integer" }, + { typeof(UInt64), "unsigned integer" }, + { typeof(Byte), "byte" }, + { typeof(Char), "character" }, + { typeof(SByte), "signed byte" }, + { typeof(Uri), "URI" }, + { typeof(Single), "decimal number" }, + { typeof(Double), "decimal number" }, + { typeof(Decimal), "decimal number" }, + { typeof(String), "string" }, + { typeof(Guid), "globally unique identifier" }, + { typeof(TimeSpan), "time interval" }, + { typeof(DateTime), "date" }, + { typeof(DateTimeOffset), "date" }, + { typeof(Boolean), "boolean" }, + }; + + private Lazy<IModelDocumentationProvider> _documentationProvider; + + public ModelDescriptionGenerator(HttpConfiguration config) + { + if (config == null) + { + throw new ArgumentNullException("config"); + } + + _documentationProvider = new Lazy<IModelDocumentationProvider>(() => config.Services.GetDocumentationProvider() as IModelDocumentationProvider); + GeneratedModels = new Dictionary<string, ModelDescription>(StringComparer.OrdinalIgnoreCase); + } + + public Dictionary<string, ModelDescription> GeneratedModels { get; private set; } + + private IModelDocumentationProvider DocumentationProvider + { + get + { + return _documentationProvider.Value; + } + } + + public ModelDescription GetOrCreateModelDescription(Type modelType) + { + if (modelType == null) + { + throw new ArgumentNullException("modelType"); + } + + Type underlyingType = Nullable.GetUnderlyingType(modelType); + if (underlyingType != null) + { + modelType = underlyingType; + } + + ModelDescription modelDescription; + string modelName = ModelNameHelper.GetModelName(modelType); + if (GeneratedModels.TryGetValue(modelName, out modelDescription)) + { + if (modelType != modelDescription.ModelType) + { + throw new InvalidOperationException( + String.Format( + CultureInfo.CurrentCulture, + "A model description could not be created. Duplicate model name '{0}' was found for types '{1}' and '{2}'. " + + "Use the [ModelName] attribute to change the model name for at least one of the types so that it has a unique name.", + modelName, + modelDescription.ModelType.FullName, + modelType.FullName)); + } + + return modelDescription; + } + + if (DefaultTypeDocumentation.ContainsKey(modelType)) + { + return GenerateSimpleTypeModelDescription(modelType); + } + + if (modelType.IsEnum) + { + return GenerateEnumTypeModelDescription(modelType); + } + + if (modelType.IsGenericType) + { + Type[] genericArguments = modelType.GetGenericArguments(); + + if (genericArguments.Length == 1) + { + Type enumerableType = typeof(IEnumerable<>).MakeGenericType(genericArguments); + if (enumerableType.IsAssignableFrom(modelType)) + { + return GenerateCollectionModelDescription(modelType, genericArguments[0]); + } + } + if (genericArguments.Length == 2) + { + Type dictionaryType = typeof(IDictionary<,>).MakeGenericType(genericArguments); + if (dictionaryType.IsAssignableFrom(modelType)) + { + return GenerateDictionaryModelDescription(modelType, genericArguments[0], genericArguments[1]); + } + + Type keyValuePairType = typeof(KeyValuePair<,>).MakeGenericType(genericArguments); + if (keyValuePairType.IsAssignableFrom(modelType)) + { + return GenerateKeyValuePairModelDescription(modelType, genericArguments[0], genericArguments[1]); + } + } + } + + if (modelType.IsArray) + { + Type elementType = modelType.GetElementType(); + return GenerateCollectionModelDescription(modelType, elementType); + } + + if (modelType == typeof(NameValueCollection)) + { + return GenerateDictionaryModelDescription(modelType, typeof(string), typeof(string)); + } + + if (typeof(IDictionary).IsAssignableFrom(modelType)) + { + return GenerateDictionaryModelDescription(modelType, typeof(object), typeof(object)); + } + + if (typeof(IEnumerable).IsAssignableFrom(modelType)) + { + return GenerateCollectionModelDescription(modelType, typeof(object)); + } + + return GenerateComplexTypeModelDescription(modelType); + } + + // Change this to provide different name for the member. + private static string GetMemberName(MemberInfo member, bool hasDataContractAttribute) + { + JsonPropertyAttribute jsonProperty = member.GetCustomAttribute<JsonPropertyAttribute>(); + if (jsonProperty != null && !String.IsNullOrEmpty(jsonProperty.PropertyName)) + { + return jsonProperty.PropertyName; + } + + if (hasDataContractAttribute) + { + DataMemberAttribute dataMember = member.GetCustomAttribute<DataMemberAttribute>(); + if (dataMember != null && !String.IsNullOrEmpty(dataMember.Name)) + { + return dataMember.Name; + } + } + + return member.Name; + } + + private static bool ShouldDisplayMember(MemberInfo member, bool hasDataContractAttribute) + { + JsonIgnoreAttribute jsonIgnore = member.GetCustomAttribute<JsonIgnoreAttribute>(); + XmlIgnoreAttribute xmlIgnore = member.GetCustomAttribute<XmlIgnoreAttribute>(); + IgnoreDataMemberAttribute ignoreDataMember = member.GetCustomAttribute<IgnoreDataMemberAttribute>(); + NonSerializedAttribute nonSerialized = member.GetCustomAttribute<NonSerializedAttribute>(); + ApiExplorerSettingsAttribute apiExplorerSetting = member.GetCustomAttribute<ApiExplorerSettingsAttribute>(); + + bool hasMemberAttribute = member.DeclaringType.IsEnum ? + member.GetCustomAttribute<EnumMemberAttribute>() != null : + member.GetCustomAttribute<DataMemberAttribute>() != null; + + // Display member only if all the followings are true: + // no JsonIgnoreAttribute + // no XmlIgnoreAttribute + // no IgnoreDataMemberAttribute + // no NonSerializedAttribute + // no ApiExplorerSettingsAttribute with IgnoreApi set to true + // no DataContractAttribute without DataMemberAttribute or EnumMemberAttribute + return jsonIgnore == null && + xmlIgnore == null && + ignoreDataMember == null && + nonSerialized == null && + (apiExplorerSetting == null || !apiExplorerSetting.IgnoreApi) && + (!hasDataContractAttribute || hasMemberAttribute); + } + + private string CreateDefaultDocumentation(Type type) + { + string documentation; + if (DefaultTypeDocumentation.TryGetValue(type, out documentation)) + { + return documentation; + } + if (DocumentationProvider != null) + { + documentation = DocumentationProvider.GetDocumentation(type); + } + + return documentation; + } + + private void GenerateAnnotations(MemberInfo property, ParameterDescription propertyModel) + { + List<ParameterAnnotation> annotations = new List<ParameterAnnotation>(); + + IEnumerable<Attribute> attributes = property.GetCustomAttributes(); + foreach (Attribute attribute in attributes) + { + Func<object, string> textGenerator; + if (AnnotationTextGenerator.TryGetValue(attribute.GetType(), out textGenerator)) + { + annotations.Add( + new ParameterAnnotation + { + AnnotationAttribute = attribute, + Documentation = textGenerator(attribute) + }); + } + } + + // Rearrange the annotations + annotations.Sort((x, y) => + { + // Special-case RequiredAttribute so that it shows up on top + if (x.AnnotationAttribute is RequiredAttribute) + { + return -1; + } + if (y.AnnotationAttribute is RequiredAttribute) + { + return 1; + } + + // Sort the rest based on alphabetic order of the documentation + return String.Compare(x.Documentation, y.Documentation, StringComparison.OrdinalIgnoreCase); + }); + + foreach (ParameterAnnotation annotation in annotations) + { + propertyModel.Annotations.Add(annotation); + } + } + + private CollectionModelDescription GenerateCollectionModelDescription(Type modelType, Type elementType) + { + ModelDescription collectionModelDescription = GetOrCreateModelDescription(elementType); + if (collectionModelDescription != null) + { + return new CollectionModelDescription + { + Name = ModelNameHelper.GetModelName(modelType), + ModelType = modelType, + ElementDescription = collectionModelDescription + }; + } + + return null; + } + + private ModelDescription GenerateComplexTypeModelDescription(Type modelType) + { + ComplexTypeModelDescription complexModelDescription = new ComplexTypeModelDescription + { + Name = ModelNameHelper.GetModelName(modelType), + ModelType = modelType, + Documentation = CreateDefaultDocumentation(modelType) + }; + + GeneratedModels.Add(complexModelDescription.Name, complexModelDescription); + bool hasDataContractAttribute = modelType.GetCustomAttribute<DataContractAttribute>() != null; + PropertyInfo[] properties = modelType.GetProperties(BindingFlags.Public | BindingFlags.Instance); + foreach (PropertyInfo property in properties) + { + if (ShouldDisplayMember(property, hasDataContractAttribute)) + { + ParameterDescription propertyModel = new ParameterDescription + { + Name = GetMemberName(property, hasDataContractAttribute) + }; + + if (DocumentationProvider != null) + { + propertyModel.Documentation = DocumentationProvider.GetDocumentation(property); + } + + GenerateAnnotations(property, propertyModel); + complexModelDescription.Properties.Add(propertyModel); + propertyModel.TypeDescription = GetOrCreateModelDescription(property.PropertyType); + } + } + + FieldInfo[] fields = modelType.GetFields(BindingFlags.Public | BindingFlags.Instance); + foreach (FieldInfo field in fields) + { + if (ShouldDisplayMember(field, hasDataContractAttribute)) + { + ParameterDescription propertyModel = new ParameterDescription + { + Name = GetMemberName(field, hasDataContractAttribute) + }; + + if (DocumentationProvider != null) + { + propertyModel.Documentation = DocumentationProvider.GetDocumentation(field); + } + + complexModelDescription.Properties.Add(propertyModel); + propertyModel.TypeDescription = GetOrCreateModelDescription(field.FieldType); + } + } + + return complexModelDescription; + } + + private DictionaryModelDescription GenerateDictionaryModelDescription(Type modelType, Type keyType, Type valueType) + { + ModelDescription keyModelDescription = GetOrCreateModelDescription(keyType); + ModelDescription valueModelDescription = GetOrCreateModelDescription(valueType); + + return new DictionaryModelDescription + { + Name = ModelNameHelper.GetModelName(modelType), + ModelType = modelType, + KeyModelDescription = keyModelDescription, + ValueModelDescription = valueModelDescription + }; + } + + private EnumTypeModelDescription GenerateEnumTypeModelDescription(Type modelType) + { + EnumTypeModelDescription enumDescription = new EnumTypeModelDescription + { + Name = ModelNameHelper.GetModelName(modelType), + ModelType = modelType, + Documentation = CreateDefaultDocumentation(modelType) + }; + bool hasDataContractAttribute = modelType.GetCustomAttribute<DataContractAttribute>() != null; + foreach (FieldInfo field in modelType.GetFields(BindingFlags.Public | BindingFlags.Static)) + { + if (ShouldDisplayMember(field, hasDataContractAttribute)) + { + EnumValueDescription enumValue = new EnumValueDescription + { + Name = field.Name, + Value = field.GetRawConstantValue().ToString() + }; + if (DocumentationProvider != null) + { + enumValue.Documentation = DocumentationProvider.GetDocumentation(field); + } + enumDescription.Values.Add(enumValue); + } + } + GeneratedModels.Add(enumDescription.Name, enumDescription); + + return enumDescription; + } + + private KeyValuePairModelDescription GenerateKeyValuePairModelDescription(Type modelType, Type keyType, Type valueType) + { + ModelDescription keyModelDescription = GetOrCreateModelDescription(keyType); + ModelDescription valueModelDescription = GetOrCreateModelDescription(valueType); + + return new KeyValuePairModelDescription + { + Name = ModelNameHelper.GetModelName(modelType), + ModelType = modelType, + KeyModelDescription = keyModelDescription, + ValueModelDescription = valueModelDescription + }; + } + + private ModelDescription GenerateSimpleTypeModelDescription(Type modelType) + { + SimpleTypeModelDescription simpleModelDescription = new SimpleTypeModelDescription + { + Name = ModelNameHelper.GetModelName(modelType), + ModelType = modelType, + Documentation = CreateDefaultDocumentation(modelType) + }; + GeneratedModels.Add(simpleModelDescription.Name, simpleModelDescription); + + return simpleModelDescription; + } + } +} \ No newline at end of file -- Gitblit v1.9.3