From 0f125df0a5c402924fa699448d5832521e3946bd Mon Sep 17 00:00:00 2001 From: Henning Krause Date: Fri, 17 Jul 2020 23:12:44 +0200 Subject: [PATCH 1/3] Added entity and description annotations to the OdataBuilder --- .../Builder/ActionOnDeleteAttribute.cs | 2 +- .../ActionLinkGenerationConvention.cs | 2 +- .../DescriptionAnnotationConvention.cs | 29 ++++++ .../Builder/DescriptionAttribute.cs | 30 ++++++ .../Builder/EdmModelHelperMethods.cs | 31 ++++++ .../Builder/ODataConventionModelBuilder.cs | 2 + .../Builder/PropertyConfiguration.cs | 22 +++++ .../Builder/StructuralTypeConfiguration.cs | 18 ++++ ...turalTypeConfigurationOfTStructuralType.cs | 11 +++ .../Microsoft.AspNet.OData.Shared.projitems | 4 +- .../ODataConventionModelBuilderTests.cs | 99 +++++++++++++++++++ .../Microsoft.AspNetCore.OData.PublicApi.bsl | 7 +- ...Microsoft.AspNetCore3x.OData.PublicApi.bsl | 7 +- 13 files changed, 257 insertions(+), 7 deletions(-) create mode 100644 src/Microsoft.AspNet.OData.Shared/Builder/Conventions/DescriptionAnnotationConvention.cs create mode 100644 src/Microsoft.AspNet.OData.Shared/Builder/DescriptionAttribute.cs diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/ActionOnDeleteAttribute.cs b/src/Microsoft.AspNet.OData.Shared/Builder/ActionOnDeleteAttribute.cs index 7de1e0b302..a71d0aacce 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/ActionOnDeleteAttribute.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/ActionOnDeleteAttribute.cs @@ -6,7 +6,7 @@ namespace Microsoft.AspNet.OData.Builder { - /// + /// /// Represents an that can be placed on a navigation property to specify the applied /// action whether delete should also remove the associated item on the other end of the association. /// diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/ActionLinkGenerationConvention.cs b/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/ActionLinkGenerationConvention.cs index 554b1837fc..59c8891a2d 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/ActionLinkGenerationConvention.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/ActionLinkGenerationConvention.cs @@ -5,7 +5,7 @@ namespace Microsoft.AspNet.OData.Builder.Conventions { - /// + /// /// The ActionLinkGenerationConvention calls action.HasActionLink(..) if the action binds to a single entity and has not previously been configured. /// internal class ActionLinkGenerationConvention : IOperationConvention diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/DescriptionAnnotationConvention.cs b/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/DescriptionAnnotationConvention.cs new file mode 100644 index 0000000000..d8b2e95876 --- /dev/null +++ b/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/DescriptionAnnotationConvention.cs @@ -0,0 +1,29 @@ +using System.Reflection; + +namespace Microsoft.AspNet.OData.Builder.Conventions +{ + internal class DescriptionAnnotationConvention : IEdmTypeConvention + { + public void Apply(IEdmTypeConfiguration edmTypeConfiguration, ODataConventionModelBuilder model) + { + if (!(edmTypeConfiguration is StructuralTypeConfiguration structuralType)) return; + + var attribute = (DescriptionAttribute) structuralType.ClrType.GetCustomAttribute(typeof(DescriptionAttribute), true); + if (attribute != null) + { + structuralType.Description = attribute.Description; + } + + foreach (var property in structuralType.Properties) + { + attribute = (DescriptionAttribute) property.PropertyInfo.GetCustomAttribute(typeof(DescriptionAttribute), true); + if (attribute != null) + { + property.Description = attribute.Description; + } + } + + + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/DescriptionAttribute.cs b/src/Microsoft.AspNet.OData.Shared/Builder/DescriptionAttribute.cs new file mode 100644 index 0000000000..3a2b00fb04 --- /dev/null +++ b/src/Microsoft.AspNet.OData.Shared/Builder/DescriptionAttribute.cs @@ -0,0 +1,30 @@ +using System; + +namespace Microsoft.AspNet.OData.Builder +{ + /// + /// Represents an that can be placed on a property or class to document its purpose. The content will be includes in the Odata metadata document. + /// + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + public class DescriptionAttribute : Attribute + { + /// + /// Gets or sets a summary about the purpose of the property or class. + /// + public string Description { get; } + + /// + /// Gets or sets a detailed description about the purpose of the property or class. + /// + public string LongDescription { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// A summary about the purpose of the property or class. + public DescriptionAttribute(string description) + { + Description = description; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/EdmModelHelperMethods.cs b/src/Microsoft.AspNet.OData.Shared/Builder/EdmModelHelperMethods.cs index 9d850b53a0..690b4e406b 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/EdmModelHelperMethods.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/EdmModelHelperMethods.cs @@ -66,9 +66,40 @@ public static IEdmModel BuildEdmModel(ODataModelBuilder builder) // build the map from IEdmEntityType to IEdmFunctionImport model.SetAnnotationValue(model, new BindableOperationFinder(model)); + + AddDescriptionAnnotations(builder, model, edmTypeMap); + + + return model; } + private static void AddDescriptionAnnotations(ODataModelBuilder builder, EdmModel model, IReadOnlyDictionary edmTypeMap) + { + foreach (var structuralType in builder.StructuralTypes) + { + + if (!edmTypeMap.TryGetValue(structuralType.ClrType, out var edmType)) continue; + if (!(edmType is IEdmVocabularyAnnotatable target)) continue; + + if (!string.IsNullOrEmpty(structuralType.Description)) + { + model.SetDescriptionAnnotation(target, structuralType.Description); + } + + if (edmType is IEdmStructuredType structuredType) + { + foreach (var (property, configuration) in structuredType.DeclaredProperties.Join(structuralType.Properties, p => p.Name, p => p.Name, (property, configuration) => (property, configuration))) + { + if (!string.IsNullOrEmpty(configuration.Description)) + { + model.SetDescriptionAnnotation(property, configuration.Description); + } + } + } + } + } + private static void AddTypes(this EdmModel model, Dictionary types) { Contract.Assert(model != null); diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/ODataConventionModelBuilder.cs b/src/Microsoft.AspNet.OData.Shared/Builder/ODataConventionModelBuilder.cs index 177685b581..720c721a85 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/ODataConventionModelBuilder.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/ODataConventionModelBuilder.cs @@ -71,6 +71,8 @@ public partial class ODataConventionModelBuilder : ODataModelBuilder // IEdmFunctionImportConventions's new ActionLinkGenerationConvention(), new FunctionLinkGenerationConvention(), + + new DescriptionAnnotationConvention(), }; // These hashset's keep track of edmtypes/navigation sources for which conventions diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/PropertyConfiguration.cs b/src/Microsoft.AspNet.OData.Shared/Builder/PropertyConfiguration.cs index caf9667fb0..4a65b6b06d 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/PropertyConfiguration.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/PropertyConfiguration.cs @@ -15,6 +15,7 @@ namespace Microsoft.AspNet.OData.Builder public abstract class PropertyConfiguration { private string _name; + private string _description; /// /// Initializes a new instance of the class. @@ -41,6 +42,7 @@ protected PropertyConfiguration(PropertyInfo property, StructuralTypeConfigurati DerivedTypeConstraints = new DerivedTypeConstraintConfiguration(); } + /// /// Gets or sets the name of the property. /// @@ -59,6 +61,26 @@ public string Name _name = value; } + } + + /// + /// Gets or sets the description of the property. + /// + public string Description + { + get + { + return _description; + } + set + { + if (value == null) + { + throw Error.PropertyNull(); + } + + _description = value; + } } /// diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfiguration.cs b/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfiguration.cs index a9b6123315..9a607328fb 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfiguration.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfiguration.cs @@ -23,6 +23,7 @@ public abstract class StructuralTypeConfiguration : IEdmTypeConfiguration private PropertyInfo _dynamicPropertyDictionary; private StructuralTypeConfiguration _baseType; private bool _baseTypeConfigured; + private string _description; /// /// Initializes a new instance of the class. @@ -85,6 +86,23 @@ public virtual string FullName } } + + /// + /// Gets or sets the description of this edm type + /// + public virtual string Description + { + get + { + return _description; + } + set + { + _description = value; + } + } + + /// /// Gets or sets the namespace of this EDM type. /// diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfigurationOfTStructuralType.cs b/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfigurationOfTStructuralType.cs index 4ad57a656c..03272216dc 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfigurationOfTStructuralType.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfigurationOfTStructuralType.cs @@ -653,6 +653,17 @@ public StructuralTypeConfiguration Count(QueryOptionSetting set return this; } + /// + /// Sets the description for this type. + /// + /// + /// + public StructuralTypeConfiguration HasDescription(string value) + { + _configuration.Description = value; + return this; + } + /// /// Sets sortable properties depends on of this structural type. /// diff --git a/src/Microsoft.AspNet.OData.Shared/Microsoft.AspNet.OData.Shared.projitems b/src/Microsoft.AspNet.OData.Shared/Microsoft.AspNet.OData.Shared.projitems index cf51c1570a..d66ee4b461 100644 --- a/src/Microsoft.AspNet.OData.Shared/Microsoft.AspNet.OData.Shared.projitems +++ b/src/Microsoft.AspNet.OData.Shared/Microsoft.AspNet.OData.Shared.projitems @@ -14,9 +14,11 @@ + + @@ -169,7 +171,7 @@ - + diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/ODataConventionModelBuilderTests.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/ODataConventionModelBuilderTests.cs index eba31dcc54..177ddc0e7b 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/ODataConventionModelBuilderTests.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/ODataConventionModelBuilderTests.cs @@ -3134,6 +3134,91 @@ public void ConventionModelBuilder_Work_With_ExpilitPropertyDeclare() Assert.False(entityType.Properties().First(p => p.Name.Equals("UserType")).Type.IsNullable); Assert.False(entityType.Properties().First(p => p.Name.Equals("Contacts")).Type.IsNullable); } + + [Fact] + public void ConventionModelBuilder_tiontion_Set_On_EntityType() + { + // Arrange + var builder = ODataConventionModelBuilderFactory.Create(); + + // Act + var user = builder.EntitySet("IdentityUsers"); + user.EntityType.HasKey(p => new { p.Provider, p.UserId }); + user.EntityType.HasDescription("Test description"); + var edmModel = builder.GetEdmModel(); + + // Assert + Assert.NotNull(edmModel); + var entityType = edmModel.SchemaElements.OfType().First(); + var annotation = entityType.VocabularyAnnotations(edmModel).First(); + Assert.Equal("Description", annotation.Term.Name); + var value = Assert.IsType(annotation.Value); + Assert.Equal("Test description", value.Value); + } + + [Fact] + public void ConventionModelBuilder_Description_Set_On_Property() + { + // Arrange + var builder = ODataConventionModelBuilderFactory.Create(); + + // Act + var user = builder.EntitySet("IdentityUsers"); + user.EntityType.HasKey(p => new { p.Provider, p.UserId }); + user.EntityType.Property(i => i.Name).Description = "Returns the name of the user."; + var edmModel = builder.GetEdmModel(); + + // Assert + Assert.NotNull(edmModel); + var entityType = edmModel.SchemaElements.OfType().First(); + var property = entityType.Properties().First(p => p.Name == "Name"); + + var annotation = property.VocabularyAnnotations(edmModel).First(); + Assert.Equal("Description", annotation.Term.Name); + var value = Assert.IsType(annotation.Value); + Assert.Equal("Returns the name of the user.", value.Value); + } + + [Fact] + public void ConventionModelBuilder_Description_Set_On_Entity_Via_Description_Attribute() + { + // Arrange + var builder = ODataConventionModelBuilderFactory.Create(); + + // Act + builder.EntitySet("DescriptionEntities"); + var edmModel = builder.GetEdmModel(); + + // Assert + Assert.NotNull(edmModel); + var entityType = edmModel.SchemaElements.OfType().First(); + var annotation = entityType.VocabularyAnnotations(edmModel).First(); + Assert.Equal("Description", annotation.Term.Name); + var value = Assert.IsType(annotation.Value); + Assert.Equal("The summary for this type.", value.Value); + + } + + [Fact] + public void ConventionModelBuilder_Description_Set_On_Property_Via_Description_Attribute() + { + // Arrange + var builder = ODataConventionModelBuilderFactory.Create(); + + // Act + builder.EntitySet("IdentityUsers"); + var edmModel = builder.GetEdmModel(); + + // Assert + Assert.NotNull(edmModel); + var entityType = edmModel.SchemaElements.OfType().First(); + var property = entityType.Properties().First(p => p.Name == "Id"); + + var annotation = property.VocabularyAnnotations(edmModel).First(); + Assert.Equal("Description", annotation.Term.Name); + var value = Assert.IsType(annotation.Value); + Assert.Equal("The summary for this property.", value.Value); + } [Fact] public void ConventionModelBuild_Work_With_AutoExpandEdmTypeAttribute() @@ -3720,4 +3805,18 @@ public class MaxLengthEntity public string NonLength { get; set; } } + + [Description("The summary for this type.", LongDescription = "The long description for this type.")] + public class DescriptionEntity + { + [Key] + public int Id { get; set; } + } + + public class DescriptionPropertyEntity + { + [Key] + [Description("The summary for this property.", LongDescription = "The long description for this property.")] + public int Id { get; set; } + } } diff --git a/test/UnitTest/Microsoft.AspNetCore.OData.Test/PublicApi/Microsoft.AspNetCore.OData.PublicApi.bsl b/test/UnitTest/Microsoft.AspNetCore.OData.Test/PublicApi/Microsoft.AspNetCore.OData.PublicApi.bsl index 82c87bfecd..0d87035150 100644 --- a/test/UnitTest/Microsoft.AspNetCore.OData.Test/PublicApi/Microsoft.AspNetCore.OData.PublicApi.bsl +++ b/test/UnitTest/Microsoft.AspNetCore.OData.Test/PublicApi/Microsoft.AspNetCore.OData.PublicApi.bsl @@ -1104,6 +1104,7 @@ public abstract class Microsoft.AspNet.OData.Builder.PropertyConfiguration { bool AutoExpand { public get; public set; } StructuralTypeConfiguration DeclaringType { public get; } DerivedTypeConstraintConfiguration DerivedTypeConstraints { public get; } + string Description { public get; public set; } bool DisableAutoExpandWhenSelectIsPresent { public get; public set; } bool IsRestricted { public get; } PropertyKind Kind { public abstract get; } @@ -1173,6 +1174,7 @@ public abstract class Microsoft.AspNet.OData.Builder.StructuralTypeConfiguration bool BaseTypeConfigured { public virtual get; } StructuralTypeConfiguration BaseTypeInternal { protected virtual get; } System.Type ClrType { public virtual get; } + string Description { public virtual get; public virtual set; } System.Reflection.PropertyInfo DynamicPropertyDictionary { public get; } System.Collections.Generic.IDictionary`2[[System.Reflection.PropertyInfo],[Microsoft.AspNet.OData.Builder.PropertyConfiguration]] ExplicitProperties { protected get; } string FullName { public virtual get; } @@ -1231,6 +1233,7 @@ public abstract class Microsoft.AspNet.OData.Builder.StructuralTypeConfiguration public StructuralTypeConfiguration`1 Filter (QueryOptionSetting setting) public StructuralTypeConfiguration`1 Filter (string[] properties) public StructuralTypeConfiguration`1 Filter (QueryOptionSetting setting, string[] properties) + public StructuralTypeConfiguration`1 HasDescription (string value) public void HasDynamicProperties (Expression`1 propertyExpression) public NavigationPropertyConfiguration HasMany (Expression`1 navigationPropertyExpression) public NavigationPropertyConfiguration HasOptional (Expression`1 navigationPropertyExpression) @@ -1248,7 +1251,6 @@ public abstract class Microsoft.AspNet.OData.Builder.StructuralTypeConfiguration public StructuralTypeConfiguration`1 OrderBy (QueryOptionSetting setting, string[] properties) public StructuralTypeConfiguration`1 Page () public StructuralTypeConfiguration`1 Page (System.Nullable`1[[System.Int32]] maxTopValue, System.Nullable`1[[System.Int32]] pageSizeValue) - public LengthPropertyConfiguration Property (Expression`1 propertyExpression) public DecimalPropertyConfiguration Property (Expression`1 propertyExpression) public PrecisionPropertyConfiguration Property (Expression`1 propertyExpression) public PrecisionPropertyConfiguration Property (Expression`1 propertyExpression) @@ -1257,8 +1259,9 @@ public abstract class Microsoft.AspNet.OData.Builder.StructuralTypeConfiguration public PrecisionPropertyConfiguration Property (Expression`1 propertyExpression) public PrecisionPropertyConfiguration Property (Expression`1 propertyExpression) public DecimalPropertyConfiguration Property (Expression`1 propertyExpression) - public PrecisionPropertyConfiguration Property (Expression`1 propertyExpression) public PrimitivePropertyConfiguration Property (Expression`1 propertyExpression) + public PrecisionPropertyConfiguration Property (Expression`1 propertyExpression) + public LengthPropertyConfiguration Property (Expression`1 propertyExpression) public PrimitivePropertyConfiguration Property (Expression`1 propertyExpression) public PrimitivePropertyConfiguration Property (Expression`1 propertyExpression) public StructuralTypeConfiguration`1 Select () diff --git a/test/UnitTest/Microsoft.AspNetCore.OData.Test/PublicApi/Microsoft.AspNetCore3x.OData.PublicApi.bsl b/test/UnitTest/Microsoft.AspNetCore.OData.Test/PublicApi/Microsoft.AspNetCore3x.OData.PublicApi.bsl index eaa74317bf..fd479162ab 100644 --- a/test/UnitTest/Microsoft.AspNetCore.OData.Test/PublicApi/Microsoft.AspNetCore3x.OData.PublicApi.bsl +++ b/test/UnitTest/Microsoft.AspNetCore.OData.Test/PublicApi/Microsoft.AspNetCore3x.OData.PublicApi.bsl @@ -1104,6 +1104,7 @@ public abstract class Microsoft.AspNet.OData.Builder.PropertyConfiguration { bool AutoExpand { public get; public set; } StructuralTypeConfiguration DeclaringType { public get; } DerivedTypeConstraintConfiguration DerivedTypeConstraints { public get; } + string Description { public get; public set; } bool DisableAutoExpandWhenSelectIsPresent { public get; public set; } bool IsRestricted { public get; } PropertyKind Kind { public abstract get; } @@ -1173,6 +1174,7 @@ public abstract class Microsoft.AspNet.OData.Builder.StructuralTypeConfiguration bool BaseTypeConfigured { public virtual get; } StructuralTypeConfiguration BaseTypeInternal { protected virtual get; } System.Type ClrType { public virtual get; } + string Description { public virtual get; public virtual set; } System.Reflection.PropertyInfo DynamicPropertyDictionary { public get; } System.Collections.Generic.IDictionary`2[[System.Reflection.PropertyInfo],[Microsoft.AspNet.OData.Builder.PropertyConfiguration]] ExplicitProperties { protected get; } string FullName { public virtual get; } @@ -1231,6 +1233,7 @@ public abstract class Microsoft.AspNet.OData.Builder.StructuralTypeConfiguration public StructuralTypeConfiguration`1 Filter (QueryOptionSetting setting) public StructuralTypeConfiguration`1 Filter (string[] properties) public StructuralTypeConfiguration`1 Filter (QueryOptionSetting setting, string[] properties) + public StructuralTypeConfiguration`1 HasDescription (string value) public void HasDynamicProperties (Expression`1 propertyExpression) public NavigationPropertyConfiguration HasMany (Expression`1 navigationPropertyExpression) public NavigationPropertyConfiguration HasOptional (Expression`1 navigationPropertyExpression) @@ -1248,7 +1251,6 @@ public abstract class Microsoft.AspNet.OData.Builder.StructuralTypeConfiguration public StructuralTypeConfiguration`1 OrderBy (QueryOptionSetting setting, string[] properties) public StructuralTypeConfiguration`1 Page () public StructuralTypeConfiguration`1 Page (System.Nullable`1[[System.Int32]] maxTopValue, System.Nullable`1[[System.Int32]] pageSizeValue) - public LengthPropertyConfiguration Property (Expression`1 propertyExpression) public DecimalPropertyConfiguration Property (Expression`1 propertyExpression) public PrecisionPropertyConfiguration Property (Expression`1 propertyExpression) public PrecisionPropertyConfiguration Property (Expression`1 propertyExpression) @@ -1257,8 +1259,9 @@ public abstract class Microsoft.AspNet.OData.Builder.StructuralTypeConfiguration public PrecisionPropertyConfiguration Property (Expression`1 propertyExpression) public PrecisionPropertyConfiguration Property (Expression`1 propertyExpression) public DecimalPropertyConfiguration Property (Expression`1 propertyExpression) - public PrecisionPropertyConfiguration Property (Expression`1 propertyExpression) public PrimitivePropertyConfiguration Property (Expression`1 propertyExpression) + public PrecisionPropertyConfiguration Property (Expression`1 propertyExpression) + public LengthPropertyConfiguration Property (Expression`1 propertyExpression) public PrimitivePropertyConfiguration Property (Expression`1 propertyExpression) public PrimitivePropertyConfiguration Property (Expression`1 propertyExpression) public StructuralTypeConfiguration`1 Select () From 4015119e32cbb9be7622551f0437be9a720006bb Mon Sep 17 00:00:00 2001 From: Henning Krause Date: Sat, 18 Jul 2020 14:50:35 +0200 Subject: [PATCH 2/3] Fixed build issues, implemented long description --- .../Attributes}/DescriptionAttribute.cs | 6 +- .../DescriptionAnnotationConvention.cs | 9 +++ .../Builder/EdmModelHelperMethods.cs | 14 +++- .../Builder/PropertyConfiguration.cs | 25 ++++---- .../Builder/StructuralTypeConfiguration.cs | 24 ++++--- ...turalTypeConfigurationOfTStructuralType.cs | 24 ++++++- .../Microsoft.AspNet.OData.Shared.projitems | 2 +- .../ODataConventionModelBuilderTests.cs | 64 +++++++++---------- 8 files changed, 103 insertions(+), 65 deletions(-) rename src/Microsoft.AspNet.OData.Shared/Builder/{ => Conventions/Attributes}/DescriptionAttribute.cs (82%) diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/DescriptionAttribute.cs b/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/DescriptionAttribute.cs similarity index 82% rename from src/Microsoft.AspNet.OData.Shared/Builder/DescriptionAttribute.cs rename to src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/DescriptionAttribute.cs index 3a2b00fb04..a454ccf66e 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/DescriptionAttribute.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/Attributes/DescriptionAttribute.cs @@ -1,15 +1,15 @@ using System; -namespace Microsoft.AspNet.OData.Builder +namespace Microsoft.AspNet.OData.Builder.Conventions.Attributes { /// /// Represents an that can be placed on a property or class to document its purpose. The content will be includes in the Odata metadata document. /// [AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, AllowMultiple = false, Inherited = false)] - public class DescriptionAttribute : Attribute + public sealed class DescriptionAttribute : Attribute { /// - /// Gets or sets a summary about the purpose of the property or class. + /// Gets or summary about the purpose of the property or class. /// public string Description { get; } diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/DescriptionAnnotationConvention.cs b/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/DescriptionAnnotationConvention.cs index d8b2e95876..08dc5f2c7b 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/DescriptionAnnotationConvention.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/DescriptionAnnotationConvention.cs @@ -1,4 +1,5 @@ using System.Reflection; +using Microsoft.AspNet.OData.Builder.Conventions.Attributes; namespace Microsoft.AspNet.OData.Builder.Conventions { @@ -12,6 +13,10 @@ public void Apply(IEdmTypeConfiguration edmTypeConfiguration, ODataConventionMod if (attribute != null) { structuralType.Description = attribute.Description; + if (!string.IsNullOrEmpty(attribute.LongDescription)) + { + structuralType.LongDescription = attribute.LongDescription; + } } foreach (var property in structuralType.Properties) @@ -20,6 +25,10 @@ public void Apply(IEdmTypeConfiguration edmTypeConfiguration, ODataConventionMod if (attribute != null) { property.Description = attribute.Description; + if (!string.IsNullOrEmpty(attribute.LongDescription)) + { + property.LongDescription = attribute.LongDescription; + } } } diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/EdmModelHelperMethods.cs b/src/Microsoft.AspNet.OData.Shared/Builder/EdmModelHelperMethods.cs index 690b4e406b..d877bfd354 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/EdmModelHelperMethods.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/EdmModelHelperMethods.cs @@ -86,14 +86,22 @@ private static void AddDescriptionAnnotations(ODataModelBuilder builder, EdmMode { model.SetDescriptionAnnotation(target, structuralType.Description); } + if (!string.IsNullOrEmpty(structuralType.LongDescription)) + { + model.SetLongDescriptionAnnotation(target, structuralType.LongDescription); + } if (edmType is IEdmStructuredType structuredType) { - foreach (var (property, configuration) in structuredType.DeclaredProperties.Join(structuralType.Properties, p => p.Name, p => p.Name, (property, configuration) => (property, configuration))) + foreach (var entry in structuredType.DeclaredProperties.Join(structuralType.Properties, p => p.Name, p => p.Name, (property, configuration) => new {property, configuration})) { - if (!string.IsNullOrEmpty(configuration.Description)) + if (!string.IsNullOrEmpty(entry.configuration.Description)) + { + model.SetDescriptionAnnotation(entry.property, entry.configuration.Description); + } + if (!string.IsNullOrEmpty(entry.configuration.LongDescription)) { - model.SetDescriptionAnnotation(property, configuration.Description); + model.SetLongDescriptionAnnotation(entry.property, entry.configuration.LongDescription); } } } diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/PropertyConfiguration.cs b/src/Microsoft.AspNet.OData.Shared/Builder/PropertyConfiguration.cs index 4a65b6b06d..6778582a85 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/PropertyConfiguration.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/PropertyConfiguration.cs @@ -16,6 +16,7 @@ public abstract class PropertyConfiguration { private string _name; private string _description; + private string _longDescription; /// /// Initializes a new instance of the class. @@ -64,23 +65,21 @@ public string Name } /// - /// Gets or sets the description of the property. + /// Gets or sets the summary for the property. /// public string Description { - get - { - return _description; - } - set - { - if (value == null) - { - throw Error.PropertyNull(); - } + get => _description; + set => _description = value ?? throw Error.PropertyNull(); + } - _description = value; - } + /// + /// Gets or sets the detailed description of the property. + /// + public string LongDescription + { + get => _longDescription; + set => _longDescription = value ?? throw Error.PropertyNull(); } /// diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfiguration.cs b/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfiguration.cs index 9a607328fb..1c5605ecdc 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfiguration.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfiguration.cs @@ -24,6 +24,7 @@ public abstract class StructuralTypeConfiguration : IEdmTypeConfiguration private StructuralTypeConfiguration _baseType; private bool _baseTypeConfigured; private string _description; + private string _longDescription; /// /// Initializes a new instance of the class. @@ -88,18 +89,21 @@ public virtual string FullName /// - /// Gets or sets the description of this edm type + /// Gets or sets the summary for the property. /// - public virtual string Description + public string Description { - get - { - return _description; - } - set - { - _description = value; - } + get => _description; + set => _description = value ?? throw Error.PropertyNull(); + } + + /// + /// Gets or sets the detailed description of the property. + /// + public string LongDescription + { + get => _longDescription; + set => _longDescription = value ?? throw Error.PropertyNull(); } diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfigurationOfTStructuralType.cs b/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfigurationOfTStructuralType.cs index 03272216dc..95f16fdf76 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfigurationOfTStructuralType.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfigurationOfTStructuralType.cs @@ -656,11 +656,29 @@ public StructuralTypeConfiguration Count(QueryOptionSetting set /// /// Sets the description for this type. /// - /// + /// A short description for this type. + /// A detailed description for this type. /// - public StructuralTypeConfiguration HasDescription(string value) + public StructuralTypeConfiguration HasDescription(string summary, string detailedDescription) { - _configuration.Description = value; + _configuration.Description = summary; + if (!string.IsNullOrEmpty(detailedDescription)) + { + _configuration.LongDescription = detailedDescription; + } + + return this; + } + + /// + /// Sets the description for this type. + /// + /// A short description for this type. + /// + public StructuralTypeConfiguration HasDescription(string summary) + { + _configuration.Description = summary; + return this; } diff --git a/src/Microsoft.AspNet.OData.Shared/Microsoft.AspNet.OData.Shared.projitems b/src/Microsoft.AspNet.OData.Shared/Microsoft.AspNet.OData.Shared.projitems index d66ee4b461..72b5db0731 100644 --- a/src/Microsoft.AspNet.OData.Shared/Microsoft.AspNet.OData.Shared.projitems +++ b/src/Microsoft.AspNet.OData.Shared/Microsoft.AspNet.OData.Shared.projitems @@ -18,7 +18,7 @@ - + diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/ODataConventionModelBuilderTests.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/ODataConventionModelBuilderTests.cs index 177ddc0e7b..b998bfa205 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/ODataConventionModelBuilderTests.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Builder/Conventions/ODataConventionModelBuilderTests.cs @@ -10,6 +10,7 @@ using System.Reflection; using System.Runtime.Serialization; using Microsoft.AspNet.OData.Builder; +using Microsoft.AspNet.OData.Builder.Conventions.Attributes; using Microsoft.AspNet.OData.Formatter; using Microsoft.AspNet.OData.Query; using Microsoft.AspNet.OData.Test.Abstraction; @@ -3136,7 +3137,7 @@ public void ConventionModelBuilder_Work_With_ExpilitPropertyDeclare() } [Fact] - public void ConventionModelBuilder_tiontion_Set_On_EntityType() + public void ConventionModelBuilder_Description_Set_On_EntityType() { // Arrange var builder = ODataConventionModelBuilderFactory.Create(); @@ -3144,18 +3145,25 @@ public void ConventionModelBuilder_tiontion_Set_On_EntityType() // Act var user = builder.EntitySet("IdentityUsers"); user.EntityType.HasKey(p => new { p.Provider, p.UserId }); - user.EntityType.HasDescription("Test description"); + user.EntityType.HasDescription("Summary", "Detailed description"); var edmModel = builder.GetEdmModel(); // Assert Assert.NotNull(edmModel); var entityType = edmModel.SchemaElements.OfType().First(); - var annotation = entityType.VocabularyAnnotations(edmModel).First(); - Assert.Equal("Description", annotation.Term.Name); - var value = Assert.IsType(annotation.Value); - Assert.Equal("Test description", value.Value); + AssertAnnotations(entityType, edmModel); } - + + private static void AssertAnnotations(IEdmVocabularyAnnotatable entityType, IEdmModel edmModel) + { + var annotation = entityType.VocabularyAnnotations(edmModel).First(e => e.Term.Name == "Description"); + var value = Assert.IsType(annotation.Value); + Assert.Equal("Summary", value.Value); + annotation = entityType.VocabularyAnnotations(edmModel).First(e => e.Term.Name == "LongDescription"); + value = Assert.IsType(annotation.Value); + Assert.Equal("Detailed description", value.Value); + } + [Fact] public void ConventionModelBuilder_Description_Set_On_Property() { @@ -3165,7 +3173,9 @@ public void ConventionModelBuilder_Description_Set_On_Property() // Act var user = builder.EntitySet("IdentityUsers"); user.EntityType.HasKey(p => new { p.Provider, p.UserId }); - user.EntityType.Property(i => i.Name).Description = "Returns the name of the user."; + var propertyConfiguration = user.EntityType.Property(i => i.Name); + propertyConfiguration.Description = "Summary"; + propertyConfiguration.LongDescription = "Detailed description"; var edmModel = builder.GetEdmModel(); // Assert @@ -3173,30 +3183,23 @@ public void ConventionModelBuilder_Description_Set_On_Property() var entityType = edmModel.SchemaElements.OfType().First(); var property = entityType.Properties().First(p => p.Name == "Name"); - var annotation = property.VocabularyAnnotations(edmModel).First(); - Assert.Equal("Description", annotation.Term.Name); - var value = Assert.IsType(annotation.Value); - Assert.Equal("Returns the name of the user.", value.Value); + AssertAnnotations(property, edmModel); } - + [Fact] public void ConventionModelBuilder_Description_Set_On_Entity_Via_Description_Attribute() { - // Arrange - var builder = ODataConventionModelBuilderFactory.Create(); - - // Act - builder.EntitySet("DescriptionEntities"); - var edmModel = builder.GetEdmModel(); + // Arrange + var builder = ODataConventionModelBuilderFactory.Create(); - // Assert - Assert.NotNull(edmModel); - var entityType = edmModel.SchemaElements.OfType().First(); - var annotation = entityType.VocabularyAnnotations(edmModel).First(); - Assert.Equal("Description", annotation.Term.Name); - var value = Assert.IsType(annotation.Value); - Assert.Equal("The summary for this type.", value.Value); + // Act + builder.EntitySet("DescriptionEntities"); + var edmModel = builder.GetEdmModel(); + // Assert + Assert.NotNull(edmModel); + var entityType = edmModel.SchemaElements.OfType().First(); + AssertAnnotations(entityType, edmModel); } [Fact] @@ -3214,10 +3217,7 @@ public void ConventionModelBuilder_Description_Set_On_Property_Via_Description_A var entityType = edmModel.SchemaElements.OfType().First(); var property = entityType.Properties().First(p => p.Name == "Id"); - var annotation = property.VocabularyAnnotations(edmModel).First(); - Assert.Equal("Description", annotation.Term.Name); - var value = Assert.IsType(annotation.Value); - Assert.Equal("The summary for this property.", value.Value); + AssertAnnotations(property, edmModel); } [Fact] @@ -3806,7 +3806,7 @@ public class MaxLengthEntity public string NonLength { get; set; } } - [Description("The summary for this type.", LongDescription = "The long description for this type.")] + [Description("Summary", LongDescription = "Detailed description")] public class DescriptionEntity { [Key] @@ -3816,7 +3816,7 @@ public class DescriptionEntity public class DescriptionPropertyEntity { [Key] - [Description("The summary for this property.", LongDescription = "The long description for this property.")] + [Description("Summary", LongDescription = "Detailed description")] public int Id { get; set; } } } From ccc7369c27c922c9df51eb259bfdca14be13bfc4 Mon Sep 17 00:00:00 2001 From: Henning Krause Date: Sat, 18 Jul 2020 15:17:05 +0200 Subject: [PATCH 3/3] Cleanup --- .../Builder/Conventions/DescriptionAnnotationConvention.cs | 2 -- .../Builder/EdmModelHelperMethods.cs | 4 +--- .../Builder/ODataConventionModelBuilder.cs | 1 + .../Builder/PropertyConfiguration.cs | 1 - .../Builder/StructuralTypeConfiguration.cs | 2 -- 5 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/DescriptionAnnotationConvention.cs b/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/DescriptionAnnotationConvention.cs index 08dc5f2c7b..8cb41e9105 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/DescriptionAnnotationConvention.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/Conventions/DescriptionAnnotationConvention.cs @@ -31,8 +31,6 @@ public void Apply(IEdmTypeConfiguration edmTypeConfiguration, ODataConventionMod } } } - - } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/EdmModelHelperMethods.cs b/src/Microsoft.AspNet.OData.Shared/Builder/EdmModelHelperMethods.cs index d877bfd354..7267e217e6 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/EdmModelHelperMethods.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/EdmModelHelperMethods.cs @@ -66,10 +66,8 @@ public static IEdmModel BuildEdmModel(ODataModelBuilder builder) // build the map from IEdmEntityType to IEdmFunctionImport model.SetAnnotationValue(model, new BindableOperationFinder(model)); - + // Add annotations for documentation AddDescriptionAnnotations(builder, model, edmTypeMap); - - return model; } diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/ODataConventionModelBuilder.cs b/src/Microsoft.AspNet.OData.Shared/Builder/ODataConventionModelBuilder.cs index 720c721a85..860b996120 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/ODataConventionModelBuilder.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/ODataConventionModelBuilder.cs @@ -72,6 +72,7 @@ public partial class ODataConventionModelBuilder : ODataModelBuilder new ActionLinkGenerationConvention(), new FunctionLinkGenerationConvention(), + // Documentation conventions new DescriptionAnnotationConvention(), }; diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/PropertyConfiguration.cs b/src/Microsoft.AspNet.OData.Shared/Builder/PropertyConfiguration.cs index 6778582a85..9e3a04a0c5 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/PropertyConfiguration.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/PropertyConfiguration.cs @@ -42,7 +42,6 @@ protected PropertyConfiguration(PropertyInfo property, StructuralTypeConfigurati QueryConfiguration = new QueryConfiguration(); DerivedTypeConstraints = new DerivedTypeConstraintConfiguration(); } - /// /// Gets or sets the name of the property. diff --git a/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfiguration.cs b/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfiguration.cs index 1c5605ecdc..be4dc1eeea 100644 --- a/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfiguration.cs +++ b/src/Microsoft.AspNet.OData.Shared/Builder/StructuralTypeConfiguration.cs @@ -87,7 +87,6 @@ public virtual string FullName } } - /// /// Gets or sets the summary for the property. /// @@ -105,7 +104,6 @@ public string LongDescription get => _longDescription; set => _longDescription = value ?? throw Error.PropertyNull(); } - /// /// Gets or sets the namespace of this EDM type.